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 behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod element;
22mod git;
23mod highlight_matching_bracket;
24mod hover_links;
25pub mod hover_popover;
26mod indent_guides;
27mod inlay_hint_cache;
28pub mod items;
29mod jsx_tag_auto_close;
30mod linked_editing_ranges;
31mod lsp_colors;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod edit_prediction_tests;
46#[cfg(test)]
47mod editor_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
54pub use edit_prediction::Direction;
55pub use editor_settings::{
56 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
57 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
58};
59pub use element::{
60 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
61};
62pub use git::blame::BlameRenderer;
63pub use hover_popover::hover_markdown_style;
64pub use items::MAX_TAB_TITLE_LEN;
65pub use lsp::CompletionContext;
66pub use lsp_ext::lsp_tasks;
67pub use multi_buffer::{
68 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
69 RowInfo, ToOffset, ToPoint,
70};
71pub use proposed_changes_editor::{
72 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
73};
74pub use text::Bias;
75
76use ::git::{
77 Restore,
78 blame::{BlameEntry, ParsedCommitMessage},
79};
80use aho_corasick::AhoCorasick;
81use anyhow::{Context as _, Result, anyhow};
82use blink_manager::BlinkManager;
83use buffer_diff::DiffHunkStatus;
84use client::{Collaborator, ParticipantIndex, parse_zed_link};
85use clock::ReplicaId;
86use code_context_menus::{
87 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
88 CompletionsMenu, ContextMenuOrigin,
89};
90use collections::{BTreeMap, HashMap, HashSet, VecDeque};
91use convert_case::{Case, Casing};
92use dap::TelemetrySpawnLocation;
93use display_map::*;
94use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
95use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
96use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
97use futures::{
98 FutureExt, StreamExt as _,
99 future::{self, Shared, join},
100 stream::FuturesUnordered,
101};
102use fuzzy::{StringMatch, StringMatchCandidate};
103use git::blame::{GitBlame, GlobalBlameRenderer};
104use gpui::{
105 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
106 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
107 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
108 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
109 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
110 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
111 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
112 div, point, prelude::*, pulsating_between, px, relative, size,
113};
114use highlight_matching_bracket::refresh_matching_bracket_highlights;
115use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
116use hover_popover::{HoverState, hide_hover};
117use indent_guides::ActiveIndentGuidesState;
118use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
119use itertools::{Either, Itertools};
120use language::{
121 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
122 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
123 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
124 IndentSize, Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal,
125 TextObject, TransactionId, TreeSitterOptions, WordsQuery,
126 language_settings::{
127 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
128 all_language_settings, language_settings,
129 },
130 point_from_lsp, point_to_lsp, text_diff_with_options,
131};
132use linked_editing_ranges::refresh_linked_ranges;
133use lsp::{
134 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
135 LanguageServerId,
136};
137use lsp_colors::LspColorData;
138use markdown::Markdown;
139use mouse_context_menu::MouseContextMenu;
140use movement::TextLayoutDetails;
141use multi_buffer::{
142 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
143 ToOffsetUtf16,
144};
145use parking_lot::Mutex;
146use persistence::DB;
147use project::{
148 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
149 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint,
150 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectPath,
151 ProjectTransaction, TaskSourceKind,
152 debugger::{
153 breakpoint_store::{
154 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
155 BreakpointStore, BreakpointStoreEvent,
156 },
157 session::{Session, SessionEvent},
158 },
159 git_store::{GitStoreEvent, RepositoryEvent},
160 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
161 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
162};
163use rand::seq::SliceRandom;
164use rpc::{ErrorCode, ErrorExt, proto::PeerId};
165use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
166use selections_collection::{
167 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
168};
169use serde::{Deserialize, Serialize};
170use settings::{GitGutterSetting, Settings, SettingsLocation, SettingsStore, update_settings_file};
171use smallvec::{SmallVec, smallvec};
172use snippet::Snippet;
173use std::{
174 any::{Any, TypeId},
175 borrow::Cow,
176 cell::{OnceCell, RefCell},
177 cmp::{self, Ordering, Reverse},
178 iter::{self, Peekable},
179 mem,
180 num::NonZeroU32,
181 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
182 path::{Path, PathBuf},
183 rc::Rc,
184 sync::Arc,
185 time::{Duration, Instant},
186};
187use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
188use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
189use theme::{
190 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
191 observe_buffer_font_size_adjustment,
192};
193use ui::{
194 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
195 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
196};
197use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
198use workspace::{
199 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
200 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
201 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
202 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
203 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
204 searchable::SearchEvent,
205};
206
207use crate::{
208 code_context_menus::CompletionsMenuSource,
209 editor_settings::MultiCursorModifier,
210 hover_links::{find_url, find_url_from_range},
211 scroll::{ScrollOffset, ScrollPixelOffset},
212 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
213};
214
215pub const FILE_HEADER_HEIGHT: u32 = 2;
216pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
217const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
218const MAX_LINE_LEN: usize = 1024;
219const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
220const MAX_SELECTION_HISTORY_LEN: usize = 1024;
221pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
222#[doc(hidden)]
223pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
224pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
225
226pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
227pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
228pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
229pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
230
231pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
232pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
233pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
234
235pub type RenderDiffHunkControlsFn = Arc<
236 dyn Fn(
237 u32,
238 &DiffHunkStatus,
239 Range<Anchor>,
240 bool,
241 Pixels,
242 &Entity<Editor>,
243 &mut Window,
244 &mut App,
245 ) -> AnyElement,
246>;
247
248enum ReportEditorEvent {
249 Saved { auto_saved: bool },
250 EditorOpened,
251 Closed,
252}
253
254impl ReportEditorEvent {
255 pub fn event_type(&self) -> &'static str {
256 match self {
257 Self::Saved { .. } => "Editor Saved",
258 Self::EditorOpened => "Editor Opened",
259 Self::Closed => "Editor Closed",
260 }
261 }
262}
263
264struct InlineValueCache {
265 enabled: bool,
266 inlays: Vec<InlayId>,
267 refresh_task: Task<Option<()>>,
268}
269
270impl InlineValueCache {
271 fn new(enabled: bool) -> Self {
272 Self {
273 enabled,
274 inlays: Vec::new(),
275 refresh_task: Task::ready(None),
276 }
277 }
278}
279
280#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
281pub enum InlayId {
282 EditPrediction(u32),
283 DebuggerValue(u32),
284 // LSP
285 Hint(u32),
286 Color(u32),
287}
288
289impl InlayId {
290 fn id(&self) -> u32 {
291 match self {
292 Self::EditPrediction(id) => *id,
293 Self::DebuggerValue(id) => *id,
294 Self::Hint(id) => *id,
295 Self::Color(id) => *id,
296 }
297 }
298}
299
300pub enum ActiveDebugLine {}
301pub enum DebugStackFrameLine {}
302enum DocumentHighlightRead {}
303enum DocumentHighlightWrite {}
304enum InputComposition {}
305pub enum PendingInput {}
306enum SelectedTextHighlight {}
307
308pub enum ConflictsOuter {}
309pub enum ConflictsOurs {}
310pub enum ConflictsTheirs {}
311pub enum ConflictsOursMarker {}
312pub enum ConflictsTheirsMarker {}
313
314#[derive(Debug, Copy, Clone, PartialEq, Eq)]
315pub enum Navigated {
316 Yes,
317 No,
318}
319
320impl Navigated {
321 pub fn from_bool(yes: bool) -> Navigated {
322 if yes { Navigated::Yes } else { Navigated::No }
323 }
324}
325
326#[derive(Debug, Clone, PartialEq, Eq)]
327enum DisplayDiffHunk {
328 Folded {
329 display_row: DisplayRow,
330 },
331 Unfolded {
332 is_created_file: bool,
333 diff_base_byte_range: Range<usize>,
334 display_row_range: Range<DisplayRow>,
335 multi_buffer_range: Range<Anchor>,
336 status: DiffHunkStatus,
337 },
338}
339
340pub enum HideMouseCursorOrigin {
341 TypingAction,
342 MovementAction,
343}
344
345pub fn init_settings(cx: &mut App) {
346 EditorSettings::register(cx);
347}
348
349pub fn init(cx: &mut App) {
350 init_settings(cx);
351
352 cx.set_global(GlobalBlameRenderer(Arc::new(())));
353
354 workspace::register_project_item::<Editor>(cx);
355 workspace::FollowableViewRegistry::register::<Editor>(cx);
356 workspace::register_serializable_item::<Editor>(cx);
357
358 cx.observe_new(
359 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
360 workspace.register_action(Editor::new_file);
361 workspace.register_action(Editor::new_file_split);
362 workspace.register_action(Editor::new_file_vertical);
363 workspace.register_action(Editor::new_file_horizontal);
364 workspace.register_action(Editor::cancel_language_server_work);
365 workspace.register_action(Editor::toggle_focus);
366 },
367 )
368 .detach();
369
370 cx.on_action(move |_: &workspace::NewFile, cx| {
371 let app_state = workspace::AppState::global(cx);
372 if let Some(app_state) = app_state.upgrade() {
373 workspace::open_new(
374 Default::default(),
375 app_state,
376 cx,
377 |workspace, window, cx| {
378 Editor::new_file(workspace, &Default::default(), window, cx)
379 },
380 )
381 .detach();
382 }
383 });
384 cx.on_action(move |_: &workspace::NewWindow, cx| {
385 let app_state = workspace::AppState::global(cx);
386 if let Some(app_state) = app_state.upgrade() {
387 workspace::open_new(
388 Default::default(),
389 app_state,
390 cx,
391 |workspace, window, cx| {
392 cx.activate(true);
393 Editor::new_file(workspace, &Default::default(), window, cx)
394 },
395 )
396 .detach();
397 }
398 });
399}
400
401pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
402 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
403}
404
405pub trait DiagnosticRenderer {
406 fn render_group(
407 &self,
408 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
409 buffer_id: BufferId,
410 snapshot: EditorSnapshot,
411 editor: WeakEntity<Editor>,
412 cx: &mut App,
413 ) -> Vec<BlockProperties<Anchor>>;
414
415 fn render_hover(
416 &self,
417 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
418 range: Range<Point>,
419 buffer_id: BufferId,
420 cx: &mut App,
421 ) -> Option<Entity<markdown::Markdown>>;
422
423 fn open_link(
424 &self,
425 editor: &mut Editor,
426 link: SharedString,
427 window: &mut Window,
428 cx: &mut Context<Editor>,
429 );
430}
431
432pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
433
434impl GlobalDiagnosticRenderer {
435 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
436 cx.try_global::<Self>().map(|g| g.0.clone())
437 }
438}
439
440impl gpui::Global for GlobalDiagnosticRenderer {}
441pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
442 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
443}
444
445pub struct SearchWithinRange;
446
447trait InvalidationRegion {
448 fn ranges(&self) -> &[Range<Anchor>];
449}
450
451#[derive(Clone, Debug, PartialEq)]
452pub enum SelectPhase {
453 Begin {
454 position: DisplayPoint,
455 add: bool,
456 click_count: usize,
457 },
458 BeginColumnar {
459 position: DisplayPoint,
460 reset: bool,
461 mode: ColumnarMode,
462 goal_column: u32,
463 },
464 Extend {
465 position: DisplayPoint,
466 click_count: usize,
467 },
468 Update {
469 position: DisplayPoint,
470 goal_column: u32,
471 scroll_delta: gpui::Point<f32>,
472 },
473 End,
474}
475
476#[derive(Clone, Debug, PartialEq)]
477pub enum ColumnarMode {
478 FromMouse,
479 FromSelection,
480}
481
482#[derive(Clone, Debug)]
483pub enum SelectMode {
484 Character,
485 Word(Range<Anchor>),
486 Line(Range<Anchor>),
487 All,
488}
489
490#[derive(Clone, PartialEq, Eq, Debug)]
491pub enum EditorMode {
492 SingleLine,
493 AutoHeight {
494 min_lines: usize,
495 max_lines: Option<usize>,
496 },
497 Full {
498 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
499 scale_ui_elements_with_buffer_font_size: bool,
500 /// When set to `true`, the editor will render a background for the active line.
501 show_active_line_background: bool,
502 /// When set to `true`, the editor's height will be determined by its content.
503 sized_by_content: bool,
504 },
505 Minimap {
506 parent: WeakEntity<Editor>,
507 },
508}
509
510impl EditorMode {
511 pub fn full() -> Self {
512 Self::Full {
513 scale_ui_elements_with_buffer_font_size: true,
514 show_active_line_background: true,
515 sized_by_content: false,
516 }
517 }
518
519 #[inline]
520 pub fn is_full(&self) -> bool {
521 matches!(self, Self::Full { .. })
522 }
523
524 #[inline]
525 pub fn is_single_line(&self) -> bool {
526 matches!(self, Self::SingleLine { .. })
527 }
528
529 #[inline]
530 fn is_minimap(&self) -> bool {
531 matches!(self, Self::Minimap { .. })
532 }
533}
534
535#[derive(Copy, Clone, Debug)]
536pub enum SoftWrap {
537 /// Prefer not to wrap at all.
538 ///
539 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
540 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
541 GitDiff,
542 /// Prefer a single line generally, unless an overly long line is encountered.
543 None,
544 /// Soft wrap lines that exceed the editor width.
545 EditorWidth,
546 /// Soft wrap lines at the preferred line length.
547 Column(u32),
548 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
549 Bounded(u32),
550}
551
552#[derive(Clone)]
553pub struct EditorStyle {
554 pub background: Hsla,
555 pub border: Hsla,
556 pub local_player: PlayerColor,
557 pub text: TextStyle,
558 pub scrollbar_width: Pixels,
559 pub syntax: Arc<SyntaxTheme>,
560 pub status: StatusColors,
561 pub inlay_hints_style: HighlightStyle,
562 pub edit_prediction_styles: EditPredictionStyles,
563 pub unnecessary_code_fade: f32,
564 pub show_underlines: bool,
565}
566
567impl Default for EditorStyle {
568 fn default() -> Self {
569 Self {
570 background: Hsla::default(),
571 border: Hsla::default(),
572 local_player: PlayerColor::default(),
573 text: TextStyle::default(),
574 scrollbar_width: Pixels::default(),
575 syntax: Default::default(),
576 // HACK: Status colors don't have a real default.
577 // We should look into removing the status colors from the editor
578 // style and retrieve them directly from the theme.
579 status: StatusColors::dark(),
580 inlay_hints_style: HighlightStyle::default(),
581 edit_prediction_styles: EditPredictionStyles {
582 insertion: HighlightStyle::default(),
583 whitespace: HighlightStyle::default(),
584 },
585 unnecessary_code_fade: Default::default(),
586 show_underlines: true,
587 }
588 }
589}
590
591pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
592 let show_background = language_settings::language_settings(None, None, cx)
593 .inlay_hints
594 .show_background;
595
596 let mut style = cx.theme().syntax().get("hint");
597
598 if style.color.is_none() {
599 style.color = Some(cx.theme().status().hint);
600 }
601
602 if !show_background {
603 style.background_color = None;
604 return style;
605 }
606
607 if style.background_color.is_none() {
608 style.background_color = Some(cx.theme().status().hint_background);
609 }
610
611 style
612}
613
614pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
615 EditPredictionStyles {
616 insertion: HighlightStyle {
617 color: Some(cx.theme().status().predictive),
618 ..HighlightStyle::default()
619 },
620 whitespace: HighlightStyle {
621 background_color: Some(cx.theme().status().created_background),
622 ..HighlightStyle::default()
623 },
624 }
625}
626
627type CompletionId = usize;
628
629pub(crate) enum EditDisplayMode {
630 TabAccept,
631 DiffPopover,
632 Inline,
633}
634
635enum EditPrediction {
636 Edit {
637 edits: Vec<(Range<Anchor>, String)>,
638 edit_preview: Option<EditPreview>,
639 display_mode: EditDisplayMode,
640 snapshot: BufferSnapshot,
641 },
642 /// Move to a specific location in the active editor
643 MoveWithin {
644 target: Anchor,
645 snapshot: BufferSnapshot,
646 },
647 /// Move to a specific location in a different editor (not the active one)
648 MoveOutside {
649 target: language::Anchor,
650 snapshot: BufferSnapshot,
651 },
652}
653
654struct EditPredictionState {
655 inlay_ids: Vec<InlayId>,
656 completion: EditPrediction,
657 completion_id: Option<SharedString>,
658 invalidation_range: Option<Range<Anchor>>,
659}
660
661enum EditPredictionSettings {
662 Disabled,
663 Enabled {
664 show_in_menu: bool,
665 preview_requires_modifier: bool,
666 },
667}
668
669enum EditPredictionHighlight {}
670
671#[derive(Debug, Clone)]
672struct InlineDiagnostic {
673 message: SharedString,
674 group_id: usize,
675 is_primary: bool,
676 start: Point,
677 severity: lsp::DiagnosticSeverity,
678}
679
680pub enum MenuEditPredictionsPolicy {
681 Never,
682 ByProvider,
683}
684
685pub enum EditPredictionPreview {
686 /// Modifier is not pressed
687 Inactive { released_too_fast: bool },
688 /// Modifier pressed
689 Active {
690 since: Instant,
691 previous_scroll_position: Option<ScrollAnchor>,
692 },
693}
694
695impl EditPredictionPreview {
696 pub fn released_too_fast(&self) -> bool {
697 match self {
698 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
699 EditPredictionPreview::Active { .. } => false,
700 }
701 }
702
703 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
704 if let EditPredictionPreview::Active {
705 previous_scroll_position,
706 ..
707 } = self
708 {
709 *previous_scroll_position = scroll_position;
710 }
711 }
712}
713
714pub struct ContextMenuOptions {
715 pub min_entries_visible: usize,
716 pub max_entries_visible: usize,
717 pub placement: Option<ContextMenuPlacement>,
718}
719
720#[derive(Debug, Clone, PartialEq, Eq)]
721pub enum ContextMenuPlacement {
722 Above,
723 Below,
724}
725
726#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
727struct EditorActionId(usize);
728
729impl EditorActionId {
730 pub fn post_inc(&mut self) -> Self {
731 let answer = self.0;
732
733 *self = Self(answer + 1);
734
735 Self(answer)
736 }
737}
738
739// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
740// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
741
742type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
743type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
744
745#[derive(Default)]
746struct ScrollbarMarkerState {
747 scrollbar_size: Size<Pixels>,
748 dirty: bool,
749 markers: Arc<[PaintQuad]>,
750 pending_refresh: Option<Task<Result<()>>>,
751}
752
753impl ScrollbarMarkerState {
754 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
755 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
756 }
757}
758
759#[derive(Clone, Copy, PartialEq, Eq)]
760pub enum MinimapVisibility {
761 Disabled,
762 Enabled {
763 /// The configuration currently present in the users settings.
764 setting_configuration: bool,
765 /// Whether to override the currently set visibility from the users setting.
766 toggle_override: bool,
767 },
768}
769
770impl MinimapVisibility {
771 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
772 if mode.is_full() {
773 Self::Enabled {
774 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
775 toggle_override: false,
776 }
777 } else {
778 Self::Disabled
779 }
780 }
781
782 fn hidden(&self) -> Self {
783 match *self {
784 Self::Enabled {
785 setting_configuration,
786 ..
787 } => Self::Enabled {
788 setting_configuration,
789 toggle_override: setting_configuration,
790 },
791 Self::Disabled => Self::Disabled,
792 }
793 }
794
795 fn disabled(&self) -> bool {
796 matches!(*self, Self::Disabled)
797 }
798
799 fn settings_visibility(&self) -> bool {
800 match *self {
801 Self::Enabled {
802 setting_configuration,
803 ..
804 } => setting_configuration,
805 _ => false,
806 }
807 }
808
809 fn visible(&self) -> bool {
810 match *self {
811 Self::Enabled {
812 setting_configuration,
813 toggle_override,
814 } => setting_configuration ^ toggle_override,
815 _ => false,
816 }
817 }
818
819 fn toggle_visibility(&self) -> Self {
820 match *self {
821 Self::Enabled {
822 toggle_override,
823 setting_configuration,
824 } => Self::Enabled {
825 setting_configuration,
826 toggle_override: !toggle_override,
827 },
828 Self::Disabled => Self::Disabled,
829 }
830 }
831}
832
833#[derive(Clone, Debug)]
834struct RunnableTasks {
835 templates: Vec<(TaskSourceKind, TaskTemplate)>,
836 offset: multi_buffer::Anchor,
837 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
838 column: u32,
839 // Values of all named captures, including those starting with '_'
840 extra_variables: HashMap<String, String>,
841 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
842 context_range: Range<BufferOffset>,
843}
844
845impl RunnableTasks {
846 fn resolve<'a>(
847 &'a self,
848 cx: &'a task::TaskContext,
849 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
850 self.templates.iter().filter_map(|(kind, template)| {
851 template
852 .resolve_task(&kind.to_id_base(), cx)
853 .map(|task| (kind.clone(), task))
854 })
855 }
856}
857
858#[derive(Clone)]
859pub struct ResolvedTasks {
860 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
861 position: Anchor,
862}
863
864#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
865struct BufferOffset(usize);
866
867/// Addons allow storing per-editor state in other crates (e.g. Vim)
868pub trait Addon: 'static {
869 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
870
871 fn render_buffer_header_controls(
872 &self,
873 _: &ExcerptInfo,
874 _: &Window,
875 _: &App,
876 ) -> Option<AnyElement> {
877 None
878 }
879
880 fn to_any(&self) -> &dyn std::any::Any;
881
882 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
883 None
884 }
885}
886
887struct ChangeLocation {
888 current: Option<Vec<Anchor>>,
889 original: Vec<Anchor>,
890}
891impl ChangeLocation {
892 fn locations(&self) -> &[Anchor] {
893 self.current.as_ref().unwrap_or(&self.original)
894 }
895}
896
897/// A set of caret positions, registered when the editor was edited.
898pub struct ChangeList {
899 changes: Vec<ChangeLocation>,
900 /// Currently "selected" change.
901 position: Option<usize>,
902}
903
904impl ChangeList {
905 pub fn new() -> Self {
906 Self {
907 changes: Vec::new(),
908 position: None,
909 }
910 }
911
912 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
913 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
914 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
915 if self.changes.is_empty() {
916 return None;
917 }
918
919 let prev = self.position.unwrap_or(self.changes.len());
920 let next = if direction == Direction::Prev {
921 prev.saturating_sub(count)
922 } else {
923 (prev + count).min(self.changes.len() - 1)
924 };
925 self.position = Some(next);
926 self.changes.get(next).map(|change| change.locations())
927 }
928
929 /// Adds a new change to the list, resetting the change list position.
930 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
931 self.position.take();
932 if let Some(last) = self.changes.last_mut()
933 && group
934 {
935 last.current = Some(new_positions)
936 } else {
937 self.changes.push(ChangeLocation {
938 original: new_positions,
939 current: None,
940 });
941 }
942 }
943
944 pub fn last(&self) -> Option<&[Anchor]> {
945 self.changes.last().map(|change| change.locations())
946 }
947
948 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
949 self.changes.last().map(|change| change.original.as_slice())
950 }
951
952 pub fn invert_last_group(&mut self) {
953 if let Some(last) = self.changes.last_mut()
954 && let Some(current) = last.current.as_mut()
955 {
956 mem::swap(&mut last.original, current);
957 }
958 }
959}
960
961#[derive(Clone)]
962struct InlineBlamePopoverState {
963 scroll_handle: ScrollHandle,
964 commit_message: Option<ParsedCommitMessage>,
965 markdown: Entity<Markdown>,
966}
967
968struct InlineBlamePopover {
969 position: gpui::Point<Pixels>,
970 hide_task: Option<Task<()>>,
971 popover_bounds: Option<Bounds<Pixels>>,
972 popover_state: InlineBlamePopoverState,
973 keyboard_grace: bool,
974}
975
976enum SelectionDragState {
977 /// State when no drag related activity is detected.
978 None,
979 /// State when the mouse is down on a selection that is about to be dragged.
980 ReadyToDrag {
981 selection: Selection<Anchor>,
982 click_position: gpui::Point<Pixels>,
983 mouse_down_time: Instant,
984 },
985 /// State when the mouse is dragging the selection in the editor.
986 Dragging {
987 selection: Selection<Anchor>,
988 drop_cursor: Selection<Anchor>,
989 hide_drop_cursor: bool,
990 },
991}
992
993enum ColumnarSelectionState {
994 FromMouse {
995 selection_tail: Anchor,
996 display_point: Option<DisplayPoint>,
997 },
998 FromSelection {
999 selection_tail: Anchor,
1000 },
1001}
1002
1003/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1004/// a breakpoint on them.
1005#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1006struct PhantomBreakpointIndicator {
1007 display_row: DisplayRow,
1008 /// There's a small debounce between hovering over the line and showing the indicator.
1009 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1010 is_active: bool,
1011 collides_with_existing_breakpoint: bool,
1012}
1013
1014/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1015///
1016/// See the [module level documentation](self) for more information.
1017pub struct Editor {
1018 focus_handle: FocusHandle,
1019 last_focused_descendant: Option<WeakFocusHandle>,
1020 /// The text buffer being edited
1021 buffer: Entity<MultiBuffer>,
1022 /// Map of how text in the buffer should be displayed.
1023 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1024 pub display_map: Entity<DisplayMap>,
1025 placeholder_display_map: Option<Entity<DisplayMap>>,
1026 pub selections: SelectionsCollection,
1027 pub scroll_manager: ScrollManager,
1028 /// When inline assist editors are linked, they all render cursors because
1029 /// typing enters text into each of them, even the ones that aren't focused.
1030 pub(crate) show_cursor_when_unfocused: bool,
1031 columnar_selection_state: Option<ColumnarSelectionState>,
1032 add_selections_state: Option<AddSelectionsState>,
1033 select_next_state: Option<SelectNextState>,
1034 select_prev_state: Option<SelectNextState>,
1035 selection_history: SelectionHistory,
1036 defer_selection_effects: bool,
1037 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1038 autoclose_regions: Vec<AutocloseRegion>,
1039 snippet_stack: InvalidationStack<SnippetState>,
1040 select_syntax_node_history: SelectSyntaxNodeHistory,
1041 ime_transaction: Option<TransactionId>,
1042 pub diagnostics_max_severity: DiagnosticSeverity,
1043 active_diagnostics: ActiveDiagnostic,
1044 show_inline_diagnostics: bool,
1045 inline_diagnostics_update: Task<()>,
1046 inline_diagnostics_enabled: bool,
1047 diagnostics_enabled: bool,
1048 word_completions_enabled: bool,
1049 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1050 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1051 hard_wrap: Option<usize>,
1052 project: Option<Entity<Project>>,
1053 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1054 completion_provider: Option<Rc<dyn CompletionProvider>>,
1055 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1056 blink_manager: Entity<BlinkManager>,
1057 show_cursor_names: bool,
1058 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1059 pub show_local_selections: bool,
1060 mode: EditorMode,
1061 show_breadcrumbs: bool,
1062 show_gutter: bool,
1063 show_scrollbars: ScrollbarAxes,
1064 minimap_visibility: MinimapVisibility,
1065 offset_content: bool,
1066 disable_expand_excerpt_buttons: bool,
1067 show_line_numbers: Option<bool>,
1068 use_relative_line_numbers: Option<bool>,
1069 show_git_diff_gutter: Option<bool>,
1070 show_code_actions: Option<bool>,
1071 show_runnables: Option<bool>,
1072 show_breakpoints: Option<bool>,
1073 show_wrap_guides: Option<bool>,
1074 show_indent_guides: Option<bool>,
1075 highlight_order: usize,
1076 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1077 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1078 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1079 scrollbar_marker_state: ScrollbarMarkerState,
1080 active_indent_guides_state: ActiveIndentGuidesState,
1081 nav_history: Option<ItemNavHistory>,
1082 context_menu: RefCell<Option<CodeContextMenu>>,
1083 context_menu_options: Option<ContextMenuOptions>,
1084 mouse_context_menu: Option<MouseContextMenu>,
1085 completion_tasks: Vec<(CompletionId, Task<()>)>,
1086 inline_blame_popover: Option<InlineBlamePopover>,
1087 inline_blame_popover_show_task: Option<Task<()>>,
1088 signature_help_state: SignatureHelpState,
1089 auto_signature_help: Option<bool>,
1090 find_all_references_task_sources: Vec<Anchor>,
1091 next_completion_id: CompletionId,
1092 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1093 code_actions_task: Option<Task<Result<()>>>,
1094 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1095 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1096 document_highlights_task: Option<Task<()>>,
1097 linked_editing_range_task: Option<Task<Option<()>>>,
1098 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1099 pending_rename: Option<RenameState>,
1100 searchable: bool,
1101 cursor_shape: CursorShape,
1102 current_line_highlight: Option<CurrentLineHighlight>,
1103 collapse_matches: bool,
1104 autoindent_mode: Option<AutoindentMode>,
1105 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1106 input_enabled: bool,
1107 use_modal_editing: bool,
1108 read_only: bool,
1109 leader_id: Option<CollaboratorId>,
1110 remote_id: Option<ViewId>,
1111 pub hover_state: HoverState,
1112 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1113 gutter_hovered: bool,
1114 hovered_link_state: Option<HoveredLinkState>,
1115 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1116 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1117 active_edit_prediction: Option<EditPredictionState>,
1118 /// Used to prevent flickering as the user types while the menu is open
1119 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1120 edit_prediction_settings: EditPredictionSettings,
1121 edit_predictions_hidden_for_vim_mode: bool,
1122 show_edit_predictions_override: Option<bool>,
1123 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1124 edit_prediction_preview: EditPredictionPreview,
1125 edit_prediction_indent_conflict: bool,
1126 edit_prediction_requires_modifier_in_indent_conflict: bool,
1127 inlay_hint_cache: InlayHintCache,
1128 next_inlay_id: u32,
1129 next_color_inlay_id: u32,
1130 _subscriptions: Vec<Subscription>,
1131 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1132 gutter_dimensions: GutterDimensions,
1133 style: Option<EditorStyle>,
1134 text_style_refinement: Option<TextStyleRefinement>,
1135 next_editor_action_id: EditorActionId,
1136 editor_actions: Rc<
1137 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1138 >,
1139 use_autoclose: bool,
1140 use_auto_surround: bool,
1141 auto_replace_emoji_shortcode: bool,
1142 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1143 show_git_blame_gutter: bool,
1144 show_git_blame_inline: bool,
1145 show_git_blame_inline_delay_task: Option<Task<()>>,
1146 git_blame_inline_enabled: bool,
1147 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1148 serialize_dirty_buffers: bool,
1149 show_selection_menu: Option<bool>,
1150 blame: Option<Entity<GitBlame>>,
1151 blame_subscription: Option<Subscription>,
1152 custom_context_menu: Option<
1153 Box<
1154 dyn 'static
1155 + Fn(
1156 &mut Self,
1157 DisplayPoint,
1158 &mut Window,
1159 &mut Context<Self>,
1160 ) -> Option<Entity<ui::ContextMenu>>,
1161 >,
1162 >,
1163 last_bounds: Option<Bounds<Pixels>>,
1164 last_position_map: Option<Rc<PositionMap>>,
1165 expect_bounds_change: Option<Bounds<Pixels>>,
1166 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1167 tasks_update_task: Option<Task<()>>,
1168 breakpoint_store: Option<Entity<BreakpointStore>>,
1169 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1170 hovered_diff_hunk_row: Option<DisplayRow>,
1171 pull_diagnostics_task: Task<()>,
1172 in_project_search: bool,
1173 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1174 breadcrumb_header: Option<String>,
1175 focused_block: Option<FocusedBlock>,
1176 next_scroll_position: NextScrollCursorCenterTopBottom,
1177 addons: HashMap<TypeId, Box<dyn Addon>>,
1178 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1179 load_diff_task: Option<Shared<Task<()>>>,
1180 /// Whether we are temporarily displaying a diff other than git's
1181 temporary_diff_override: bool,
1182 selection_mark_mode: bool,
1183 toggle_fold_multiple_buffers: Task<()>,
1184 _scroll_cursor_center_top_bottom_task: Task<()>,
1185 serialize_selections: Task<()>,
1186 serialize_folds: Task<()>,
1187 mouse_cursor_hidden: bool,
1188 minimap: Option<Entity<Self>>,
1189 hide_mouse_mode: HideMouseMode,
1190 pub change_list: ChangeList,
1191 inline_value_cache: InlineValueCache,
1192 selection_drag_state: SelectionDragState,
1193 colors: Option<LspColorData>,
1194 post_scroll_update: Task<()>,
1195 refresh_colors_task: Task<()>,
1196 folding_newlines: Task<()>,
1197 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1198}
1199
1200#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1201enum NextScrollCursorCenterTopBottom {
1202 #[default]
1203 Center,
1204 Top,
1205 Bottom,
1206}
1207
1208impl NextScrollCursorCenterTopBottom {
1209 fn next(&self) -> Self {
1210 match self {
1211 Self::Center => Self::Top,
1212 Self::Top => Self::Bottom,
1213 Self::Bottom => Self::Center,
1214 }
1215 }
1216}
1217
1218#[derive(Clone)]
1219pub struct EditorSnapshot {
1220 pub mode: EditorMode,
1221 show_gutter: bool,
1222 show_line_numbers: Option<bool>,
1223 show_git_diff_gutter: Option<bool>,
1224 show_code_actions: Option<bool>,
1225 show_runnables: Option<bool>,
1226 show_breakpoints: Option<bool>,
1227 git_blame_gutter_max_author_length: Option<usize>,
1228 pub display_snapshot: DisplaySnapshot,
1229 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1230 is_focused: bool,
1231 scroll_anchor: ScrollAnchor,
1232 ongoing_scroll: OngoingScroll,
1233 current_line_highlight: CurrentLineHighlight,
1234 gutter_hovered: bool,
1235}
1236
1237#[derive(Default, Debug, Clone, Copy)]
1238pub struct GutterDimensions {
1239 pub left_padding: Pixels,
1240 pub right_padding: Pixels,
1241 pub width: Pixels,
1242 pub margin: Pixels,
1243 pub git_blame_entries_width: Option<Pixels>,
1244}
1245
1246impl GutterDimensions {
1247 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1248 Self {
1249 margin: Self::default_gutter_margin(font_id, font_size, cx),
1250 ..Default::default()
1251 }
1252 }
1253
1254 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1255 -cx.text_system().descent(font_id, font_size)
1256 }
1257 /// The full width of the space taken up by the gutter.
1258 pub fn full_width(&self) -> Pixels {
1259 self.margin + self.width
1260 }
1261
1262 /// The width of the space reserved for the fold indicators,
1263 /// use alongside 'justify_end' and `gutter_width` to
1264 /// right align content with the line numbers
1265 pub fn fold_area_width(&self) -> Pixels {
1266 self.margin + self.right_padding
1267 }
1268}
1269
1270struct CharacterDimensions {
1271 em_width: Pixels,
1272 em_advance: Pixels,
1273 line_height: Pixels,
1274}
1275
1276#[derive(Debug)]
1277pub struct RemoteSelection {
1278 pub replica_id: ReplicaId,
1279 pub selection: Selection<Anchor>,
1280 pub cursor_shape: CursorShape,
1281 pub collaborator_id: CollaboratorId,
1282 pub line_mode: bool,
1283 pub user_name: Option<SharedString>,
1284 pub color: PlayerColor,
1285}
1286
1287#[derive(Clone, Debug)]
1288struct SelectionHistoryEntry {
1289 selections: Arc<[Selection<Anchor>]>,
1290 select_next_state: Option<SelectNextState>,
1291 select_prev_state: Option<SelectNextState>,
1292 add_selections_state: Option<AddSelectionsState>,
1293}
1294
1295#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1296enum SelectionHistoryMode {
1297 Normal,
1298 Undoing,
1299 Redoing,
1300 Skipping,
1301}
1302
1303#[derive(Clone, PartialEq, Eq, Hash)]
1304struct HoveredCursor {
1305 replica_id: ReplicaId,
1306 selection_id: usize,
1307}
1308
1309impl Default for SelectionHistoryMode {
1310 fn default() -> Self {
1311 Self::Normal
1312 }
1313}
1314
1315#[derive(Debug)]
1316/// SelectionEffects controls the side-effects of updating the selection.
1317///
1318/// The default behaviour does "what you mostly want":
1319/// - it pushes to the nav history if the cursor moved by >10 lines
1320/// - it re-triggers completion requests
1321/// - it scrolls to fit
1322///
1323/// You might want to modify these behaviours. For example when doing a "jump"
1324/// like go to definition, we always want to add to nav history; but when scrolling
1325/// in vim mode we never do.
1326///
1327/// Similarly, you might want to disable scrolling if you don't want the viewport to
1328/// move.
1329#[derive(Clone)]
1330pub struct SelectionEffects {
1331 nav_history: Option<bool>,
1332 completions: bool,
1333 scroll: Option<Autoscroll>,
1334}
1335
1336impl Default for SelectionEffects {
1337 fn default() -> Self {
1338 Self {
1339 nav_history: None,
1340 completions: true,
1341 scroll: Some(Autoscroll::fit()),
1342 }
1343 }
1344}
1345impl SelectionEffects {
1346 pub fn scroll(scroll: Autoscroll) -> Self {
1347 Self {
1348 scroll: Some(scroll),
1349 ..Default::default()
1350 }
1351 }
1352
1353 pub fn no_scroll() -> Self {
1354 Self {
1355 scroll: None,
1356 ..Default::default()
1357 }
1358 }
1359
1360 pub fn completions(self, completions: bool) -> Self {
1361 Self {
1362 completions,
1363 ..self
1364 }
1365 }
1366
1367 pub fn nav_history(self, nav_history: bool) -> Self {
1368 Self {
1369 nav_history: Some(nav_history),
1370 ..self
1371 }
1372 }
1373}
1374
1375struct DeferredSelectionEffectsState {
1376 changed: bool,
1377 effects: SelectionEffects,
1378 old_cursor_position: Anchor,
1379 history_entry: SelectionHistoryEntry,
1380}
1381
1382#[derive(Default)]
1383struct SelectionHistory {
1384 #[allow(clippy::type_complexity)]
1385 selections_by_transaction:
1386 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1387 mode: SelectionHistoryMode,
1388 undo_stack: VecDeque<SelectionHistoryEntry>,
1389 redo_stack: VecDeque<SelectionHistoryEntry>,
1390}
1391
1392impl SelectionHistory {
1393 #[track_caller]
1394 fn insert_transaction(
1395 &mut self,
1396 transaction_id: TransactionId,
1397 selections: Arc<[Selection<Anchor>]>,
1398 ) {
1399 if selections.is_empty() {
1400 log::error!(
1401 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1402 std::panic::Location::caller()
1403 );
1404 return;
1405 }
1406 self.selections_by_transaction
1407 .insert(transaction_id, (selections, None));
1408 }
1409
1410 #[allow(clippy::type_complexity)]
1411 fn transaction(
1412 &self,
1413 transaction_id: TransactionId,
1414 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1415 self.selections_by_transaction.get(&transaction_id)
1416 }
1417
1418 #[allow(clippy::type_complexity)]
1419 fn transaction_mut(
1420 &mut self,
1421 transaction_id: TransactionId,
1422 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1423 self.selections_by_transaction.get_mut(&transaction_id)
1424 }
1425
1426 fn push(&mut self, entry: SelectionHistoryEntry) {
1427 if !entry.selections.is_empty() {
1428 match self.mode {
1429 SelectionHistoryMode::Normal => {
1430 self.push_undo(entry);
1431 self.redo_stack.clear();
1432 }
1433 SelectionHistoryMode::Undoing => self.push_redo(entry),
1434 SelectionHistoryMode::Redoing => self.push_undo(entry),
1435 SelectionHistoryMode::Skipping => {}
1436 }
1437 }
1438 }
1439
1440 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1441 if self
1442 .undo_stack
1443 .back()
1444 .is_none_or(|e| e.selections != entry.selections)
1445 {
1446 self.undo_stack.push_back(entry);
1447 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1448 self.undo_stack.pop_front();
1449 }
1450 }
1451 }
1452
1453 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1454 if self
1455 .redo_stack
1456 .back()
1457 .is_none_or(|e| e.selections != entry.selections)
1458 {
1459 self.redo_stack.push_back(entry);
1460 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1461 self.redo_stack.pop_front();
1462 }
1463 }
1464 }
1465}
1466
1467#[derive(Clone, Copy)]
1468pub struct RowHighlightOptions {
1469 pub autoscroll: bool,
1470 pub include_gutter: bool,
1471}
1472
1473impl Default for RowHighlightOptions {
1474 fn default() -> Self {
1475 Self {
1476 autoscroll: Default::default(),
1477 include_gutter: true,
1478 }
1479 }
1480}
1481
1482struct RowHighlight {
1483 index: usize,
1484 range: Range<Anchor>,
1485 color: Hsla,
1486 options: RowHighlightOptions,
1487 type_id: TypeId,
1488}
1489
1490#[derive(Clone, Debug)]
1491struct AddSelectionsState {
1492 groups: Vec<AddSelectionsGroup>,
1493}
1494
1495#[derive(Clone, Debug)]
1496struct AddSelectionsGroup {
1497 above: bool,
1498 stack: Vec<usize>,
1499}
1500
1501#[derive(Clone)]
1502struct SelectNextState {
1503 query: AhoCorasick,
1504 wordwise: bool,
1505 done: bool,
1506}
1507
1508impl std::fmt::Debug for SelectNextState {
1509 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1510 f.debug_struct(std::any::type_name::<Self>())
1511 .field("wordwise", &self.wordwise)
1512 .field("done", &self.done)
1513 .finish()
1514 }
1515}
1516
1517#[derive(Debug)]
1518struct AutocloseRegion {
1519 selection_id: usize,
1520 range: Range<Anchor>,
1521 pair: BracketPair,
1522}
1523
1524#[derive(Debug)]
1525struct SnippetState {
1526 ranges: Vec<Vec<Range<Anchor>>>,
1527 active_index: usize,
1528 choices: Vec<Option<Vec<String>>>,
1529}
1530
1531#[doc(hidden)]
1532pub struct RenameState {
1533 pub range: Range<Anchor>,
1534 pub old_name: Arc<str>,
1535 pub editor: Entity<Editor>,
1536 block_id: CustomBlockId,
1537}
1538
1539struct InvalidationStack<T>(Vec<T>);
1540
1541struct RegisteredEditPredictionProvider {
1542 provider: Arc<dyn EditPredictionProviderHandle>,
1543 _subscription: Subscription,
1544}
1545
1546#[derive(Debug, PartialEq, Eq)]
1547pub struct ActiveDiagnosticGroup {
1548 pub active_range: Range<Anchor>,
1549 pub active_message: String,
1550 pub group_id: usize,
1551 pub blocks: HashSet<CustomBlockId>,
1552}
1553
1554#[derive(Debug, PartialEq, Eq)]
1555
1556pub(crate) enum ActiveDiagnostic {
1557 None,
1558 All,
1559 Group(ActiveDiagnosticGroup),
1560}
1561
1562#[derive(Serialize, Deserialize, Clone, Debug)]
1563pub struct ClipboardSelection {
1564 /// The number of bytes in this selection.
1565 pub len: usize,
1566 /// Whether this was a full-line selection.
1567 pub is_entire_line: bool,
1568 /// The indentation of the first line when this content was originally copied.
1569 pub first_line_indent: u32,
1570}
1571
1572// selections, scroll behavior, was newest selection reversed
1573type SelectSyntaxNodeHistoryState = (
1574 Box<[Selection<usize>]>,
1575 SelectSyntaxNodeScrollBehavior,
1576 bool,
1577);
1578
1579#[derive(Default)]
1580struct SelectSyntaxNodeHistory {
1581 stack: Vec<SelectSyntaxNodeHistoryState>,
1582 // disable temporarily to allow changing selections without losing the stack
1583 pub disable_clearing: bool,
1584}
1585
1586impl SelectSyntaxNodeHistory {
1587 pub fn try_clear(&mut self) {
1588 if !self.disable_clearing {
1589 self.stack.clear();
1590 }
1591 }
1592
1593 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1594 self.stack.push(selection);
1595 }
1596
1597 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1598 self.stack.pop()
1599 }
1600}
1601
1602enum SelectSyntaxNodeScrollBehavior {
1603 CursorTop,
1604 FitSelection,
1605 CursorBottom,
1606}
1607
1608#[derive(Debug)]
1609pub(crate) struct NavigationData {
1610 cursor_anchor: Anchor,
1611 cursor_position: Point,
1612 scroll_anchor: ScrollAnchor,
1613 scroll_top_row: u32,
1614}
1615
1616#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1617pub enum GotoDefinitionKind {
1618 Symbol,
1619 Declaration,
1620 Type,
1621 Implementation,
1622}
1623
1624#[derive(Debug, Clone)]
1625enum InlayHintRefreshReason {
1626 ModifiersChanged(bool),
1627 Toggle(bool),
1628 SettingsChange(InlayHintSettings),
1629 NewLinesShown,
1630 BufferEdited(HashSet<Arc<Language>>),
1631 RefreshRequested,
1632 ExcerptsRemoved(Vec<ExcerptId>),
1633}
1634
1635impl InlayHintRefreshReason {
1636 fn description(&self) -> &'static str {
1637 match self {
1638 Self::ModifiersChanged(_) => "modifiers changed",
1639 Self::Toggle(_) => "toggle",
1640 Self::SettingsChange(_) => "settings change",
1641 Self::NewLinesShown => "new lines shown",
1642 Self::BufferEdited(_) => "buffer edited",
1643 Self::RefreshRequested => "refresh requested",
1644 Self::ExcerptsRemoved(_) => "excerpts removed",
1645 }
1646 }
1647}
1648
1649pub enum FormatTarget {
1650 Buffers(HashSet<Entity<Buffer>>),
1651 Ranges(Vec<Range<MultiBufferPoint>>),
1652}
1653
1654pub(crate) struct FocusedBlock {
1655 id: BlockId,
1656 focus_handle: WeakFocusHandle,
1657}
1658
1659#[derive(Clone)]
1660enum JumpData {
1661 MultiBufferRow {
1662 row: MultiBufferRow,
1663 line_offset_from_top: u32,
1664 },
1665 MultiBufferPoint {
1666 excerpt_id: ExcerptId,
1667 position: Point,
1668 anchor: text::Anchor,
1669 line_offset_from_top: u32,
1670 },
1671}
1672
1673pub enum MultibufferSelectionMode {
1674 First,
1675 All,
1676}
1677
1678#[derive(Clone, Copy, Debug, Default)]
1679pub struct RewrapOptions {
1680 pub override_language_settings: bool,
1681 pub preserve_existing_whitespace: bool,
1682}
1683
1684impl Editor {
1685 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1686 let buffer = cx.new(|cx| Buffer::local("", cx));
1687 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1688 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1689 }
1690
1691 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1692 let buffer = cx.new(|cx| Buffer::local("", cx));
1693 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1694 Self::new(EditorMode::full(), buffer, None, window, cx)
1695 }
1696
1697 pub fn auto_height(
1698 min_lines: usize,
1699 max_lines: usize,
1700 window: &mut Window,
1701 cx: &mut Context<Self>,
1702 ) -> Self {
1703 let buffer = cx.new(|cx| Buffer::local("", cx));
1704 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1705 Self::new(
1706 EditorMode::AutoHeight {
1707 min_lines,
1708 max_lines: Some(max_lines),
1709 },
1710 buffer,
1711 None,
1712 window,
1713 cx,
1714 )
1715 }
1716
1717 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1718 /// The editor grows as tall as needed to fit its content.
1719 pub fn auto_height_unbounded(
1720 min_lines: usize,
1721 window: &mut Window,
1722 cx: &mut Context<Self>,
1723 ) -> Self {
1724 let buffer = cx.new(|cx| Buffer::local("", cx));
1725 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1726 Self::new(
1727 EditorMode::AutoHeight {
1728 min_lines,
1729 max_lines: None,
1730 },
1731 buffer,
1732 None,
1733 window,
1734 cx,
1735 )
1736 }
1737
1738 pub fn for_buffer(
1739 buffer: Entity<Buffer>,
1740 project: Option<Entity<Project>>,
1741 window: &mut Window,
1742 cx: &mut Context<Self>,
1743 ) -> Self {
1744 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1745 Self::new(EditorMode::full(), buffer, project, window, cx)
1746 }
1747
1748 pub fn for_multibuffer(
1749 buffer: Entity<MultiBuffer>,
1750 project: Option<Entity<Project>>,
1751 window: &mut Window,
1752 cx: &mut Context<Self>,
1753 ) -> Self {
1754 Self::new(EditorMode::full(), buffer, project, window, cx)
1755 }
1756
1757 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1758 let mut clone = Self::new(
1759 self.mode.clone(),
1760 self.buffer.clone(),
1761 self.project.clone(),
1762 window,
1763 cx,
1764 );
1765 self.display_map.update(cx, |display_map, cx| {
1766 let snapshot = display_map.snapshot(cx);
1767 clone.display_map.update(cx, |display_map, cx| {
1768 display_map.set_state(&snapshot, cx);
1769 });
1770 });
1771 clone.folds_did_change(cx);
1772 clone.selections.clone_state(&self.selections);
1773 clone.scroll_manager.clone_state(&self.scroll_manager);
1774 clone.searchable = self.searchable;
1775 clone.read_only = self.read_only;
1776 clone
1777 }
1778
1779 pub fn new(
1780 mode: EditorMode,
1781 buffer: Entity<MultiBuffer>,
1782 project: Option<Entity<Project>>,
1783 window: &mut Window,
1784 cx: &mut Context<Self>,
1785 ) -> Self {
1786 Editor::new_internal(mode, buffer, project, None, window, cx)
1787 }
1788
1789 fn new_internal(
1790 mode: EditorMode,
1791 multi_buffer: Entity<MultiBuffer>,
1792 project: Option<Entity<Project>>,
1793 display_map: Option<Entity<DisplayMap>>,
1794 window: &mut Window,
1795 cx: &mut Context<Self>,
1796 ) -> Self {
1797 debug_assert!(
1798 display_map.is_none() || mode.is_minimap(),
1799 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1800 );
1801
1802 let full_mode = mode.is_full();
1803 let is_minimap = mode.is_minimap();
1804 let diagnostics_max_severity = if full_mode {
1805 EditorSettings::get_global(cx)
1806 .diagnostics_max_severity
1807 .unwrap_or(DiagnosticSeverity::Hint)
1808 } else {
1809 DiagnosticSeverity::Off
1810 };
1811 let style = window.text_style();
1812 let font_size = style.font_size.to_pixels(window.rem_size());
1813 let editor = cx.entity().downgrade();
1814 let fold_placeholder = FoldPlaceholder {
1815 constrain_width: false,
1816 render: Arc::new(move |fold_id, fold_range, cx| {
1817 let editor = editor.clone();
1818 div()
1819 .id(fold_id)
1820 .bg(cx.theme().colors().ghost_element_background)
1821 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1822 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1823 .rounded_xs()
1824 .size_full()
1825 .cursor_pointer()
1826 .child("⋯")
1827 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1828 .on_click(move |_, _window, cx| {
1829 editor
1830 .update(cx, |editor, cx| {
1831 editor.unfold_ranges(
1832 &[fold_range.start..fold_range.end],
1833 true,
1834 false,
1835 cx,
1836 );
1837 cx.stop_propagation();
1838 })
1839 .ok();
1840 })
1841 .into_any()
1842 }),
1843 merge_adjacent: true,
1844 ..FoldPlaceholder::default()
1845 };
1846 let display_map = display_map.unwrap_or_else(|| {
1847 cx.new(|cx| {
1848 DisplayMap::new(
1849 multi_buffer.clone(),
1850 style.font(),
1851 font_size,
1852 None,
1853 FILE_HEADER_HEIGHT,
1854 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1855 fold_placeholder,
1856 diagnostics_max_severity,
1857 cx,
1858 )
1859 })
1860 });
1861
1862 let selections = SelectionsCollection::new(display_map.clone(), multi_buffer.clone());
1863
1864 let blink_manager = cx.new(|cx| {
1865 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1866 if is_minimap {
1867 blink_manager.disable(cx);
1868 }
1869 blink_manager
1870 });
1871
1872 let soft_wrap_mode_override =
1873 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1874
1875 let mut project_subscriptions = Vec::new();
1876 if full_mode && let Some(project) = project.as_ref() {
1877 project_subscriptions.push(cx.subscribe_in(
1878 project,
1879 window,
1880 |editor, _, event, window, cx| match event {
1881 project::Event::RefreshCodeLens => {
1882 // we always query lens with actions, without storing them, always refreshing them
1883 }
1884 project::Event::RefreshInlayHints => {
1885 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1886 }
1887 project::Event::LanguageServerRemoved(..) => {
1888 if editor.tasks_update_task.is_none() {
1889 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1890 }
1891 editor.registered_buffers.clear();
1892 editor.register_visible_buffers(cx);
1893 }
1894 project::Event::LanguageServerAdded(..) => {
1895 if editor.tasks_update_task.is_none() {
1896 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1897 }
1898 }
1899 project::Event::SnippetEdit(id, snippet_edits) => {
1900 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1901 let focus_handle = editor.focus_handle(cx);
1902 if focus_handle.is_focused(window) {
1903 let snapshot = buffer.read(cx).snapshot();
1904 for (range, snippet) in snippet_edits {
1905 let editor_range =
1906 language::range_from_lsp(*range).to_offset(&snapshot);
1907 editor
1908 .insert_snippet(
1909 &[editor_range],
1910 snippet.clone(),
1911 window,
1912 cx,
1913 )
1914 .ok();
1915 }
1916 }
1917 }
1918 }
1919 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1920 let buffer_id = *buffer_id;
1921 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
1922 let registered = editor.register_buffer(buffer_id, cx);
1923 if registered {
1924 editor.update_lsp_data(Some(buffer_id), window, cx);
1925 editor.refresh_inlay_hints(
1926 InlayHintRefreshReason::RefreshRequested,
1927 cx,
1928 );
1929 refresh_linked_ranges(editor, window, cx);
1930 editor.refresh_code_actions(window, cx);
1931 editor.refresh_document_highlights(cx);
1932 }
1933 }
1934 }
1935
1936 project::Event::EntryRenamed(transaction) => {
1937 let Some(workspace) = editor.workspace() else {
1938 return;
1939 };
1940 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1941 else {
1942 return;
1943 };
1944 if active_editor.entity_id() == cx.entity_id() {
1945 let edited_buffers_already_open = {
1946 let other_editors: Vec<Entity<Editor>> = workspace
1947 .read(cx)
1948 .panes()
1949 .iter()
1950 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1951 .filter(|editor| editor.entity_id() != cx.entity_id())
1952 .collect();
1953
1954 transaction.0.keys().all(|buffer| {
1955 other_editors.iter().any(|editor| {
1956 let multi_buffer = editor.read(cx).buffer();
1957 multi_buffer.read(cx).is_singleton()
1958 && multi_buffer.read(cx).as_singleton().map_or(
1959 false,
1960 |singleton| {
1961 singleton.entity_id() == buffer.entity_id()
1962 },
1963 )
1964 })
1965 })
1966 };
1967
1968 if !edited_buffers_already_open {
1969 let workspace = workspace.downgrade();
1970 let transaction = transaction.clone();
1971 cx.defer_in(window, move |_, window, cx| {
1972 cx.spawn_in(window, async move |editor, cx| {
1973 Self::open_project_transaction(
1974 &editor,
1975 workspace,
1976 transaction,
1977 "Rename".to_string(),
1978 cx,
1979 )
1980 .await
1981 .ok()
1982 })
1983 .detach();
1984 });
1985 }
1986 }
1987 }
1988
1989 _ => {}
1990 },
1991 ));
1992 if let Some(task_inventory) = project
1993 .read(cx)
1994 .task_store()
1995 .read(cx)
1996 .task_inventory()
1997 .cloned()
1998 {
1999 project_subscriptions.push(cx.observe_in(
2000 &task_inventory,
2001 window,
2002 |editor, _, window, cx| {
2003 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2004 },
2005 ));
2006 };
2007
2008 project_subscriptions.push(cx.subscribe_in(
2009 &project.read(cx).breakpoint_store(),
2010 window,
2011 |editor, _, event, window, cx| match event {
2012 BreakpointStoreEvent::ClearDebugLines => {
2013 editor.clear_row_highlights::<ActiveDebugLine>();
2014 editor.refresh_inline_values(cx);
2015 }
2016 BreakpointStoreEvent::SetDebugLine => {
2017 if editor.go_to_active_debug_line(window, cx) {
2018 cx.stop_propagation();
2019 }
2020
2021 editor.refresh_inline_values(cx);
2022 }
2023 _ => {}
2024 },
2025 ));
2026 let git_store = project.read(cx).git_store().clone();
2027 let project = project.clone();
2028 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2029 if let GitStoreEvent::RepositoryUpdated(
2030 _,
2031 RepositoryEvent::Updated {
2032 new_instance: true, ..
2033 },
2034 _,
2035 ) = event
2036 {
2037 this.load_diff_task = Some(
2038 update_uncommitted_diff_for_buffer(
2039 cx.entity(),
2040 &project,
2041 this.buffer.read(cx).all_buffers(),
2042 this.buffer.clone(),
2043 cx,
2044 )
2045 .shared(),
2046 );
2047 }
2048 }));
2049 }
2050
2051 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2052
2053 let inlay_hint_settings =
2054 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2055 let focus_handle = cx.focus_handle();
2056 if !is_minimap {
2057 cx.on_focus(&focus_handle, window, Self::handle_focus)
2058 .detach();
2059 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2060 .detach();
2061 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2062 .detach();
2063 cx.on_blur(&focus_handle, window, Self::handle_blur)
2064 .detach();
2065 cx.observe_pending_input(window, Self::observe_pending_input)
2066 .detach();
2067 }
2068
2069 let show_indent_guides =
2070 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2071 Some(false)
2072 } else {
2073 None
2074 };
2075
2076 let breakpoint_store = match (&mode, project.as_ref()) {
2077 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2078 _ => None,
2079 };
2080
2081 let mut code_action_providers = Vec::new();
2082 let mut load_uncommitted_diff = None;
2083 if let Some(project) = project.clone() {
2084 load_uncommitted_diff = Some(
2085 update_uncommitted_diff_for_buffer(
2086 cx.entity(),
2087 &project,
2088 multi_buffer.read(cx).all_buffers(),
2089 multi_buffer.clone(),
2090 cx,
2091 )
2092 .shared(),
2093 );
2094 code_action_providers.push(Rc::new(project) as Rc<_>);
2095 }
2096
2097 let mut editor = Self {
2098 focus_handle,
2099 show_cursor_when_unfocused: false,
2100 last_focused_descendant: None,
2101 buffer: multi_buffer.clone(),
2102 display_map: display_map.clone(),
2103 placeholder_display_map: None,
2104 selections,
2105 scroll_manager: ScrollManager::new(cx),
2106 columnar_selection_state: None,
2107 add_selections_state: None,
2108 select_next_state: None,
2109 select_prev_state: None,
2110 selection_history: SelectionHistory::default(),
2111 defer_selection_effects: false,
2112 deferred_selection_effects_state: None,
2113 autoclose_regions: Vec::new(),
2114 snippet_stack: InvalidationStack::default(),
2115 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2116 ime_transaction: None,
2117 active_diagnostics: ActiveDiagnostic::None,
2118 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2119 inline_diagnostics_update: Task::ready(()),
2120 inline_diagnostics: Vec::new(),
2121 soft_wrap_mode_override,
2122 diagnostics_max_severity,
2123 hard_wrap: None,
2124 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2125 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2126 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2127 project,
2128 blink_manager: blink_manager.clone(),
2129 show_local_selections: true,
2130 show_scrollbars: ScrollbarAxes {
2131 horizontal: full_mode,
2132 vertical: full_mode,
2133 },
2134 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2135 offset_content: !matches!(mode, EditorMode::SingleLine),
2136 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2137 show_gutter: full_mode,
2138 show_line_numbers: (!full_mode).then_some(false),
2139 use_relative_line_numbers: None,
2140 disable_expand_excerpt_buttons: !full_mode,
2141 show_git_diff_gutter: None,
2142 show_code_actions: None,
2143 show_runnables: None,
2144 show_breakpoints: None,
2145 show_wrap_guides: None,
2146 show_indent_guides,
2147 highlight_order: 0,
2148 highlighted_rows: HashMap::default(),
2149 background_highlights: HashMap::default(),
2150 gutter_highlights: HashMap::default(),
2151 scrollbar_marker_state: ScrollbarMarkerState::default(),
2152 active_indent_guides_state: ActiveIndentGuidesState::default(),
2153 nav_history: None,
2154 context_menu: RefCell::new(None),
2155 context_menu_options: None,
2156 mouse_context_menu: None,
2157 completion_tasks: Vec::new(),
2158 inline_blame_popover: None,
2159 inline_blame_popover_show_task: None,
2160 signature_help_state: SignatureHelpState::default(),
2161 auto_signature_help: None,
2162 find_all_references_task_sources: Vec::new(),
2163 next_completion_id: 0,
2164 next_inlay_id: 0,
2165 code_action_providers,
2166 available_code_actions: None,
2167 code_actions_task: None,
2168 quick_selection_highlight_task: None,
2169 debounced_selection_highlight_task: None,
2170 document_highlights_task: None,
2171 linked_editing_range_task: None,
2172 pending_rename: None,
2173 searchable: !is_minimap,
2174 cursor_shape: EditorSettings::get_global(cx)
2175 .cursor_shape
2176 .unwrap_or_default(),
2177 current_line_highlight: None,
2178 autoindent_mode: Some(AutoindentMode::EachLine),
2179 collapse_matches: false,
2180 workspace: None,
2181 input_enabled: !is_minimap,
2182 use_modal_editing: full_mode,
2183 read_only: is_minimap,
2184 use_autoclose: true,
2185 use_auto_surround: true,
2186 auto_replace_emoji_shortcode: false,
2187 jsx_tag_auto_close_enabled_in_any_buffer: false,
2188 leader_id: None,
2189 remote_id: None,
2190 hover_state: HoverState::default(),
2191 pending_mouse_down: None,
2192 hovered_link_state: None,
2193 edit_prediction_provider: None,
2194 active_edit_prediction: None,
2195 stale_edit_prediction_in_menu: None,
2196 edit_prediction_preview: EditPredictionPreview::Inactive {
2197 released_too_fast: false,
2198 },
2199 inline_diagnostics_enabled: full_mode,
2200 diagnostics_enabled: full_mode,
2201 word_completions_enabled: full_mode,
2202 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2203 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2204 gutter_hovered: false,
2205 pixel_position_of_newest_cursor: None,
2206 last_bounds: None,
2207 last_position_map: None,
2208 expect_bounds_change: None,
2209 gutter_dimensions: GutterDimensions::default(),
2210 style: None,
2211 show_cursor_names: false,
2212 hovered_cursors: HashMap::default(),
2213 next_editor_action_id: EditorActionId::default(),
2214 editor_actions: Rc::default(),
2215 edit_predictions_hidden_for_vim_mode: false,
2216 show_edit_predictions_override: None,
2217 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2218 edit_prediction_settings: EditPredictionSettings::Disabled,
2219 edit_prediction_indent_conflict: false,
2220 edit_prediction_requires_modifier_in_indent_conflict: true,
2221 custom_context_menu: None,
2222 show_git_blame_gutter: false,
2223 show_git_blame_inline: false,
2224 show_selection_menu: None,
2225 show_git_blame_inline_delay_task: None,
2226 git_blame_inline_enabled: full_mode
2227 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2228 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2229 serialize_dirty_buffers: !is_minimap
2230 && ProjectSettings::get_global(cx)
2231 .session
2232 .restore_unsaved_buffers,
2233 blame: None,
2234 blame_subscription: None,
2235 tasks: BTreeMap::default(),
2236
2237 breakpoint_store,
2238 gutter_breakpoint_indicator: (None, None),
2239 hovered_diff_hunk_row: None,
2240 _subscriptions: (!is_minimap)
2241 .then(|| {
2242 vec![
2243 cx.observe(&multi_buffer, Self::on_buffer_changed),
2244 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2245 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2246 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2247 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2248 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2249 cx.observe_window_activation(window, |editor, window, cx| {
2250 let active = window.is_window_active();
2251 editor.blink_manager.update(cx, |blink_manager, cx| {
2252 if active {
2253 blink_manager.enable(cx);
2254 } else {
2255 blink_manager.disable(cx);
2256 }
2257 });
2258 if active {
2259 editor.show_mouse_cursor(cx);
2260 }
2261 }),
2262 ]
2263 })
2264 .unwrap_or_default(),
2265 tasks_update_task: None,
2266 pull_diagnostics_task: Task::ready(()),
2267 colors: None,
2268 refresh_colors_task: Task::ready(()),
2269 next_color_inlay_id: 0,
2270 post_scroll_update: Task::ready(()),
2271 linked_edit_ranges: Default::default(),
2272 in_project_search: false,
2273 previous_search_ranges: None,
2274 breadcrumb_header: None,
2275 focused_block: None,
2276 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2277 addons: HashMap::default(),
2278 registered_buffers: HashMap::default(),
2279 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2280 selection_mark_mode: false,
2281 toggle_fold_multiple_buffers: Task::ready(()),
2282 serialize_selections: Task::ready(()),
2283 serialize_folds: Task::ready(()),
2284 text_style_refinement: None,
2285 load_diff_task: load_uncommitted_diff,
2286 temporary_diff_override: false,
2287 mouse_cursor_hidden: false,
2288 minimap: None,
2289 hide_mouse_mode: EditorSettings::get_global(cx)
2290 .hide_mouse
2291 .unwrap_or_default(),
2292 change_list: ChangeList::new(),
2293 mode,
2294 selection_drag_state: SelectionDragState::None,
2295 folding_newlines: Task::ready(()),
2296 lookup_key: None,
2297 };
2298
2299 if is_minimap {
2300 return editor;
2301 }
2302
2303 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2304 editor
2305 ._subscriptions
2306 .push(cx.observe(breakpoints, |_, _, cx| {
2307 cx.notify();
2308 }));
2309 }
2310 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2311 editor._subscriptions.extend(project_subscriptions);
2312
2313 editor._subscriptions.push(cx.subscribe_in(
2314 &cx.entity(),
2315 window,
2316 |editor, _, e: &EditorEvent, window, cx| match e {
2317 EditorEvent::ScrollPositionChanged { local, .. } => {
2318 if *local {
2319 let new_anchor = editor.scroll_manager.anchor();
2320 let snapshot = editor.snapshot(window, cx);
2321 editor.update_restoration_data(cx, move |data| {
2322 data.scroll_position = (
2323 new_anchor.top_row(snapshot.buffer_snapshot()),
2324 new_anchor.offset,
2325 );
2326 });
2327 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2328 editor.inline_blame_popover.take();
2329 }
2330 }
2331 EditorEvent::Edited { .. } => {
2332 if !vim_enabled(cx) {
2333 let display_map = editor.display_snapshot(cx);
2334 let selections = editor.selections.all_adjusted_display(&display_map);
2335 let pop_state = editor
2336 .change_list
2337 .last()
2338 .map(|previous| {
2339 previous.len() == selections.len()
2340 && previous.iter().enumerate().all(|(ix, p)| {
2341 p.to_display_point(&display_map).row()
2342 == selections[ix].head().row()
2343 })
2344 })
2345 .unwrap_or(false);
2346 let new_positions = selections
2347 .into_iter()
2348 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2349 .collect();
2350 editor
2351 .change_list
2352 .push_to_change_list(pop_state, new_positions);
2353 }
2354 }
2355 _ => (),
2356 },
2357 ));
2358
2359 if let Some(dap_store) = editor
2360 .project
2361 .as_ref()
2362 .map(|project| project.read(cx).dap_store())
2363 {
2364 let weak_editor = cx.weak_entity();
2365
2366 editor
2367 ._subscriptions
2368 .push(
2369 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2370 let session_entity = cx.entity();
2371 weak_editor
2372 .update(cx, |editor, cx| {
2373 editor._subscriptions.push(
2374 cx.subscribe(&session_entity, Self::on_debug_session_event),
2375 );
2376 })
2377 .ok();
2378 }),
2379 );
2380
2381 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2382 editor
2383 ._subscriptions
2384 .push(cx.subscribe(&session, Self::on_debug_session_event));
2385 }
2386 }
2387
2388 // skip adding the initial selection to selection history
2389 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2390 editor.end_selection(window, cx);
2391 editor.selection_history.mode = SelectionHistoryMode::Normal;
2392
2393 editor.scroll_manager.show_scrollbars(window, cx);
2394 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2395
2396 if full_mode {
2397 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2398 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2399
2400 if editor.git_blame_inline_enabled {
2401 editor.start_git_blame_inline(false, window, cx);
2402 }
2403
2404 editor.go_to_active_debug_line(window, cx);
2405
2406 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2407 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2408 }
2409
2410 editor.minimap =
2411 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2412 editor.colors = Some(LspColorData::new(cx));
2413 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2414 }
2415
2416 editor
2417 }
2418
2419 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2420 self.selections.display_map(cx)
2421 }
2422
2423 pub fn deploy_mouse_context_menu(
2424 &mut self,
2425 position: gpui::Point<Pixels>,
2426 context_menu: Entity<ContextMenu>,
2427 window: &mut Window,
2428 cx: &mut Context<Self>,
2429 ) {
2430 self.mouse_context_menu = Some(MouseContextMenu::new(
2431 self,
2432 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2433 context_menu,
2434 window,
2435 cx,
2436 ));
2437 }
2438
2439 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2440 self.mouse_context_menu
2441 .as_ref()
2442 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2443 }
2444
2445 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2446 if self
2447 .selections
2448 .pending_anchor()
2449 .is_some_and(|pending_selection| {
2450 let snapshot = self.buffer().read(cx).snapshot(cx);
2451 pending_selection.range().includes(range, &snapshot)
2452 })
2453 {
2454 return true;
2455 }
2456
2457 self.selections
2458 .disjoint_in_range::<usize>(range.clone(), &self.display_snapshot(cx))
2459 .into_iter()
2460 .any(|selection| {
2461 // This is needed to cover a corner case, if we just check for an existing
2462 // selection in the fold range, having a cursor at the start of the fold
2463 // marks it as selected. Non-empty selections don't cause this.
2464 let length = selection.end - selection.start;
2465 length > 0
2466 })
2467 }
2468
2469 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2470 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2471 }
2472
2473 fn key_context_internal(
2474 &self,
2475 has_active_edit_prediction: bool,
2476 window: &Window,
2477 cx: &App,
2478 ) -> KeyContext {
2479 let mut key_context = KeyContext::new_with_defaults();
2480 key_context.add("Editor");
2481 let mode = match self.mode {
2482 EditorMode::SingleLine => "single_line",
2483 EditorMode::AutoHeight { .. } => "auto_height",
2484 EditorMode::Minimap { .. } => "minimap",
2485 EditorMode::Full { .. } => "full",
2486 };
2487
2488 if EditorSettings::jupyter_enabled(cx) {
2489 key_context.add("jupyter");
2490 }
2491
2492 key_context.set("mode", mode);
2493 if self.pending_rename.is_some() {
2494 key_context.add("renaming");
2495 }
2496
2497 match self.context_menu.borrow().as_ref() {
2498 Some(CodeContextMenu::Completions(menu)) => {
2499 if menu.visible() {
2500 key_context.add("menu");
2501 key_context.add("showing_completions");
2502 }
2503 }
2504 Some(CodeContextMenu::CodeActions(menu)) => {
2505 if menu.visible() {
2506 key_context.add("menu");
2507 key_context.add("showing_code_actions")
2508 }
2509 }
2510 None => {}
2511 }
2512
2513 if self.signature_help_state.has_multiple_signatures() {
2514 key_context.add("showing_signature_help");
2515 }
2516
2517 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2518 if !self.focus_handle(cx).contains_focused(window, cx)
2519 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2520 {
2521 for addon in self.addons.values() {
2522 addon.extend_key_context(&mut key_context, cx)
2523 }
2524 }
2525
2526 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2527 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2528 Some(
2529 file.full_path(cx)
2530 .extension()?
2531 .to_string_lossy()
2532 .into_owned(),
2533 )
2534 }) {
2535 key_context.set("extension", extension);
2536 }
2537 } else {
2538 key_context.add("multibuffer");
2539 }
2540
2541 if has_active_edit_prediction {
2542 if self.edit_prediction_in_conflict() {
2543 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2544 } else {
2545 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2546 key_context.add("copilot_suggestion");
2547 }
2548 }
2549
2550 if self.selection_mark_mode {
2551 key_context.add("selection_mode");
2552 }
2553
2554 key_context
2555 }
2556
2557 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2558 self.last_bounds.as_ref()
2559 }
2560
2561 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2562 if self.mouse_cursor_hidden {
2563 self.mouse_cursor_hidden = false;
2564 cx.notify();
2565 }
2566 }
2567
2568 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2569 let hide_mouse_cursor = match origin {
2570 HideMouseCursorOrigin::TypingAction => {
2571 matches!(
2572 self.hide_mouse_mode,
2573 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2574 )
2575 }
2576 HideMouseCursorOrigin::MovementAction => {
2577 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2578 }
2579 };
2580 if self.mouse_cursor_hidden != hide_mouse_cursor {
2581 self.mouse_cursor_hidden = hide_mouse_cursor;
2582 cx.notify();
2583 }
2584 }
2585
2586 pub fn edit_prediction_in_conflict(&self) -> bool {
2587 if !self.show_edit_predictions_in_menu() {
2588 return false;
2589 }
2590
2591 let showing_completions = self
2592 .context_menu
2593 .borrow()
2594 .as_ref()
2595 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2596
2597 showing_completions
2598 || self.edit_prediction_requires_modifier()
2599 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2600 // bindings to insert tab characters.
2601 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2602 }
2603
2604 pub fn accept_edit_prediction_keybind(
2605 &self,
2606 accept_partial: bool,
2607 window: &Window,
2608 cx: &App,
2609 ) -> AcceptEditPredictionBinding {
2610 let key_context = self.key_context_internal(true, window, cx);
2611 let in_conflict = self.edit_prediction_in_conflict();
2612
2613 let bindings = if accept_partial {
2614 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2615 } else {
2616 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2617 };
2618
2619 // TODO: if the binding contains multiple keystrokes, display all of them, not
2620 // just the first one.
2621 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2622 !in_conflict
2623 || binding
2624 .keystrokes()
2625 .first()
2626 .is_some_and(|keystroke| keystroke.modifiers().modified())
2627 }))
2628 }
2629
2630 pub fn new_file(
2631 workspace: &mut Workspace,
2632 _: &workspace::NewFile,
2633 window: &mut Window,
2634 cx: &mut Context<Workspace>,
2635 ) {
2636 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2637 "Failed to create buffer",
2638 window,
2639 cx,
2640 |e, _, _| match e.error_code() {
2641 ErrorCode::RemoteUpgradeRequired => Some(format!(
2642 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2643 e.error_tag("required").unwrap_or("the latest version")
2644 )),
2645 _ => None,
2646 },
2647 );
2648 }
2649
2650 pub fn new_in_workspace(
2651 workspace: &mut Workspace,
2652 window: &mut Window,
2653 cx: &mut Context<Workspace>,
2654 ) -> Task<Result<Entity<Editor>>> {
2655 let project = workspace.project().clone();
2656 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2657
2658 cx.spawn_in(window, async move |workspace, cx| {
2659 let buffer = create.await?;
2660 workspace.update_in(cx, |workspace, window, cx| {
2661 let editor =
2662 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2663 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2664 editor
2665 })
2666 })
2667 }
2668
2669 fn new_file_vertical(
2670 workspace: &mut Workspace,
2671 _: &workspace::NewFileSplitVertical,
2672 window: &mut Window,
2673 cx: &mut Context<Workspace>,
2674 ) {
2675 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2676 }
2677
2678 fn new_file_horizontal(
2679 workspace: &mut Workspace,
2680 _: &workspace::NewFileSplitHorizontal,
2681 window: &mut Window,
2682 cx: &mut Context<Workspace>,
2683 ) {
2684 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2685 }
2686
2687 fn new_file_split(
2688 workspace: &mut Workspace,
2689 action: &workspace::NewFileSplit,
2690 window: &mut Window,
2691 cx: &mut Context<Workspace>,
2692 ) {
2693 Self::new_file_in_direction(workspace, action.0, window, cx)
2694 }
2695
2696 fn new_file_in_direction(
2697 workspace: &mut Workspace,
2698 direction: SplitDirection,
2699 window: &mut Window,
2700 cx: &mut Context<Workspace>,
2701 ) {
2702 let project = workspace.project().clone();
2703 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2704
2705 cx.spawn_in(window, async move |workspace, cx| {
2706 let buffer = create.await?;
2707 workspace.update_in(cx, move |workspace, window, cx| {
2708 workspace.split_item(
2709 direction,
2710 Box::new(
2711 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2712 ),
2713 window,
2714 cx,
2715 )
2716 })?;
2717 anyhow::Ok(())
2718 })
2719 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2720 match e.error_code() {
2721 ErrorCode::RemoteUpgradeRequired => Some(format!(
2722 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2723 e.error_tag("required").unwrap_or("the latest version")
2724 )),
2725 _ => None,
2726 }
2727 });
2728 }
2729
2730 pub fn leader_id(&self) -> Option<CollaboratorId> {
2731 self.leader_id
2732 }
2733
2734 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2735 &self.buffer
2736 }
2737
2738 pub fn project(&self) -> Option<&Entity<Project>> {
2739 self.project.as_ref()
2740 }
2741
2742 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2743 self.workspace.as_ref()?.0.upgrade()
2744 }
2745
2746 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2747 self.buffer().read(cx).title(cx)
2748 }
2749
2750 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2751 let git_blame_gutter_max_author_length = self
2752 .render_git_blame_gutter(cx)
2753 .then(|| {
2754 if let Some(blame) = self.blame.as_ref() {
2755 let max_author_length =
2756 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2757 Some(max_author_length)
2758 } else {
2759 None
2760 }
2761 })
2762 .flatten();
2763
2764 EditorSnapshot {
2765 mode: self.mode.clone(),
2766 show_gutter: self.show_gutter,
2767 show_line_numbers: self.show_line_numbers,
2768 show_git_diff_gutter: self.show_git_diff_gutter,
2769 show_code_actions: self.show_code_actions,
2770 show_runnables: self.show_runnables,
2771 show_breakpoints: self.show_breakpoints,
2772 git_blame_gutter_max_author_length,
2773 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2774 placeholder_display_snapshot: self
2775 .placeholder_display_map
2776 .as_ref()
2777 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2778 scroll_anchor: self.scroll_manager.anchor(),
2779 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2780 is_focused: self.focus_handle.is_focused(window),
2781 current_line_highlight: self
2782 .current_line_highlight
2783 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2784 gutter_hovered: self.gutter_hovered,
2785 }
2786 }
2787
2788 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2789 self.buffer.read(cx).language_at(point, cx)
2790 }
2791
2792 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2793 self.buffer.read(cx).read(cx).file_at(point).cloned()
2794 }
2795
2796 pub fn active_excerpt(
2797 &self,
2798 cx: &App,
2799 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2800 self.buffer
2801 .read(cx)
2802 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2803 }
2804
2805 pub fn mode(&self) -> &EditorMode {
2806 &self.mode
2807 }
2808
2809 pub fn set_mode(&mut self, mode: EditorMode) {
2810 self.mode = mode;
2811 }
2812
2813 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2814 self.collaboration_hub.as_deref()
2815 }
2816
2817 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2818 self.collaboration_hub = Some(hub);
2819 }
2820
2821 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2822 self.in_project_search = in_project_search;
2823 }
2824
2825 pub fn set_custom_context_menu(
2826 &mut self,
2827 f: impl 'static
2828 + Fn(
2829 &mut Self,
2830 DisplayPoint,
2831 &mut Window,
2832 &mut Context<Self>,
2833 ) -> Option<Entity<ui::ContextMenu>>,
2834 ) {
2835 self.custom_context_menu = Some(Box::new(f))
2836 }
2837
2838 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2839 self.completion_provider = provider;
2840 }
2841
2842 #[cfg(any(test, feature = "test-support"))]
2843 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2844 self.completion_provider.clone()
2845 }
2846
2847 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2848 self.semantics_provider.clone()
2849 }
2850
2851 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2852 self.semantics_provider = provider;
2853 }
2854
2855 pub fn set_edit_prediction_provider<T>(
2856 &mut self,
2857 provider: Option<Entity<T>>,
2858 window: &mut Window,
2859 cx: &mut Context<Self>,
2860 ) where
2861 T: EditPredictionProvider,
2862 {
2863 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2864 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2865 if this.focus_handle.is_focused(window) {
2866 this.update_visible_edit_prediction(window, cx);
2867 }
2868 }),
2869 provider: Arc::new(provider),
2870 });
2871 self.update_edit_prediction_settings(cx);
2872 self.refresh_edit_prediction(false, false, window, cx);
2873 }
2874
2875 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2876 self.placeholder_display_map
2877 .as_ref()
2878 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2879 }
2880
2881 pub fn set_placeholder_text(
2882 &mut self,
2883 placeholder_text: &str,
2884 window: &mut Window,
2885 cx: &mut Context<Self>,
2886 ) {
2887 let multibuffer = cx
2888 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2889
2890 let style = window.text_style();
2891
2892 self.placeholder_display_map = Some(cx.new(|cx| {
2893 DisplayMap::new(
2894 multibuffer,
2895 style.font(),
2896 style.font_size.to_pixels(window.rem_size()),
2897 None,
2898 FILE_HEADER_HEIGHT,
2899 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2900 Default::default(),
2901 DiagnosticSeverity::Off,
2902 cx,
2903 )
2904 }));
2905 cx.notify();
2906 }
2907
2908 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2909 self.cursor_shape = cursor_shape;
2910
2911 // Disrupt blink for immediate user feedback that the cursor shape has changed
2912 self.blink_manager.update(cx, BlinkManager::show_cursor);
2913
2914 cx.notify();
2915 }
2916
2917 pub fn set_current_line_highlight(
2918 &mut self,
2919 current_line_highlight: Option<CurrentLineHighlight>,
2920 ) {
2921 self.current_line_highlight = current_line_highlight;
2922 }
2923
2924 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2925 self.collapse_matches = collapse_matches;
2926 }
2927
2928 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2929 if self.collapse_matches {
2930 return range.start..range.start;
2931 }
2932 range.clone()
2933 }
2934
2935 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2936 if self.display_map.read(cx).clip_at_line_ends != clip {
2937 self.display_map
2938 .update(cx, |map, _| map.clip_at_line_ends = clip);
2939 }
2940 }
2941
2942 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2943 self.input_enabled = input_enabled;
2944 }
2945
2946 pub fn set_edit_predictions_hidden_for_vim_mode(
2947 &mut self,
2948 hidden: bool,
2949 window: &mut Window,
2950 cx: &mut Context<Self>,
2951 ) {
2952 if hidden != self.edit_predictions_hidden_for_vim_mode {
2953 self.edit_predictions_hidden_for_vim_mode = hidden;
2954 if hidden {
2955 self.update_visible_edit_prediction(window, cx);
2956 } else {
2957 self.refresh_edit_prediction(true, false, window, cx);
2958 }
2959 }
2960 }
2961
2962 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2963 self.menu_edit_predictions_policy = value;
2964 }
2965
2966 pub fn set_autoindent(&mut self, autoindent: bool) {
2967 if autoindent {
2968 self.autoindent_mode = Some(AutoindentMode::EachLine);
2969 } else {
2970 self.autoindent_mode = None;
2971 }
2972 }
2973
2974 pub fn read_only(&self, cx: &App) -> bool {
2975 self.read_only || self.buffer.read(cx).read_only()
2976 }
2977
2978 pub fn set_read_only(&mut self, read_only: bool) {
2979 self.read_only = read_only;
2980 }
2981
2982 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2983 self.use_autoclose = autoclose;
2984 }
2985
2986 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2987 self.use_auto_surround = auto_surround;
2988 }
2989
2990 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2991 self.auto_replace_emoji_shortcode = auto_replace;
2992 }
2993
2994 pub fn toggle_edit_predictions(
2995 &mut self,
2996 _: &ToggleEditPrediction,
2997 window: &mut Window,
2998 cx: &mut Context<Self>,
2999 ) {
3000 if self.show_edit_predictions_override.is_some() {
3001 self.set_show_edit_predictions(None, window, cx);
3002 } else {
3003 let show_edit_predictions = !self.edit_predictions_enabled();
3004 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3005 }
3006 }
3007
3008 pub fn set_show_edit_predictions(
3009 &mut self,
3010 show_edit_predictions: Option<bool>,
3011 window: &mut Window,
3012 cx: &mut Context<Self>,
3013 ) {
3014 self.show_edit_predictions_override = show_edit_predictions;
3015 self.update_edit_prediction_settings(cx);
3016
3017 if let Some(false) = show_edit_predictions {
3018 self.discard_edit_prediction(false, cx);
3019 } else {
3020 self.refresh_edit_prediction(false, true, window, cx);
3021 }
3022 }
3023
3024 fn edit_predictions_disabled_in_scope(
3025 &self,
3026 buffer: &Entity<Buffer>,
3027 buffer_position: language::Anchor,
3028 cx: &App,
3029 ) -> bool {
3030 let snapshot = buffer.read(cx).snapshot();
3031 let settings = snapshot.settings_at(buffer_position, cx);
3032
3033 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3034 return false;
3035 };
3036
3037 scope.override_name().is_some_and(|scope_name| {
3038 settings
3039 .edit_predictions_disabled_in
3040 .iter()
3041 .any(|s| s == scope_name)
3042 })
3043 }
3044
3045 pub fn set_use_modal_editing(&mut self, to: bool) {
3046 self.use_modal_editing = to;
3047 }
3048
3049 pub fn use_modal_editing(&self) -> bool {
3050 self.use_modal_editing
3051 }
3052
3053 fn selections_did_change(
3054 &mut self,
3055 local: bool,
3056 old_cursor_position: &Anchor,
3057 effects: SelectionEffects,
3058 window: &mut Window,
3059 cx: &mut Context<Self>,
3060 ) {
3061 window.invalidate_character_coordinates();
3062
3063 // Copy selections to primary selection buffer
3064 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3065 if local {
3066 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3067 let buffer_handle = self.buffer.read(cx).read(cx);
3068
3069 let mut text = String::new();
3070 for (index, selection) in selections.iter().enumerate() {
3071 let text_for_selection = buffer_handle
3072 .text_for_range(selection.start..selection.end)
3073 .collect::<String>();
3074
3075 text.push_str(&text_for_selection);
3076 if index != selections.len() - 1 {
3077 text.push('\n');
3078 }
3079 }
3080
3081 if !text.is_empty() {
3082 cx.write_to_primary(ClipboardItem::new_string(text));
3083 }
3084 }
3085
3086 let selection_anchors = self.selections.disjoint_anchors_arc();
3087
3088 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3089 self.buffer.update(cx, |buffer, cx| {
3090 buffer.set_active_selections(
3091 &selection_anchors,
3092 self.selections.line_mode(),
3093 self.cursor_shape,
3094 cx,
3095 )
3096 });
3097 }
3098 let display_map = self
3099 .display_map
3100 .update(cx, |display_map, cx| display_map.snapshot(cx));
3101 let buffer = display_map.buffer_snapshot();
3102 if self.selections.count() == 1 {
3103 self.add_selections_state = None;
3104 }
3105 self.select_next_state = None;
3106 self.select_prev_state = None;
3107 self.select_syntax_node_history.try_clear();
3108 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3109 self.snippet_stack.invalidate(&selection_anchors, buffer);
3110 self.take_rename(false, window, cx);
3111
3112 let newest_selection = self.selections.newest_anchor();
3113 let new_cursor_position = newest_selection.head();
3114 let selection_start = newest_selection.start;
3115
3116 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3117 self.push_to_nav_history(
3118 *old_cursor_position,
3119 Some(new_cursor_position.to_point(buffer)),
3120 false,
3121 effects.nav_history == Some(true),
3122 cx,
3123 );
3124 }
3125
3126 if local {
3127 if let Some(buffer_id) = new_cursor_position.buffer_id {
3128 self.register_buffer(buffer_id, cx);
3129 }
3130
3131 let mut context_menu = self.context_menu.borrow_mut();
3132 let completion_menu = match context_menu.as_ref() {
3133 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3134 Some(CodeContextMenu::CodeActions(_)) => {
3135 *context_menu = None;
3136 None
3137 }
3138 None => None,
3139 };
3140 let completion_position = completion_menu.map(|menu| menu.initial_position);
3141 drop(context_menu);
3142
3143 if effects.completions
3144 && let Some(completion_position) = completion_position
3145 {
3146 let start_offset = selection_start.to_offset(buffer);
3147 let position_matches = start_offset == completion_position.to_offset(buffer);
3148 let continue_showing = if position_matches {
3149 if self.snippet_stack.is_empty() {
3150 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3151 == Some(CharKind::Word)
3152 } else {
3153 // Snippet choices can be shown even when the cursor is in whitespace.
3154 // Dismissing the menu with actions like backspace is handled by
3155 // invalidation regions.
3156 true
3157 }
3158 } else {
3159 false
3160 };
3161
3162 if continue_showing {
3163 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3164 } else {
3165 self.hide_context_menu(window, cx);
3166 }
3167 }
3168
3169 hide_hover(self, cx);
3170
3171 if old_cursor_position.to_display_point(&display_map).row()
3172 != new_cursor_position.to_display_point(&display_map).row()
3173 {
3174 self.available_code_actions.take();
3175 }
3176 self.refresh_code_actions(window, cx);
3177 self.refresh_document_highlights(cx);
3178 refresh_linked_ranges(self, window, cx);
3179
3180 self.refresh_selected_text_highlights(false, window, cx);
3181 refresh_matching_bracket_highlights(self, cx);
3182 self.update_visible_edit_prediction(window, cx);
3183 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3184 self.inline_blame_popover.take();
3185 if self.git_blame_inline_enabled {
3186 self.start_inline_blame_timer(window, cx);
3187 }
3188 }
3189
3190 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3191 cx.emit(EditorEvent::SelectionsChanged { local });
3192
3193 let selections = &self.selections.disjoint_anchors_arc();
3194 if selections.len() == 1 {
3195 cx.emit(SearchEvent::ActiveMatchChanged)
3196 }
3197 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3198 let inmemory_selections = selections
3199 .iter()
3200 .map(|s| {
3201 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3202 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3203 })
3204 .collect();
3205 self.update_restoration_data(cx, |data| {
3206 data.selections = inmemory_selections;
3207 });
3208
3209 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3210 && let Some(workspace_id) =
3211 self.workspace.as_ref().and_then(|workspace| workspace.1)
3212 {
3213 let snapshot = self.buffer().read(cx).snapshot(cx);
3214 let selections = selections.clone();
3215 let background_executor = cx.background_executor().clone();
3216 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3217 self.serialize_selections = cx.background_spawn(async move {
3218 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3219 let db_selections = selections
3220 .iter()
3221 .map(|selection| {
3222 (
3223 selection.start.to_offset(&snapshot),
3224 selection.end.to_offset(&snapshot),
3225 )
3226 })
3227 .collect();
3228
3229 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3230 .await
3231 .with_context(|| {
3232 format!(
3233 "persisting editor selections for editor {editor_id}, \
3234 workspace {workspace_id:?}"
3235 )
3236 })
3237 .log_err();
3238 });
3239 }
3240 }
3241
3242 cx.notify();
3243 }
3244
3245 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3246 use text::ToOffset as _;
3247 use text::ToPoint as _;
3248
3249 if self.mode.is_minimap()
3250 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3251 {
3252 return;
3253 }
3254
3255 if !self.buffer().read(cx).is_singleton() {
3256 return;
3257 }
3258
3259 let display_snapshot = self
3260 .display_map
3261 .update(cx, |display_map, cx| display_map.snapshot(cx));
3262 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3263 return;
3264 };
3265 let inmemory_folds = display_snapshot
3266 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3267 .map(|fold| {
3268 fold.range.start.text_anchor.to_point(&snapshot)
3269 ..fold.range.end.text_anchor.to_point(&snapshot)
3270 })
3271 .collect();
3272 self.update_restoration_data(cx, |data| {
3273 data.folds = inmemory_folds;
3274 });
3275
3276 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3277 return;
3278 };
3279 let background_executor = cx.background_executor().clone();
3280 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3281 let db_folds = display_snapshot
3282 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3283 .map(|fold| {
3284 (
3285 fold.range.start.text_anchor.to_offset(&snapshot),
3286 fold.range.end.text_anchor.to_offset(&snapshot),
3287 )
3288 })
3289 .collect();
3290 self.serialize_folds = cx.background_spawn(async move {
3291 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3292 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3293 .await
3294 .with_context(|| {
3295 format!(
3296 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3297 )
3298 })
3299 .log_err();
3300 });
3301 }
3302
3303 pub fn sync_selections(
3304 &mut self,
3305 other: Entity<Editor>,
3306 cx: &mut Context<Self>,
3307 ) -> gpui::Subscription {
3308 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3309 if !other_selections.is_empty() {
3310 self.selections.change_with(cx, |selections| {
3311 selections.select_anchors(other_selections);
3312 });
3313 }
3314
3315 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3316 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3317 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3318 if other_selections.is_empty() {
3319 return;
3320 }
3321 this.selections.change_with(cx, |selections| {
3322 selections.select_anchors(other_selections);
3323 });
3324 }
3325 });
3326
3327 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3328 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3329 let these_selections = this.selections.disjoint_anchors().to_vec();
3330 if these_selections.is_empty() {
3331 return;
3332 }
3333 other.update(cx, |other_editor, cx| {
3334 other_editor.selections.change_with(cx, |selections| {
3335 selections.select_anchors(these_selections);
3336 })
3337 });
3338 }
3339 });
3340
3341 Subscription::join(other_subscription, this_subscription)
3342 }
3343
3344 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3345 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3346 /// effects of selection change occur at the end of the transaction.
3347 pub fn change_selections<R>(
3348 &mut self,
3349 effects: SelectionEffects,
3350 window: &mut Window,
3351 cx: &mut Context<Self>,
3352 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3353 ) -> R {
3354 if let Some(state) = &mut self.deferred_selection_effects_state {
3355 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3356 state.effects.completions = effects.completions;
3357 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3358 let (changed, result) = self.selections.change_with(cx, change);
3359 state.changed |= changed;
3360 return result;
3361 }
3362 let mut state = DeferredSelectionEffectsState {
3363 changed: false,
3364 effects,
3365 old_cursor_position: self.selections.newest_anchor().head(),
3366 history_entry: SelectionHistoryEntry {
3367 selections: self.selections.disjoint_anchors_arc(),
3368 select_next_state: self.select_next_state.clone(),
3369 select_prev_state: self.select_prev_state.clone(),
3370 add_selections_state: self.add_selections_state.clone(),
3371 },
3372 };
3373 let (changed, result) = self.selections.change_with(cx, change);
3374 state.changed = state.changed || changed;
3375 if self.defer_selection_effects {
3376 self.deferred_selection_effects_state = Some(state);
3377 } else {
3378 self.apply_selection_effects(state, window, cx);
3379 }
3380 result
3381 }
3382
3383 /// Defers the effects of selection change, so that the effects of multiple calls to
3384 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3385 /// to selection history and the state of popovers based on selection position aren't
3386 /// erroneously updated.
3387 pub fn with_selection_effects_deferred<R>(
3388 &mut self,
3389 window: &mut Window,
3390 cx: &mut Context<Self>,
3391 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3392 ) -> R {
3393 let already_deferred = self.defer_selection_effects;
3394 self.defer_selection_effects = true;
3395 let result = update(self, window, cx);
3396 if !already_deferred {
3397 self.defer_selection_effects = false;
3398 if let Some(state) = self.deferred_selection_effects_state.take() {
3399 self.apply_selection_effects(state, window, cx);
3400 }
3401 }
3402 result
3403 }
3404
3405 fn apply_selection_effects(
3406 &mut self,
3407 state: DeferredSelectionEffectsState,
3408 window: &mut Window,
3409 cx: &mut Context<Self>,
3410 ) {
3411 if state.changed {
3412 self.selection_history.push(state.history_entry);
3413
3414 if let Some(autoscroll) = state.effects.scroll {
3415 self.request_autoscroll(autoscroll, cx);
3416 }
3417
3418 let old_cursor_position = &state.old_cursor_position;
3419
3420 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3421
3422 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3423 self.show_signature_help(&ShowSignatureHelp, window, cx);
3424 }
3425 }
3426 }
3427
3428 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3429 where
3430 I: IntoIterator<Item = (Range<S>, T)>,
3431 S: ToOffset,
3432 T: Into<Arc<str>>,
3433 {
3434 if self.read_only(cx) {
3435 return;
3436 }
3437
3438 self.buffer
3439 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3440 }
3441
3442 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3443 where
3444 I: IntoIterator<Item = (Range<S>, T)>,
3445 S: ToOffset,
3446 T: Into<Arc<str>>,
3447 {
3448 if self.read_only(cx) {
3449 return;
3450 }
3451
3452 self.buffer.update(cx, |buffer, cx| {
3453 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3454 });
3455 }
3456
3457 pub fn edit_with_block_indent<I, S, T>(
3458 &mut self,
3459 edits: I,
3460 original_indent_columns: Vec<Option<u32>>,
3461 cx: &mut Context<Self>,
3462 ) where
3463 I: IntoIterator<Item = (Range<S>, T)>,
3464 S: ToOffset,
3465 T: Into<Arc<str>>,
3466 {
3467 if self.read_only(cx) {
3468 return;
3469 }
3470
3471 self.buffer.update(cx, |buffer, cx| {
3472 buffer.edit(
3473 edits,
3474 Some(AutoindentMode::Block {
3475 original_indent_columns,
3476 }),
3477 cx,
3478 )
3479 });
3480 }
3481
3482 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3483 self.hide_context_menu(window, cx);
3484
3485 match phase {
3486 SelectPhase::Begin {
3487 position,
3488 add,
3489 click_count,
3490 } => self.begin_selection(position, add, click_count, window, cx),
3491 SelectPhase::BeginColumnar {
3492 position,
3493 goal_column,
3494 reset,
3495 mode,
3496 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3497 SelectPhase::Extend {
3498 position,
3499 click_count,
3500 } => self.extend_selection(position, click_count, window, cx),
3501 SelectPhase::Update {
3502 position,
3503 goal_column,
3504 scroll_delta,
3505 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3506 SelectPhase::End => self.end_selection(window, cx),
3507 }
3508 }
3509
3510 fn extend_selection(
3511 &mut self,
3512 position: DisplayPoint,
3513 click_count: usize,
3514 window: &mut Window,
3515 cx: &mut Context<Self>,
3516 ) {
3517 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3518 let tail = self.selections.newest::<usize>(&display_map).tail();
3519 let click_count = click_count.max(match self.selections.select_mode() {
3520 SelectMode::Character => 1,
3521 SelectMode::Word(_) => 2,
3522 SelectMode::Line(_) => 3,
3523 SelectMode::All => 4,
3524 });
3525 self.begin_selection(position, false, click_count, window, cx);
3526
3527 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3528
3529 let current_selection = match self.selections.select_mode() {
3530 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3531 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3532 };
3533
3534 let mut pending_selection = self
3535 .selections
3536 .pending_anchor()
3537 .cloned()
3538 .expect("extend_selection not called with pending selection");
3539
3540 if pending_selection
3541 .start
3542 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3543 == Ordering::Greater
3544 {
3545 pending_selection.start = current_selection.start;
3546 }
3547 if pending_selection
3548 .end
3549 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3550 == Ordering::Less
3551 {
3552 pending_selection.end = current_selection.end;
3553 pending_selection.reversed = true;
3554 }
3555
3556 let mut pending_mode = self.selections.pending_mode().unwrap();
3557 match &mut pending_mode {
3558 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3559 _ => {}
3560 }
3561
3562 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3563 SelectionEffects::scroll(Autoscroll::fit())
3564 } else {
3565 SelectionEffects::no_scroll()
3566 };
3567
3568 self.change_selections(effects, window, cx, |s| {
3569 s.set_pending(pending_selection.clone(), pending_mode);
3570 s.set_is_extending(true);
3571 });
3572 }
3573
3574 fn begin_selection(
3575 &mut self,
3576 position: DisplayPoint,
3577 add: bool,
3578 click_count: usize,
3579 window: &mut Window,
3580 cx: &mut Context<Self>,
3581 ) {
3582 if !self.focus_handle.is_focused(window) {
3583 self.last_focused_descendant = None;
3584 window.focus(&self.focus_handle);
3585 }
3586
3587 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3588 let buffer = display_map.buffer_snapshot();
3589 let position = display_map.clip_point(position, Bias::Left);
3590
3591 let start;
3592 let end;
3593 let mode;
3594 let mut auto_scroll;
3595 match click_count {
3596 1 => {
3597 start = buffer.anchor_before(position.to_point(&display_map));
3598 end = start;
3599 mode = SelectMode::Character;
3600 auto_scroll = true;
3601 }
3602 2 => {
3603 let position = display_map
3604 .clip_point(position, Bias::Left)
3605 .to_offset(&display_map, Bias::Left);
3606 let (range, _) = buffer.surrounding_word(position, None);
3607 start = buffer.anchor_before(range.start);
3608 end = buffer.anchor_before(range.end);
3609 mode = SelectMode::Word(start..end);
3610 auto_scroll = true;
3611 }
3612 3 => {
3613 let position = display_map
3614 .clip_point(position, Bias::Left)
3615 .to_point(&display_map);
3616 let line_start = display_map.prev_line_boundary(position).0;
3617 let next_line_start = buffer.clip_point(
3618 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3619 Bias::Left,
3620 );
3621 start = buffer.anchor_before(line_start);
3622 end = buffer.anchor_before(next_line_start);
3623 mode = SelectMode::Line(start..end);
3624 auto_scroll = true;
3625 }
3626 _ => {
3627 start = buffer.anchor_before(0);
3628 end = buffer.anchor_before(buffer.len());
3629 mode = SelectMode::All;
3630 auto_scroll = false;
3631 }
3632 }
3633 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3634
3635 let point_to_delete: Option<usize> = {
3636 let selected_points: Vec<Selection<Point>> =
3637 self.selections.disjoint_in_range(start..end, &display_map);
3638
3639 if !add || click_count > 1 {
3640 None
3641 } else if !selected_points.is_empty() {
3642 Some(selected_points[0].id)
3643 } else {
3644 let clicked_point_already_selected =
3645 self.selections.disjoint_anchors().iter().find(|selection| {
3646 selection.start.to_point(buffer) == start.to_point(buffer)
3647 || selection.end.to_point(buffer) == end.to_point(buffer)
3648 });
3649
3650 clicked_point_already_selected.map(|selection| selection.id)
3651 }
3652 };
3653
3654 let selections_count = self.selections.count();
3655 let effects = if auto_scroll {
3656 SelectionEffects::default()
3657 } else {
3658 SelectionEffects::no_scroll()
3659 };
3660
3661 self.change_selections(effects, window, cx, |s| {
3662 if let Some(point_to_delete) = point_to_delete {
3663 s.delete(point_to_delete);
3664
3665 if selections_count == 1 {
3666 s.set_pending_anchor_range(start..end, mode);
3667 }
3668 } else {
3669 if !add {
3670 s.clear_disjoint();
3671 }
3672
3673 s.set_pending_anchor_range(start..end, mode);
3674 }
3675 });
3676 }
3677
3678 fn begin_columnar_selection(
3679 &mut self,
3680 position: DisplayPoint,
3681 goal_column: u32,
3682 reset: bool,
3683 mode: ColumnarMode,
3684 window: &mut Window,
3685 cx: &mut Context<Self>,
3686 ) {
3687 if !self.focus_handle.is_focused(window) {
3688 self.last_focused_descendant = None;
3689 window.focus(&self.focus_handle);
3690 }
3691
3692 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3693
3694 if reset {
3695 let pointer_position = display_map
3696 .buffer_snapshot()
3697 .anchor_before(position.to_point(&display_map));
3698
3699 self.change_selections(
3700 SelectionEffects::scroll(Autoscroll::newest()),
3701 window,
3702 cx,
3703 |s| {
3704 s.clear_disjoint();
3705 s.set_pending_anchor_range(
3706 pointer_position..pointer_position,
3707 SelectMode::Character,
3708 );
3709 },
3710 );
3711 };
3712
3713 let tail = self.selections.newest::<Point>(&display_map).tail();
3714 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3715 self.columnar_selection_state = match mode {
3716 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3717 selection_tail: selection_anchor,
3718 display_point: if reset {
3719 if position.column() != goal_column {
3720 Some(DisplayPoint::new(position.row(), goal_column))
3721 } else {
3722 None
3723 }
3724 } else {
3725 None
3726 },
3727 }),
3728 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3729 selection_tail: selection_anchor,
3730 }),
3731 };
3732
3733 if !reset {
3734 self.select_columns(position, goal_column, &display_map, window, cx);
3735 }
3736 }
3737
3738 fn update_selection(
3739 &mut self,
3740 position: DisplayPoint,
3741 goal_column: u32,
3742 scroll_delta: gpui::Point<f32>,
3743 window: &mut Window,
3744 cx: &mut Context<Self>,
3745 ) {
3746 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3747
3748 if self.columnar_selection_state.is_some() {
3749 self.select_columns(position, goal_column, &display_map, window, cx);
3750 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3751 let buffer = display_map.buffer_snapshot();
3752 let head;
3753 let tail;
3754 let mode = self.selections.pending_mode().unwrap();
3755 match &mode {
3756 SelectMode::Character => {
3757 head = position.to_point(&display_map);
3758 tail = pending.tail().to_point(buffer);
3759 }
3760 SelectMode::Word(original_range) => {
3761 let offset = display_map
3762 .clip_point(position, Bias::Left)
3763 .to_offset(&display_map, Bias::Left);
3764 let original_range = original_range.to_offset(buffer);
3765
3766 let head_offset = if buffer.is_inside_word(offset, None)
3767 || original_range.contains(&offset)
3768 {
3769 let (word_range, _) = buffer.surrounding_word(offset, None);
3770 if word_range.start < original_range.start {
3771 word_range.start
3772 } else {
3773 word_range.end
3774 }
3775 } else {
3776 offset
3777 };
3778
3779 head = head_offset.to_point(buffer);
3780 if head_offset <= original_range.start {
3781 tail = original_range.end.to_point(buffer);
3782 } else {
3783 tail = original_range.start.to_point(buffer);
3784 }
3785 }
3786 SelectMode::Line(original_range) => {
3787 let original_range = original_range.to_point(display_map.buffer_snapshot());
3788
3789 let position = display_map
3790 .clip_point(position, Bias::Left)
3791 .to_point(&display_map);
3792 let line_start = display_map.prev_line_boundary(position).0;
3793 let next_line_start = buffer.clip_point(
3794 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3795 Bias::Left,
3796 );
3797
3798 if line_start < original_range.start {
3799 head = line_start
3800 } else {
3801 head = next_line_start
3802 }
3803
3804 if head <= original_range.start {
3805 tail = original_range.end;
3806 } else {
3807 tail = original_range.start;
3808 }
3809 }
3810 SelectMode::All => {
3811 return;
3812 }
3813 };
3814
3815 if head < tail {
3816 pending.start = buffer.anchor_before(head);
3817 pending.end = buffer.anchor_before(tail);
3818 pending.reversed = true;
3819 } else {
3820 pending.start = buffer.anchor_before(tail);
3821 pending.end = buffer.anchor_before(head);
3822 pending.reversed = false;
3823 }
3824
3825 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3826 s.set_pending(pending.clone(), mode);
3827 });
3828 } else {
3829 log::error!("update_selection dispatched with no pending selection");
3830 return;
3831 }
3832
3833 self.apply_scroll_delta(scroll_delta, window, cx);
3834 cx.notify();
3835 }
3836
3837 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3838 self.columnar_selection_state.take();
3839 if let Some(pending_mode) = self.selections.pending_mode() {
3840 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3841 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3842 s.select(selections);
3843 s.clear_pending();
3844 if s.is_extending() {
3845 s.set_is_extending(false);
3846 } else {
3847 s.set_select_mode(pending_mode);
3848 }
3849 });
3850 }
3851 }
3852
3853 fn select_columns(
3854 &mut self,
3855 head: DisplayPoint,
3856 goal_column: u32,
3857 display_map: &DisplaySnapshot,
3858 window: &mut Window,
3859 cx: &mut Context<Self>,
3860 ) {
3861 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3862 return;
3863 };
3864
3865 let tail = match columnar_state {
3866 ColumnarSelectionState::FromMouse {
3867 selection_tail,
3868 display_point,
3869 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3870 ColumnarSelectionState::FromSelection { selection_tail } => {
3871 selection_tail.to_display_point(display_map)
3872 }
3873 };
3874
3875 let start_row = cmp::min(tail.row(), head.row());
3876 let end_row = cmp::max(tail.row(), head.row());
3877 let start_column = cmp::min(tail.column(), goal_column);
3878 let end_column = cmp::max(tail.column(), goal_column);
3879 let reversed = start_column < tail.column();
3880
3881 let selection_ranges = (start_row.0..=end_row.0)
3882 .map(DisplayRow)
3883 .filter_map(|row| {
3884 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3885 || start_column <= display_map.line_len(row))
3886 && !display_map.is_block_line(row)
3887 {
3888 let start = display_map
3889 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3890 .to_point(display_map);
3891 let end = display_map
3892 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3893 .to_point(display_map);
3894 if reversed {
3895 Some(end..start)
3896 } else {
3897 Some(start..end)
3898 }
3899 } else {
3900 None
3901 }
3902 })
3903 .collect::<Vec<_>>();
3904 if selection_ranges.is_empty() {
3905 return;
3906 }
3907
3908 let ranges = match columnar_state {
3909 ColumnarSelectionState::FromMouse { .. } => {
3910 let mut non_empty_ranges = selection_ranges
3911 .iter()
3912 .filter(|selection_range| selection_range.start != selection_range.end)
3913 .peekable();
3914 if non_empty_ranges.peek().is_some() {
3915 non_empty_ranges.cloned().collect()
3916 } else {
3917 selection_ranges
3918 }
3919 }
3920 _ => selection_ranges,
3921 };
3922
3923 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3924 s.select_ranges(ranges);
3925 });
3926 cx.notify();
3927 }
3928
3929 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
3930 self.selections
3931 .all_adjusted(snapshot)
3932 .iter()
3933 .any(|selection| !selection.is_empty())
3934 }
3935
3936 pub fn has_pending_nonempty_selection(&self) -> bool {
3937 let pending_nonempty_selection = match self.selections.pending_anchor() {
3938 Some(Selection { start, end, .. }) => start != end,
3939 None => false,
3940 };
3941
3942 pending_nonempty_selection
3943 || (self.columnar_selection_state.is_some()
3944 && self.selections.disjoint_anchors().len() > 1)
3945 }
3946
3947 pub fn has_pending_selection(&self) -> bool {
3948 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3949 }
3950
3951 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3952 self.selection_mark_mode = false;
3953 self.selection_drag_state = SelectionDragState::None;
3954
3955 if self.clear_expanded_diff_hunks(cx) {
3956 cx.notify();
3957 return;
3958 }
3959 if self.dismiss_menus_and_popups(true, window, cx) {
3960 return;
3961 }
3962
3963 if self.mode.is_full()
3964 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3965 {
3966 return;
3967 }
3968
3969 cx.propagate();
3970 }
3971
3972 pub fn dismiss_menus_and_popups(
3973 &mut self,
3974 is_user_requested: bool,
3975 window: &mut Window,
3976 cx: &mut Context<Self>,
3977 ) -> bool {
3978 if self.take_rename(false, window, cx).is_some() {
3979 return true;
3980 }
3981
3982 if hide_hover(self, cx) {
3983 return true;
3984 }
3985
3986 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3987 return true;
3988 }
3989
3990 if self.hide_context_menu(window, cx).is_some() {
3991 return true;
3992 }
3993
3994 if self.mouse_context_menu.take().is_some() {
3995 return true;
3996 }
3997
3998 if is_user_requested && self.discard_edit_prediction(true, cx) {
3999 return true;
4000 }
4001
4002 if self.snippet_stack.pop().is_some() {
4003 return true;
4004 }
4005
4006 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4007 self.dismiss_diagnostics(cx);
4008 return true;
4009 }
4010
4011 false
4012 }
4013
4014 fn linked_editing_ranges_for(
4015 &self,
4016 selection: Range<text::Anchor>,
4017 cx: &App,
4018 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4019 if self.linked_edit_ranges.is_empty() {
4020 return None;
4021 }
4022 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4023 selection.end.buffer_id.and_then(|end_buffer_id| {
4024 if selection.start.buffer_id != Some(end_buffer_id) {
4025 return None;
4026 }
4027 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4028 let snapshot = buffer.read(cx).snapshot();
4029 self.linked_edit_ranges
4030 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4031 .map(|ranges| (ranges, snapshot, buffer))
4032 })?;
4033 use text::ToOffset as TO;
4034 // find offset from the start of current range to current cursor position
4035 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4036
4037 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4038 let start_difference = start_offset - start_byte_offset;
4039 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4040 let end_difference = end_offset - start_byte_offset;
4041 // Current range has associated linked ranges.
4042 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4043 for range in linked_ranges.iter() {
4044 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4045 let end_offset = start_offset + end_difference;
4046 let start_offset = start_offset + start_difference;
4047 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4048 continue;
4049 }
4050 if self.selections.disjoint_anchor_ranges().any(|s| {
4051 if s.start.buffer_id != selection.start.buffer_id
4052 || s.end.buffer_id != selection.end.buffer_id
4053 {
4054 return false;
4055 }
4056 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4057 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4058 }) {
4059 continue;
4060 }
4061 let start = buffer_snapshot.anchor_after(start_offset);
4062 let end = buffer_snapshot.anchor_after(end_offset);
4063 linked_edits
4064 .entry(buffer.clone())
4065 .or_default()
4066 .push(start..end);
4067 }
4068 Some(linked_edits)
4069 }
4070
4071 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4072 let text: Arc<str> = text.into();
4073
4074 if self.read_only(cx) {
4075 return;
4076 }
4077
4078 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4079
4080 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4081 let mut bracket_inserted = false;
4082 let mut edits = Vec::new();
4083 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4084 let mut new_selections = Vec::with_capacity(selections.len());
4085 let mut new_autoclose_regions = Vec::new();
4086 let snapshot = self.buffer.read(cx).read(cx);
4087 let mut clear_linked_edit_ranges = false;
4088
4089 for (selection, autoclose_region) in
4090 self.selections_with_autoclose_regions(selections, &snapshot)
4091 {
4092 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4093 // Determine if the inserted text matches the opening or closing
4094 // bracket of any of this language's bracket pairs.
4095 let mut bracket_pair = None;
4096 let mut is_bracket_pair_start = false;
4097 let mut is_bracket_pair_end = false;
4098 if !text.is_empty() {
4099 let mut bracket_pair_matching_end = None;
4100 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4101 // and they are removing the character that triggered IME popup.
4102 for (pair, enabled) in scope.brackets() {
4103 if !pair.close && !pair.surround {
4104 continue;
4105 }
4106
4107 if enabled && pair.start.ends_with(text.as_ref()) {
4108 let prefix_len = pair.start.len() - text.len();
4109 let preceding_text_matches_prefix = prefix_len == 0
4110 || (selection.start.column >= (prefix_len as u32)
4111 && snapshot.contains_str_at(
4112 Point::new(
4113 selection.start.row,
4114 selection.start.column - (prefix_len as u32),
4115 ),
4116 &pair.start[..prefix_len],
4117 ));
4118 if preceding_text_matches_prefix {
4119 bracket_pair = Some(pair.clone());
4120 is_bracket_pair_start = true;
4121 break;
4122 }
4123 }
4124 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4125 {
4126 // take first bracket pair matching end, but don't break in case a later bracket
4127 // pair matches start
4128 bracket_pair_matching_end = Some(pair.clone());
4129 }
4130 }
4131 if let Some(end) = bracket_pair_matching_end
4132 && bracket_pair.is_none()
4133 {
4134 bracket_pair = Some(end);
4135 is_bracket_pair_end = true;
4136 }
4137 }
4138
4139 if let Some(bracket_pair) = bracket_pair {
4140 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4141 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4142 let auto_surround =
4143 self.use_auto_surround && snapshot_settings.use_auto_surround;
4144 if selection.is_empty() {
4145 if is_bracket_pair_start {
4146 // If the inserted text is a suffix of an opening bracket and the
4147 // selection is preceded by the rest of the opening bracket, then
4148 // insert the closing bracket.
4149 let following_text_allows_autoclose = snapshot
4150 .chars_at(selection.start)
4151 .next()
4152 .is_none_or(|c| scope.should_autoclose_before(c));
4153
4154 let preceding_text_allows_autoclose = selection.start.column == 0
4155 || snapshot
4156 .reversed_chars_at(selection.start)
4157 .next()
4158 .is_none_or(|c| {
4159 bracket_pair.start != bracket_pair.end
4160 || !snapshot
4161 .char_classifier_at(selection.start)
4162 .is_word(c)
4163 });
4164
4165 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4166 && bracket_pair.start.len() == 1
4167 {
4168 let target = bracket_pair.start.chars().next().unwrap();
4169 let current_line_count = snapshot
4170 .reversed_chars_at(selection.start)
4171 .take_while(|&c| c != '\n')
4172 .filter(|&c| c == target)
4173 .count();
4174 current_line_count % 2 == 1
4175 } else {
4176 false
4177 };
4178
4179 if autoclose
4180 && bracket_pair.close
4181 && following_text_allows_autoclose
4182 && preceding_text_allows_autoclose
4183 && !is_closing_quote
4184 {
4185 let anchor = snapshot.anchor_before(selection.end);
4186 new_selections.push((selection.map(|_| anchor), text.len()));
4187 new_autoclose_regions.push((
4188 anchor,
4189 text.len(),
4190 selection.id,
4191 bracket_pair.clone(),
4192 ));
4193 edits.push((
4194 selection.range(),
4195 format!("{}{}", text, bracket_pair.end).into(),
4196 ));
4197 bracket_inserted = true;
4198 continue;
4199 }
4200 }
4201
4202 if let Some(region) = autoclose_region {
4203 // If the selection is followed by an auto-inserted closing bracket,
4204 // then don't insert that closing bracket again; just move the selection
4205 // past the closing bracket.
4206 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4207 && text.as_ref() == region.pair.end.as_str()
4208 && snapshot.contains_str_at(region.range.end, text.as_ref());
4209 if should_skip {
4210 let anchor = snapshot.anchor_after(selection.end);
4211 new_selections
4212 .push((selection.map(|_| anchor), region.pair.end.len()));
4213 continue;
4214 }
4215 }
4216
4217 let always_treat_brackets_as_autoclosed = snapshot
4218 .language_settings_at(selection.start, cx)
4219 .always_treat_brackets_as_autoclosed;
4220 if always_treat_brackets_as_autoclosed
4221 && is_bracket_pair_end
4222 && snapshot.contains_str_at(selection.end, text.as_ref())
4223 {
4224 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4225 // and the inserted text is a closing bracket and the selection is followed
4226 // by the closing bracket then move the selection past the closing bracket.
4227 let anchor = snapshot.anchor_after(selection.end);
4228 new_selections.push((selection.map(|_| anchor), text.len()));
4229 continue;
4230 }
4231 }
4232 // If an opening bracket is 1 character long and is typed while
4233 // text is selected, then surround that text with the bracket pair.
4234 else if auto_surround
4235 && bracket_pair.surround
4236 && is_bracket_pair_start
4237 && bracket_pair.start.chars().count() == 1
4238 {
4239 edits.push((selection.start..selection.start, text.clone()));
4240 edits.push((
4241 selection.end..selection.end,
4242 bracket_pair.end.as_str().into(),
4243 ));
4244 bracket_inserted = true;
4245 new_selections.push((
4246 Selection {
4247 id: selection.id,
4248 start: snapshot.anchor_after(selection.start),
4249 end: snapshot.anchor_before(selection.end),
4250 reversed: selection.reversed,
4251 goal: selection.goal,
4252 },
4253 0,
4254 ));
4255 continue;
4256 }
4257 }
4258 }
4259
4260 if self.auto_replace_emoji_shortcode
4261 && selection.is_empty()
4262 && text.as_ref().ends_with(':')
4263 && let Some(possible_emoji_short_code) =
4264 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4265 && !possible_emoji_short_code.is_empty()
4266 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4267 {
4268 let emoji_shortcode_start = Point::new(
4269 selection.start.row,
4270 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4271 );
4272
4273 // Remove shortcode from buffer
4274 edits.push((
4275 emoji_shortcode_start..selection.start,
4276 "".to_string().into(),
4277 ));
4278 new_selections.push((
4279 Selection {
4280 id: selection.id,
4281 start: snapshot.anchor_after(emoji_shortcode_start),
4282 end: snapshot.anchor_before(selection.start),
4283 reversed: selection.reversed,
4284 goal: selection.goal,
4285 },
4286 0,
4287 ));
4288
4289 // Insert emoji
4290 let selection_start_anchor = snapshot.anchor_after(selection.start);
4291 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4292 edits.push((selection.start..selection.end, emoji.to_string().into()));
4293
4294 continue;
4295 }
4296
4297 // If not handling any auto-close operation, then just replace the selected
4298 // text with the given input and move the selection to the end of the
4299 // newly inserted text.
4300 let anchor = snapshot.anchor_after(selection.end);
4301 if !self.linked_edit_ranges.is_empty() {
4302 let start_anchor = snapshot.anchor_before(selection.start);
4303
4304 let is_word_char = text.chars().next().is_none_or(|char| {
4305 let classifier = snapshot
4306 .char_classifier_at(start_anchor.to_offset(&snapshot))
4307 .scope_context(Some(CharScopeContext::LinkedEdit));
4308 classifier.is_word(char)
4309 });
4310
4311 if is_word_char {
4312 if let Some(ranges) = self
4313 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4314 {
4315 for (buffer, edits) in ranges {
4316 linked_edits
4317 .entry(buffer.clone())
4318 .or_default()
4319 .extend(edits.into_iter().map(|range| (range, text.clone())));
4320 }
4321 }
4322 } else {
4323 clear_linked_edit_ranges = true;
4324 }
4325 }
4326
4327 new_selections.push((selection.map(|_| anchor), 0));
4328 edits.push((selection.start..selection.end, text.clone()));
4329 }
4330
4331 drop(snapshot);
4332
4333 self.transact(window, cx, |this, window, cx| {
4334 if clear_linked_edit_ranges {
4335 this.linked_edit_ranges.clear();
4336 }
4337 let initial_buffer_versions =
4338 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4339
4340 this.buffer.update(cx, |buffer, cx| {
4341 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4342 });
4343 for (buffer, edits) in linked_edits {
4344 buffer.update(cx, |buffer, cx| {
4345 let snapshot = buffer.snapshot();
4346 let edits = edits
4347 .into_iter()
4348 .map(|(range, text)| {
4349 use text::ToPoint as TP;
4350 let end_point = TP::to_point(&range.end, &snapshot);
4351 let start_point = TP::to_point(&range.start, &snapshot);
4352 (start_point..end_point, text)
4353 })
4354 .sorted_by_key(|(range, _)| range.start);
4355 buffer.edit(edits, None, cx);
4356 })
4357 }
4358 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4359 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4360 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4361 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4362 .zip(new_selection_deltas)
4363 .map(|(selection, delta)| Selection {
4364 id: selection.id,
4365 start: selection.start + delta,
4366 end: selection.end + delta,
4367 reversed: selection.reversed,
4368 goal: SelectionGoal::None,
4369 })
4370 .collect::<Vec<_>>();
4371
4372 let mut i = 0;
4373 for (position, delta, selection_id, pair) in new_autoclose_regions {
4374 let position = position.to_offset(map.buffer_snapshot()) + delta;
4375 let start = map.buffer_snapshot().anchor_before(position);
4376 let end = map.buffer_snapshot().anchor_after(position);
4377 while let Some(existing_state) = this.autoclose_regions.get(i) {
4378 match existing_state
4379 .range
4380 .start
4381 .cmp(&start, map.buffer_snapshot())
4382 {
4383 Ordering::Less => i += 1,
4384 Ordering::Greater => break,
4385 Ordering::Equal => {
4386 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4387 Ordering::Less => i += 1,
4388 Ordering::Equal => break,
4389 Ordering::Greater => break,
4390 }
4391 }
4392 }
4393 }
4394 this.autoclose_regions.insert(
4395 i,
4396 AutocloseRegion {
4397 selection_id,
4398 range: start..end,
4399 pair,
4400 },
4401 );
4402 }
4403
4404 let had_active_edit_prediction = this.has_active_edit_prediction();
4405 this.change_selections(
4406 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4407 window,
4408 cx,
4409 |s| s.select(new_selections),
4410 );
4411
4412 if !bracket_inserted
4413 && let Some(on_type_format_task) =
4414 this.trigger_on_type_formatting(text.to_string(), window, cx)
4415 {
4416 on_type_format_task.detach_and_log_err(cx);
4417 }
4418
4419 let editor_settings = EditorSettings::get_global(cx);
4420 if bracket_inserted
4421 && (editor_settings.auto_signature_help
4422 || editor_settings.show_signature_help_after_edits)
4423 {
4424 this.show_signature_help(&ShowSignatureHelp, window, cx);
4425 }
4426
4427 let trigger_in_words =
4428 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4429 if this.hard_wrap.is_some() {
4430 let latest: Range<Point> = this.selections.newest(&map).range();
4431 if latest.is_empty()
4432 && this
4433 .buffer()
4434 .read(cx)
4435 .snapshot(cx)
4436 .line_len(MultiBufferRow(latest.start.row))
4437 == latest.start.column
4438 {
4439 this.rewrap_impl(
4440 RewrapOptions {
4441 override_language_settings: true,
4442 preserve_existing_whitespace: true,
4443 },
4444 cx,
4445 )
4446 }
4447 }
4448 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4449 refresh_linked_ranges(this, window, cx);
4450 this.refresh_edit_prediction(true, false, window, cx);
4451 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4452 });
4453 }
4454
4455 fn find_possible_emoji_shortcode_at_position(
4456 snapshot: &MultiBufferSnapshot,
4457 position: Point,
4458 ) -> Option<String> {
4459 let mut chars = Vec::new();
4460 let mut found_colon = false;
4461 for char in snapshot.reversed_chars_at(position).take(100) {
4462 // Found a possible emoji shortcode in the middle of the buffer
4463 if found_colon {
4464 if char.is_whitespace() {
4465 chars.reverse();
4466 return Some(chars.iter().collect());
4467 }
4468 // If the previous character is not a whitespace, we are in the middle of a word
4469 // and we only want to complete the shortcode if the word is made up of other emojis
4470 let mut containing_word = String::new();
4471 for ch in snapshot
4472 .reversed_chars_at(position)
4473 .skip(chars.len() + 1)
4474 .take(100)
4475 {
4476 if ch.is_whitespace() {
4477 break;
4478 }
4479 containing_word.push(ch);
4480 }
4481 let containing_word = containing_word.chars().rev().collect::<String>();
4482 if util::word_consists_of_emojis(containing_word.as_str()) {
4483 chars.reverse();
4484 return Some(chars.iter().collect());
4485 }
4486 }
4487
4488 if char.is_whitespace() || !char.is_ascii() {
4489 return None;
4490 }
4491 if char == ':' {
4492 found_colon = true;
4493 } else {
4494 chars.push(char);
4495 }
4496 }
4497 // Found a possible emoji shortcode at the beginning of the buffer
4498 chars.reverse();
4499 Some(chars.iter().collect())
4500 }
4501
4502 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4503 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4504 self.transact(window, cx, |this, window, cx| {
4505 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4506 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
4507 let multi_buffer = this.buffer.read(cx);
4508 let buffer = multi_buffer.snapshot(cx);
4509 selections
4510 .iter()
4511 .map(|selection| {
4512 let start_point = selection.start.to_point(&buffer);
4513 let mut existing_indent =
4514 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4515 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4516 let start = selection.start;
4517 let end = selection.end;
4518 let selection_is_empty = start == end;
4519 let language_scope = buffer.language_scope_at(start);
4520 let (
4521 comment_delimiter,
4522 doc_delimiter,
4523 insert_extra_newline,
4524 indent_on_newline,
4525 indent_on_extra_newline,
4526 ) = if let Some(language) = &language_scope {
4527 let mut insert_extra_newline =
4528 insert_extra_newline_brackets(&buffer, start..end, language)
4529 || insert_extra_newline_tree_sitter(&buffer, start..end);
4530
4531 // Comment extension on newline is allowed only for cursor selections
4532 let comment_delimiter = maybe!({
4533 if !selection_is_empty {
4534 return None;
4535 }
4536
4537 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4538 return None;
4539 }
4540
4541 let delimiters = language.line_comment_prefixes();
4542 let max_len_of_delimiter =
4543 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4544 let (snapshot, range) =
4545 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4546
4547 let num_of_whitespaces = snapshot
4548 .chars_for_range(range.clone())
4549 .take_while(|c| c.is_whitespace())
4550 .count();
4551 let comment_candidate = snapshot
4552 .chars_for_range(range.clone())
4553 .skip(num_of_whitespaces)
4554 .take(max_len_of_delimiter)
4555 .collect::<String>();
4556 let (delimiter, trimmed_len) = delimiters
4557 .iter()
4558 .filter_map(|delimiter| {
4559 let prefix = delimiter.trim_end();
4560 if comment_candidate.starts_with(prefix) {
4561 Some((delimiter, prefix.len()))
4562 } else {
4563 None
4564 }
4565 })
4566 .max_by_key(|(_, len)| *len)?;
4567
4568 if let Some(BlockCommentConfig {
4569 start: block_start, ..
4570 }) = language.block_comment()
4571 {
4572 let block_start_trimmed = block_start.trim_end();
4573 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4574 let line_content = snapshot
4575 .chars_for_range(range)
4576 .skip(num_of_whitespaces)
4577 .take(block_start_trimmed.len())
4578 .collect::<String>();
4579
4580 if line_content.starts_with(block_start_trimmed) {
4581 return None;
4582 }
4583 }
4584 }
4585
4586 let cursor_is_placed_after_comment_marker =
4587 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4588 if cursor_is_placed_after_comment_marker {
4589 Some(delimiter.clone())
4590 } else {
4591 None
4592 }
4593 });
4594
4595 let mut indent_on_newline = IndentSize::spaces(0);
4596 let mut indent_on_extra_newline = IndentSize::spaces(0);
4597
4598 let doc_delimiter = maybe!({
4599 if !selection_is_empty {
4600 return None;
4601 }
4602
4603 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4604 return None;
4605 }
4606
4607 let BlockCommentConfig {
4608 start: start_tag,
4609 end: end_tag,
4610 prefix: delimiter,
4611 tab_size: len,
4612 } = language.documentation_comment()?;
4613 let is_within_block_comment = buffer
4614 .language_scope_at(start_point)
4615 .is_some_and(|scope| scope.override_name() == Some("comment"));
4616 if !is_within_block_comment {
4617 return None;
4618 }
4619
4620 let (snapshot, range) =
4621 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4622
4623 let num_of_whitespaces = snapshot
4624 .chars_for_range(range.clone())
4625 .take_while(|c| c.is_whitespace())
4626 .count();
4627
4628 // It is safe to use a column from MultiBufferPoint in context of a single buffer ranges, because we're only ever looking at a single line at a time.
4629 let column = start_point.column;
4630 let cursor_is_after_start_tag = {
4631 let start_tag_len = start_tag.len();
4632 let start_tag_line = snapshot
4633 .chars_for_range(range.clone())
4634 .skip(num_of_whitespaces)
4635 .take(start_tag_len)
4636 .collect::<String>();
4637 if start_tag_line.starts_with(start_tag.as_ref()) {
4638 num_of_whitespaces + start_tag_len <= column as usize
4639 } else {
4640 false
4641 }
4642 };
4643
4644 let cursor_is_after_delimiter = {
4645 let delimiter_trim = delimiter.trim_end();
4646 let delimiter_line = snapshot
4647 .chars_for_range(range.clone())
4648 .skip(num_of_whitespaces)
4649 .take(delimiter_trim.len())
4650 .collect::<String>();
4651 if delimiter_line.starts_with(delimiter_trim) {
4652 num_of_whitespaces + delimiter_trim.len() <= column as usize
4653 } else {
4654 false
4655 }
4656 };
4657
4658 let cursor_is_before_end_tag_if_exists = {
4659 let mut char_position = 0u32;
4660 let mut end_tag_offset = None;
4661
4662 'outer: for chunk in snapshot.text_for_range(range) {
4663 if let Some(byte_pos) = chunk.find(&**end_tag) {
4664 let chars_before_match =
4665 chunk[..byte_pos].chars().count() as u32;
4666 end_tag_offset =
4667 Some(char_position + chars_before_match);
4668 break 'outer;
4669 }
4670 char_position += chunk.chars().count() as u32;
4671 }
4672
4673 if let Some(end_tag_offset) = end_tag_offset {
4674 let cursor_is_before_end_tag = column <= end_tag_offset;
4675 if cursor_is_after_start_tag {
4676 if cursor_is_before_end_tag {
4677 insert_extra_newline = true;
4678 }
4679 let cursor_is_at_start_of_end_tag =
4680 column == end_tag_offset;
4681 if cursor_is_at_start_of_end_tag {
4682 indent_on_extra_newline.len = *len;
4683 }
4684 }
4685 cursor_is_before_end_tag
4686 } else {
4687 true
4688 }
4689 };
4690
4691 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4692 && cursor_is_before_end_tag_if_exists
4693 {
4694 if cursor_is_after_start_tag {
4695 indent_on_newline.len = *len;
4696 }
4697 Some(delimiter.clone())
4698 } else {
4699 None
4700 }
4701 });
4702
4703 (
4704 comment_delimiter,
4705 doc_delimiter,
4706 insert_extra_newline,
4707 indent_on_newline,
4708 indent_on_extra_newline,
4709 )
4710 } else {
4711 (
4712 None,
4713 None,
4714 false,
4715 IndentSize::default(),
4716 IndentSize::default(),
4717 )
4718 };
4719
4720 let prevent_auto_indent = doc_delimiter.is_some();
4721 let delimiter = comment_delimiter.or(doc_delimiter);
4722
4723 let capacity_for_delimiter =
4724 delimiter.as_deref().map(str::len).unwrap_or_default();
4725 let mut new_text = String::with_capacity(
4726 1 + capacity_for_delimiter
4727 + existing_indent.len as usize
4728 + indent_on_newline.len as usize
4729 + indent_on_extra_newline.len as usize,
4730 );
4731 new_text.push('\n');
4732 new_text.extend(existing_indent.chars());
4733 new_text.extend(indent_on_newline.chars());
4734
4735 if let Some(delimiter) = &delimiter {
4736 new_text.push_str(delimiter);
4737 }
4738
4739 if insert_extra_newline {
4740 new_text.push('\n');
4741 new_text.extend(existing_indent.chars());
4742 new_text.extend(indent_on_extra_newline.chars());
4743 }
4744
4745 let anchor = buffer.anchor_after(end);
4746 let new_selection = selection.map(|_| anchor);
4747 (
4748 ((start..end, new_text), prevent_auto_indent),
4749 (insert_extra_newline, new_selection),
4750 )
4751 })
4752 .unzip()
4753 };
4754
4755 let mut auto_indent_edits = Vec::new();
4756 let mut edits = Vec::new();
4757 for (edit, prevent_auto_indent) in edits_with_flags {
4758 if prevent_auto_indent {
4759 edits.push(edit);
4760 } else {
4761 auto_indent_edits.push(edit);
4762 }
4763 }
4764 if !edits.is_empty() {
4765 this.edit(edits, cx);
4766 }
4767 if !auto_indent_edits.is_empty() {
4768 this.edit_with_autoindent(auto_indent_edits, cx);
4769 }
4770
4771 let buffer = this.buffer.read(cx).snapshot(cx);
4772 let new_selections = selection_info
4773 .into_iter()
4774 .map(|(extra_newline_inserted, new_selection)| {
4775 let mut cursor = new_selection.end.to_point(&buffer);
4776 if extra_newline_inserted {
4777 cursor.row -= 1;
4778 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4779 }
4780 new_selection.map(|_| cursor)
4781 })
4782 .collect();
4783
4784 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4785 this.refresh_edit_prediction(true, false, window, cx);
4786 });
4787 }
4788
4789 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4790 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4791
4792 let buffer = self.buffer.read(cx);
4793 let snapshot = buffer.snapshot(cx);
4794
4795 let mut edits = Vec::new();
4796 let mut rows = Vec::new();
4797
4798 for (rows_inserted, selection) in self
4799 .selections
4800 .all_adjusted(&self.display_snapshot(cx))
4801 .into_iter()
4802 .enumerate()
4803 {
4804 let cursor = selection.head();
4805 let row = cursor.row;
4806
4807 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4808
4809 let newline = "\n".to_string();
4810 edits.push((start_of_line..start_of_line, newline));
4811
4812 rows.push(row + rows_inserted as u32);
4813 }
4814
4815 self.transact(window, cx, |editor, window, cx| {
4816 editor.edit(edits, cx);
4817
4818 editor.change_selections(Default::default(), window, cx, |s| {
4819 let mut index = 0;
4820 s.move_cursors_with(|map, _, _| {
4821 let row = rows[index];
4822 index += 1;
4823
4824 let point = Point::new(row, 0);
4825 let boundary = map.next_line_boundary(point).1;
4826 let clipped = map.clip_point(boundary, Bias::Left);
4827
4828 (clipped, SelectionGoal::None)
4829 });
4830 });
4831
4832 let mut indent_edits = Vec::new();
4833 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4834 for row in rows {
4835 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4836 for (row, indent) in indents {
4837 if indent.len == 0 {
4838 continue;
4839 }
4840
4841 let text = match indent.kind {
4842 IndentKind::Space => " ".repeat(indent.len as usize),
4843 IndentKind::Tab => "\t".repeat(indent.len as usize),
4844 };
4845 let point = Point::new(row.0, 0);
4846 indent_edits.push((point..point, text));
4847 }
4848 }
4849 editor.edit(indent_edits, cx);
4850 });
4851 }
4852
4853 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4854 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4855
4856 let buffer = self.buffer.read(cx);
4857 let snapshot = buffer.snapshot(cx);
4858
4859 let mut edits = Vec::new();
4860 let mut rows = Vec::new();
4861 let mut rows_inserted = 0;
4862
4863 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
4864 let cursor = selection.head();
4865 let row = cursor.row;
4866
4867 let point = Point::new(row + 1, 0);
4868 let start_of_line = snapshot.clip_point(point, Bias::Left);
4869
4870 let newline = "\n".to_string();
4871 edits.push((start_of_line..start_of_line, newline));
4872
4873 rows_inserted += 1;
4874 rows.push(row + rows_inserted);
4875 }
4876
4877 self.transact(window, cx, |editor, window, cx| {
4878 editor.edit(edits, cx);
4879
4880 editor.change_selections(Default::default(), window, cx, |s| {
4881 let mut index = 0;
4882 s.move_cursors_with(|map, _, _| {
4883 let row = rows[index];
4884 index += 1;
4885
4886 let point = Point::new(row, 0);
4887 let boundary = map.next_line_boundary(point).1;
4888 let clipped = map.clip_point(boundary, Bias::Left);
4889
4890 (clipped, SelectionGoal::None)
4891 });
4892 });
4893
4894 let mut indent_edits = Vec::new();
4895 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4896 for row in rows {
4897 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4898 for (row, indent) in indents {
4899 if indent.len == 0 {
4900 continue;
4901 }
4902
4903 let text = match indent.kind {
4904 IndentKind::Space => " ".repeat(indent.len as usize),
4905 IndentKind::Tab => "\t".repeat(indent.len as usize),
4906 };
4907 let point = Point::new(row.0, 0);
4908 indent_edits.push((point..point, text));
4909 }
4910 }
4911 editor.edit(indent_edits, cx);
4912 });
4913 }
4914
4915 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4916 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4917 original_indent_columns: Vec::new(),
4918 });
4919 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4920 }
4921
4922 fn insert_with_autoindent_mode(
4923 &mut self,
4924 text: &str,
4925 autoindent_mode: Option<AutoindentMode>,
4926 window: &mut Window,
4927 cx: &mut Context<Self>,
4928 ) {
4929 if self.read_only(cx) {
4930 return;
4931 }
4932
4933 let text: Arc<str> = text.into();
4934 self.transact(window, cx, |this, window, cx| {
4935 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
4936 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4937 let anchors = {
4938 let snapshot = buffer.read(cx);
4939 old_selections
4940 .iter()
4941 .map(|s| {
4942 let anchor = snapshot.anchor_after(s.head());
4943 s.map(|_| anchor)
4944 })
4945 .collect::<Vec<_>>()
4946 };
4947 buffer.edit(
4948 old_selections
4949 .iter()
4950 .map(|s| (s.start..s.end, text.clone())),
4951 autoindent_mode,
4952 cx,
4953 );
4954 anchors
4955 });
4956
4957 this.change_selections(Default::default(), window, cx, |s| {
4958 s.select_anchors(selection_anchors);
4959 });
4960
4961 cx.notify();
4962 });
4963 }
4964
4965 fn trigger_completion_on_input(
4966 &mut self,
4967 text: &str,
4968 trigger_in_words: bool,
4969 window: &mut Window,
4970 cx: &mut Context<Self>,
4971 ) {
4972 let completions_source = self
4973 .context_menu
4974 .borrow()
4975 .as_ref()
4976 .and_then(|menu| match menu {
4977 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4978 CodeContextMenu::CodeActions(_) => None,
4979 });
4980
4981 match completions_source {
4982 Some(CompletionsMenuSource::Words { .. }) => {
4983 self.open_or_update_completions_menu(
4984 Some(CompletionsMenuSource::Words {
4985 ignore_threshold: false,
4986 }),
4987 None,
4988 window,
4989 cx,
4990 );
4991 }
4992 Some(CompletionsMenuSource::Normal)
4993 | Some(CompletionsMenuSource::SnippetChoices)
4994 | None
4995 if self.is_completion_trigger(
4996 text,
4997 trigger_in_words,
4998 completions_source.is_some(),
4999 cx,
5000 ) =>
5001 {
5002 self.show_completions(
5003 &ShowCompletions {
5004 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
5005 },
5006 window,
5007 cx,
5008 )
5009 }
5010 _ => {
5011 self.hide_context_menu(window, cx);
5012 }
5013 }
5014 }
5015
5016 fn is_completion_trigger(
5017 &self,
5018 text: &str,
5019 trigger_in_words: bool,
5020 menu_is_open: bool,
5021 cx: &mut Context<Self>,
5022 ) -> bool {
5023 let position = self.selections.newest_anchor().head();
5024 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
5025 return false;
5026 };
5027
5028 if let Some(completion_provider) = &self.completion_provider {
5029 completion_provider.is_completion_trigger(
5030 &buffer,
5031 position.text_anchor,
5032 text,
5033 trigger_in_words,
5034 menu_is_open,
5035 cx,
5036 )
5037 } else {
5038 false
5039 }
5040 }
5041
5042 /// If any empty selections is touching the start of its innermost containing autoclose
5043 /// region, expand it to select the brackets.
5044 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5045 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5046 let buffer = self.buffer.read(cx).read(cx);
5047 let new_selections = self
5048 .selections_with_autoclose_regions(selections, &buffer)
5049 .map(|(mut selection, region)| {
5050 if !selection.is_empty() {
5051 return selection;
5052 }
5053
5054 if let Some(region) = region {
5055 let mut range = region.range.to_offset(&buffer);
5056 if selection.start == range.start && range.start >= region.pair.start.len() {
5057 range.start -= region.pair.start.len();
5058 if buffer.contains_str_at(range.start, ®ion.pair.start)
5059 && buffer.contains_str_at(range.end, ®ion.pair.end)
5060 {
5061 range.end += region.pair.end.len();
5062 selection.start = range.start;
5063 selection.end = range.end;
5064
5065 return selection;
5066 }
5067 }
5068 }
5069
5070 let always_treat_brackets_as_autoclosed = buffer
5071 .language_settings_at(selection.start, cx)
5072 .always_treat_brackets_as_autoclosed;
5073
5074 if !always_treat_brackets_as_autoclosed {
5075 return selection;
5076 }
5077
5078 if let Some(scope) = buffer.language_scope_at(selection.start) {
5079 for (pair, enabled) in scope.brackets() {
5080 if !enabled || !pair.close {
5081 continue;
5082 }
5083
5084 if buffer.contains_str_at(selection.start, &pair.end) {
5085 let pair_start_len = pair.start.len();
5086 if buffer.contains_str_at(
5087 selection.start.saturating_sub(pair_start_len),
5088 &pair.start,
5089 ) {
5090 selection.start -= pair_start_len;
5091 selection.end += pair.end.len();
5092
5093 return selection;
5094 }
5095 }
5096 }
5097 }
5098
5099 selection
5100 })
5101 .collect();
5102
5103 drop(buffer);
5104 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5105 selections.select(new_selections)
5106 });
5107 }
5108
5109 /// Iterate the given selections, and for each one, find the smallest surrounding
5110 /// autoclose region. This uses the ordering of the selections and the autoclose
5111 /// regions to avoid repeated comparisons.
5112 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5113 &'a self,
5114 selections: impl IntoIterator<Item = Selection<D>>,
5115 buffer: &'a MultiBufferSnapshot,
5116 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5117 let mut i = 0;
5118 let mut regions = self.autoclose_regions.as_slice();
5119 selections.into_iter().map(move |selection| {
5120 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5121
5122 let mut enclosing = None;
5123 while let Some(pair_state) = regions.get(i) {
5124 if pair_state.range.end.to_offset(buffer) < range.start {
5125 regions = ®ions[i + 1..];
5126 i = 0;
5127 } else if pair_state.range.start.to_offset(buffer) > range.end {
5128 break;
5129 } else {
5130 if pair_state.selection_id == selection.id {
5131 enclosing = Some(pair_state);
5132 }
5133 i += 1;
5134 }
5135 }
5136
5137 (selection, enclosing)
5138 })
5139 }
5140
5141 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5142 fn invalidate_autoclose_regions(
5143 &mut self,
5144 mut selections: &[Selection<Anchor>],
5145 buffer: &MultiBufferSnapshot,
5146 ) {
5147 self.autoclose_regions.retain(|state| {
5148 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5149 return false;
5150 }
5151
5152 let mut i = 0;
5153 while let Some(selection) = selections.get(i) {
5154 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5155 selections = &selections[1..];
5156 continue;
5157 }
5158 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5159 break;
5160 }
5161 if selection.id == state.selection_id {
5162 return true;
5163 } else {
5164 i += 1;
5165 }
5166 }
5167 false
5168 });
5169 }
5170
5171 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5172 let offset = position.to_offset(buffer);
5173 let (word_range, kind) =
5174 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5175 if offset > word_range.start && kind == Some(CharKind::Word) {
5176 Some(
5177 buffer
5178 .text_for_range(word_range.start..offset)
5179 .collect::<String>(),
5180 )
5181 } else {
5182 None
5183 }
5184 }
5185
5186 pub fn toggle_inline_values(
5187 &mut self,
5188 _: &ToggleInlineValues,
5189 _: &mut Window,
5190 cx: &mut Context<Self>,
5191 ) {
5192 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5193
5194 self.refresh_inline_values(cx);
5195 }
5196
5197 pub fn toggle_inlay_hints(
5198 &mut self,
5199 _: &ToggleInlayHints,
5200 _: &mut Window,
5201 cx: &mut Context<Self>,
5202 ) {
5203 self.refresh_inlay_hints(
5204 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5205 cx,
5206 );
5207 }
5208
5209 pub fn inlay_hints_enabled(&self) -> bool {
5210 self.inlay_hint_cache.enabled
5211 }
5212
5213 pub fn inline_values_enabled(&self) -> bool {
5214 self.inline_value_cache.enabled
5215 }
5216
5217 #[cfg(any(test, feature = "test-support"))]
5218 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5219 self.display_map
5220 .read(cx)
5221 .current_inlays()
5222 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5223 .cloned()
5224 .collect()
5225 }
5226
5227 #[cfg(any(test, feature = "test-support"))]
5228 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5229 self.display_map
5230 .read(cx)
5231 .current_inlays()
5232 .cloned()
5233 .collect()
5234 }
5235
5236 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5237 if self.semantics_provider.is_none() || !self.mode.is_full() {
5238 return;
5239 }
5240
5241 let reason_description = reason.description();
5242 let ignore_debounce = matches!(
5243 reason,
5244 InlayHintRefreshReason::SettingsChange(_)
5245 | InlayHintRefreshReason::Toggle(_)
5246 | InlayHintRefreshReason::ExcerptsRemoved(_)
5247 | InlayHintRefreshReason::ModifiersChanged(_)
5248 );
5249 let (invalidate_cache, required_languages) = match reason {
5250 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5251 match self.inlay_hint_cache.modifiers_override(enabled) {
5252 Some(enabled) => {
5253 if enabled {
5254 (InvalidationStrategy::RefreshRequested, None)
5255 } else {
5256 self.clear_inlay_hints(cx);
5257 return;
5258 }
5259 }
5260 None => return,
5261 }
5262 }
5263 InlayHintRefreshReason::Toggle(enabled) => {
5264 if self.inlay_hint_cache.toggle(enabled) {
5265 if enabled {
5266 (InvalidationStrategy::RefreshRequested, None)
5267 } else {
5268 self.clear_inlay_hints(cx);
5269 return;
5270 }
5271 } else {
5272 return;
5273 }
5274 }
5275 InlayHintRefreshReason::SettingsChange(new_settings) => {
5276 match self.inlay_hint_cache.update_settings(
5277 &self.buffer,
5278 new_settings,
5279 self.visible_inlay_hints(cx).cloned().collect::<Vec<_>>(),
5280 cx,
5281 ) {
5282 ControlFlow::Break(Some(InlaySplice {
5283 to_remove,
5284 to_insert,
5285 })) => {
5286 self.splice_inlays(&to_remove, to_insert, cx);
5287 return;
5288 }
5289 ControlFlow::Break(None) => return,
5290 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5291 }
5292 }
5293 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5294 if let Some(InlaySplice {
5295 to_remove,
5296 to_insert,
5297 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5298 {
5299 self.splice_inlays(&to_remove, to_insert, cx);
5300 }
5301 self.display_map.update(cx, |display_map, _| {
5302 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5303 });
5304 return;
5305 }
5306 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5307 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5308 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5309 }
5310 InlayHintRefreshReason::RefreshRequested => {
5311 (InvalidationStrategy::RefreshRequested, None)
5312 }
5313 };
5314
5315 let mut visible_excerpts = self.visible_excerpts(required_languages.as_ref(), cx);
5316 visible_excerpts.retain(|_, (buffer, _, _)| {
5317 self.registered_buffers
5318 .contains_key(&buffer.read(cx).remote_id())
5319 });
5320
5321 if let Some(InlaySplice {
5322 to_remove,
5323 to_insert,
5324 }) = self.inlay_hint_cache.spawn_hint_refresh(
5325 reason_description,
5326 visible_excerpts,
5327 invalidate_cache,
5328 ignore_debounce,
5329 cx,
5330 ) {
5331 self.splice_inlays(&to_remove, to_insert, cx);
5332 }
5333 }
5334
5335 pub fn clear_inlay_hints(&self, cx: &mut Context<Editor>) {
5336 self.splice_inlays(
5337 &self
5338 .visible_inlay_hints(cx)
5339 .map(|inlay| inlay.id)
5340 .collect::<Vec<_>>(),
5341 Vec::new(),
5342 cx,
5343 );
5344 }
5345
5346 fn visible_inlay_hints<'a>(
5347 &'a self,
5348 cx: &'a Context<Editor>,
5349 ) -> impl Iterator<Item = &'a Inlay> {
5350 self.display_map
5351 .read(cx)
5352 .current_inlays()
5353 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5354 }
5355
5356 pub fn visible_excerpts(
5357 &self,
5358 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5359 cx: &mut Context<Editor>,
5360 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5361 let Some(project) = self.project() else {
5362 return HashMap::default();
5363 };
5364 let project = project.read(cx);
5365 let multi_buffer = self.buffer().read(cx);
5366 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5367 let multi_buffer_visible_start = self
5368 .scroll_manager
5369 .anchor()
5370 .anchor
5371 .to_point(&multi_buffer_snapshot);
5372 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5373 multi_buffer_visible_start
5374 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5375 Bias::Left,
5376 );
5377 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5378 multi_buffer_snapshot
5379 .range_to_buffer_ranges(multi_buffer_visible_range)
5380 .into_iter()
5381 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5382 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5383 let buffer_file = project::File::from_dyn(buffer.file())?;
5384 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5385 let worktree_entry = buffer_worktree
5386 .read(cx)
5387 .entry_for_id(buffer_file.project_entry_id()?)?;
5388 if worktree_entry.is_ignored {
5389 return None;
5390 }
5391
5392 let language = buffer.language()?;
5393 if let Some(restrict_to_languages) = restrict_to_languages
5394 && !restrict_to_languages.contains(language)
5395 {
5396 return None;
5397 }
5398 Some((
5399 excerpt_id,
5400 (
5401 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5402 buffer.version().clone(),
5403 excerpt_visible_range,
5404 ),
5405 ))
5406 })
5407 .collect()
5408 }
5409
5410 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5411 TextLayoutDetails {
5412 text_system: window.text_system().clone(),
5413 editor_style: self.style.clone().unwrap(),
5414 rem_size: window.rem_size(),
5415 scroll_anchor: self.scroll_manager.anchor(),
5416 visible_rows: self.visible_line_count(),
5417 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5418 }
5419 }
5420
5421 pub fn splice_inlays(
5422 &self,
5423 to_remove: &[InlayId],
5424 to_insert: Vec<Inlay>,
5425 cx: &mut Context<Self>,
5426 ) {
5427 self.display_map.update(cx, |display_map, cx| {
5428 display_map.splice_inlays(to_remove, to_insert, cx)
5429 });
5430 cx.notify();
5431 }
5432
5433 fn trigger_on_type_formatting(
5434 &self,
5435 input: String,
5436 window: &mut Window,
5437 cx: &mut Context<Self>,
5438 ) -> Option<Task<Result<()>>> {
5439 if input.len() != 1 {
5440 return None;
5441 }
5442
5443 let project = self.project()?;
5444 let position = self.selections.newest_anchor().head();
5445 let (buffer, buffer_position) = self
5446 .buffer
5447 .read(cx)
5448 .text_anchor_for_position(position, cx)?;
5449
5450 let settings = language_settings::language_settings(
5451 buffer
5452 .read(cx)
5453 .language_at(buffer_position)
5454 .map(|l| l.name()),
5455 buffer.read(cx).file(),
5456 cx,
5457 );
5458 if !settings.use_on_type_format {
5459 return None;
5460 }
5461
5462 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5463 // hence we do LSP request & edit on host side only — add formats to host's history.
5464 let push_to_lsp_host_history = true;
5465 // If this is not the host, append its history with new edits.
5466 let push_to_client_history = project.read(cx).is_via_collab();
5467
5468 let on_type_formatting = project.update(cx, |project, cx| {
5469 project.on_type_format(
5470 buffer.clone(),
5471 buffer_position,
5472 input,
5473 push_to_lsp_host_history,
5474 cx,
5475 )
5476 });
5477 Some(cx.spawn_in(window, async move |editor, cx| {
5478 if let Some(transaction) = on_type_formatting.await? {
5479 if push_to_client_history {
5480 buffer
5481 .update(cx, |buffer, _| {
5482 buffer.push_transaction(transaction, Instant::now());
5483 buffer.finalize_last_transaction();
5484 })
5485 .ok();
5486 }
5487 editor.update(cx, |editor, cx| {
5488 editor.refresh_document_highlights(cx);
5489 })?;
5490 }
5491 Ok(())
5492 }))
5493 }
5494
5495 pub fn show_word_completions(
5496 &mut self,
5497 _: &ShowWordCompletions,
5498 window: &mut Window,
5499 cx: &mut Context<Self>,
5500 ) {
5501 self.open_or_update_completions_menu(
5502 Some(CompletionsMenuSource::Words {
5503 ignore_threshold: true,
5504 }),
5505 None,
5506 window,
5507 cx,
5508 );
5509 }
5510
5511 pub fn show_completions(
5512 &mut self,
5513 options: &ShowCompletions,
5514 window: &mut Window,
5515 cx: &mut Context<Self>,
5516 ) {
5517 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5518 }
5519
5520 fn open_or_update_completions_menu(
5521 &mut self,
5522 requested_source: Option<CompletionsMenuSource>,
5523 trigger: Option<&str>,
5524 window: &mut Window,
5525 cx: &mut Context<Self>,
5526 ) {
5527 if self.pending_rename.is_some() {
5528 return;
5529 }
5530
5531 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5532
5533 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5534 // inserted and selected. To handle that case, the start of the selection is used so that
5535 // the menu starts with all choices.
5536 let position = self
5537 .selections
5538 .newest_anchor()
5539 .start
5540 .bias_right(&multibuffer_snapshot);
5541 if position.diff_base_anchor.is_some() {
5542 return;
5543 }
5544 let buffer_position = multibuffer_snapshot.anchor_before(position);
5545 let Some(buffer) = buffer_position
5546 .buffer_id
5547 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5548 else {
5549 return;
5550 };
5551 let buffer_snapshot = buffer.read(cx).snapshot();
5552
5553 let query: Option<Arc<String>> =
5554 Self::completion_query(&multibuffer_snapshot, buffer_position)
5555 .map(|query| query.into());
5556
5557 drop(multibuffer_snapshot);
5558
5559 // Hide the current completions menu when query is empty. Without this, cached
5560 // completions from before the trigger char may be reused (#32774).
5561 if query.is_none() {
5562 let menu_is_open = matches!(
5563 self.context_menu.borrow().as_ref(),
5564 Some(CodeContextMenu::Completions(_))
5565 );
5566 if menu_is_open {
5567 self.hide_context_menu(window, cx);
5568 }
5569 }
5570
5571 let mut ignore_word_threshold = false;
5572 let provider = match requested_source {
5573 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5574 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5575 ignore_word_threshold = ignore_threshold;
5576 None
5577 }
5578 Some(CompletionsMenuSource::SnippetChoices) => {
5579 log::error!("bug: SnippetChoices requested_source is not handled");
5580 None
5581 }
5582 };
5583
5584 let sort_completions = provider
5585 .as_ref()
5586 .is_some_and(|provider| provider.sort_completions());
5587
5588 let filter_completions = provider
5589 .as_ref()
5590 .is_none_or(|provider| provider.filter_completions());
5591
5592 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5593 if filter_completions {
5594 menu.filter(query.clone(), provider.clone(), window, cx);
5595 }
5596 // When `is_incomplete` is false, no need to re-query completions when the current query
5597 // is a suffix of the initial query.
5598 if !menu.is_incomplete {
5599 // If the new query is a suffix of the old query (typing more characters) and
5600 // the previous result was complete, the existing completions can be filtered.
5601 //
5602 // Note that this is always true for snippet completions.
5603 let query_matches = match (&menu.initial_query, &query) {
5604 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5605 (None, _) => true,
5606 _ => false,
5607 };
5608 if query_matches {
5609 let position_matches = if menu.initial_position == position {
5610 true
5611 } else {
5612 let snapshot = self.buffer.read(cx).read(cx);
5613 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5614 };
5615 if position_matches {
5616 return;
5617 }
5618 }
5619 }
5620 };
5621
5622 let trigger_kind = match trigger {
5623 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5624 CompletionTriggerKind::TRIGGER_CHARACTER
5625 }
5626 _ => CompletionTriggerKind::INVOKED,
5627 };
5628 let completion_context = CompletionContext {
5629 trigger_character: trigger.and_then(|trigger| {
5630 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5631 Some(String::from(trigger))
5632 } else {
5633 None
5634 }
5635 }),
5636 trigger_kind,
5637 };
5638
5639 let Anchor {
5640 excerpt_id: buffer_excerpt_id,
5641 text_anchor: buffer_position,
5642 ..
5643 } = buffer_position;
5644
5645 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5646 buffer_snapshot.surrounding_word(buffer_position, None)
5647 {
5648 let word_to_exclude = buffer_snapshot
5649 .text_for_range(word_range.clone())
5650 .collect::<String>();
5651 (
5652 buffer_snapshot.anchor_before(word_range.start)
5653 ..buffer_snapshot.anchor_after(buffer_position),
5654 Some(word_to_exclude),
5655 )
5656 } else {
5657 (buffer_position..buffer_position, None)
5658 };
5659
5660 let language = buffer_snapshot
5661 .language_at(buffer_position)
5662 .map(|language| language.name());
5663
5664 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5665 .completions
5666 .clone();
5667
5668 let show_completion_documentation = buffer_snapshot
5669 .settings_at(buffer_position, cx)
5670 .show_completion_documentation;
5671
5672 // The document can be large, so stay in reasonable bounds when searching for words,
5673 // otherwise completion pop-up might be slow to appear.
5674 const WORD_LOOKUP_ROWS: u32 = 5_000;
5675 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5676 let min_word_search = buffer_snapshot.clip_point(
5677 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5678 Bias::Left,
5679 );
5680 let max_word_search = buffer_snapshot.clip_point(
5681 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5682 Bias::Right,
5683 );
5684 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5685 ..buffer_snapshot.point_to_offset(max_word_search);
5686
5687 let skip_digits = query
5688 .as_ref()
5689 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5690
5691 let omit_word_completions = !self.word_completions_enabled
5692 || (!ignore_word_threshold
5693 && match &query {
5694 Some(query) => query.chars().count() < completion_settings.words_min_length,
5695 None => completion_settings.words_min_length != 0,
5696 });
5697
5698 let (mut words, provider_responses) = match &provider {
5699 Some(provider) => {
5700 let provider_responses = provider.completions(
5701 buffer_excerpt_id,
5702 &buffer,
5703 buffer_position,
5704 completion_context,
5705 window,
5706 cx,
5707 );
5708
5709 let words = match (omit_word_completions, completion_settings.words) {
5710 (true, _) | (_, WordsCompletionMode::Disabled) => {
5711 Task::ready(BTreeMap::default())
5712 }
5713 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5714 .background_spawn(async move {
5715 buffer_snapshot.words_in_range(WordsQuery {
5716 fuzzy_contents: None,
5717 range: word_search_range,
5718 skip_digits,
5719 })
5720 }),
5721 };
5722
5723 (words, provider_responses)
5724 }
5725 None => {
5726 let words = if omit_word_completions {
5727 Task::ready(BTreeMap::default())
5728 } else {
5729 cx.background_spawn(async move {
5730 buffer_snapshot.words_in_range(WordsQuery {
5731 fuzzy_contents: None,
5732 range: word_search_range,
5733 skip_digits,
5734 })
5735 })
5736 };
5737 (words, Task::ready(Ok(Vec::new())))
5738 }
5739 };
5740
5741 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5742
5743 let id = post_inc(&mut self.next_completion_id);
5744 let task = cx.spawn_in(window, async move |editor, cx| {
5745 let Ok(()) = editor.update(cx, |this, _| {
5746 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5747 }) else {
5748 return;
5749 };
5750
5751 // TODO: Ideally completions from different sources would be selectively re-queried, so
5752 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5753 let mut completions = Vec::new();
5754 let mut is_incomplete = false;
5755 let mut display_options: Option<CompletionDisplayOptions> = None;
5756 if let Some(provider_responses) = provider_responses.await.log_err()
5757 && !provider_responses.is_empty()
5758 {
5759 for response in provider_responses {
5760 completions.extend(response.completions);
5761 is_incomplete = is_incomplete || response.is_incomplete;
5762 match display_options.as_mut() {
5763 None => {
5764 display_options = Some(response.display_options);
5765 }
5766 Some(options) => options.merge(&response.display_options),
5767 }
5768 }
5769 if completion_settings.words == WordsCompletionMode::Fallback {
5770 words = Task::ready(BTreeMap::default());
5771 }
5772 }
5773 let display_options = display_options.unwrap_or_default();
5774
5775 let mut words = words.await;
5776 if let Some(word_to_exclude) = &word_to_exclude {
5777 words.remove(word_to_exclude);
5778 }
5779 for lsp_completion in &completions {
5780 words.remove(&lsp_completion.new_text);
5781 }
5782 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5783 replace_range: word_replace_range.clone(),
5784 new_text: word.clone(),
5785 label: CodeLabel::plain(word, None),
5786 icon_path: None,
5787 documentation: None,
5788 source: CompletionSource::BufferWord {
5789 word_range,
5790 resolved: false,
5791 },
5792 insert_text_mode: Some(InsertTextMode::AS_IS),
5793 confirm: None,
5794 }));
5795
5796 let menu = if completions.is_empty() {
5797 None
5798 } else {
5799 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5800 let languages = editor
5801 .workspace
5802 .as_ref()
5803 .and_then(|(workspace, _)| workspace.upgrade())
5804 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5805 let menu = CompletionsMenu::new(
5806 id,
5807 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5808 sort_completions,
5809 show_completion_documentation,
5810 position,
5811 query.clone(),
5812 is_incomplete,
5813 buffer.clone(),
5814 completions.into(),
5815 display_options,
5816 snippet_sort_order,
5817 languages,
5818 language,
5819 cx,
5820 );
5821
5822 let query = if filter_completions { query } else { None };
5823 let matches_task = if let Some(query) = query {
5824 menu.do_async_filtering(query, cx)
5825 } else {
5826 Task::ready(menu.unfiltered_matches())
5827 };
5828 (menu, matches_task)
5829 }) else {
5830 return;
5831 };
5832
5833 let matches = matches_task.await;
5834
5835 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5836 // Newer menu already set, so exit.
5837 if let Some(CodeContextMenu::Completions(prev_menu)) =
5838 editor.context_menu.borrow().as_ref()
5839 && prev_menu.id > id
5840 {
5841 return;
5842 };
5843
5844 // Only valid to take prev_menu because it the new menu is immediately set
5845 // below, or the menu is hidden.
5846 if let Some(CodeContextMenu::Completions(prev_menu)) =
5847 editor.context_menu.borrow_mut().take()
5848 {
5849 let position_matches =
5850 if prev_menu.initial_position == menu.initial_position {
5851 true
5852 } else {
5853 let snapshot = editor.buffer.read(cx).read(cx);
5854 prev_menu.initial_position.to_offset(&snapshot)
5855 == menu.initial_position.to_offset(&snapshot)
5856 };
5857 if position_matches {
5858 // Preserve markdown cache before `set_filter_results` because it will
5859 // try to populate the documentation cache.
5860 menu.preserve_markdown_cache(prev_menu);
5861 }
5862 };
5863
5864 menu.set_filter_results(matches, provider, window, cx);
5865 }) else {
5866 return;
5867 };
5868
5869 menu.visible().then_some(menu)
5870 };
5871
5872 editor
5873 .update_in(cx, |editor, window, cx| {
5874 if editor.focus_handle.is_focused(window)
5875 && let Some(menu) = menu
5876 {
5877 *editor.context_menu.borrow_mut() =
5878 Some(CodeContextMenu::Completions(menu));
5879
5880 crate::hover_popover::hide_hover(editor, cx);
5881 if editor.show_edit_predictions_in_menu() {
5882 editor.update_visible_edit_prediction(window, cx);
5883 } else {
5884 editor.discard_edit_prediction(false, cx);
5885 }
5886
5887 cx.notify();
5888 return;
5889 }
5890
5891 if editor.completion_tasks.len() <= 1 {
5892 // If there are no more completion tasks and the last menu was empty, we should hide it.
5893 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5894 // If it was already hidden and we don't show edit predictions in the menu,
5895 // we should also show the edit prediction when available.
5896 if was_hidden && editor.show_edit_predictions_in_menu() {
5897 editor.update_visible_edit_prediction(window, cx);
5898 }
5899 }
5900 })
5901 .ok();
5902 });
5903
5904 self.completion_tasks.push((id, task));
5905 }
5906
5907 #[cfg(feature = "test-support")]
5908 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5909 let menu = self.context_menu.borrow();
5910 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5911 let completions = menu.completions.borrow();
5912 Some(completions.to_vec())
5913 } else {
5914 None
5915 }
5916 }
5917
5918 pub fn with_completions_menu_matching_id<R>(
5919 &self,
5920 id: CompletionId,
5921 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5922 ) -> R {
5923 let mut context_menu = self.context_menu.borrow_mut();
5924 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5925 return f(None);
5926 };
5927 if completions_menu.id != id {
5928 return f(None);
5929 }
5930 f(Some(completions_menu))
5931 }
5932
5933 pub fn confirm_completion(
5934 &mut self,
5935 action: &ConfirmCompletion,
5936 window: &mut Window,
5937 cx: &mut Context<Self>,
5938 ) -> Option<Task<Result<()>>> {
5939 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5940 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5941 }
5942
5943 pub fn confirm_completion_insert(
5944 &mut self,
5945 _: &ConfirmCompletionInsert,
5946 window: &mut Window,
5947 cx: &mut Context<Self>,
5948 ) -> Option<Task<Result<()>>> {
5949 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5950 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5951 }
5952
5953 pub fn confirm_completion_replace(
5954 &mut self,
5955 _: &ConfirmCompletionReplace,
5956 window: &mut Window,
5957 cx: &mut Context<Self>,
5958 ) -> Option<Task<Result<()>>> {
5959 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5960 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5961 }
5962
5963 pub fn compose_completion(
5964 &mut self,
5965 action: &ComposeCompletion,
5966 window: &mut Window,
5967 cx: &mut Context<Self>,
5968 ) -> Option<Task<Result<()>>> {
5969 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5970 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5971 }
5972
5973 fn do_completion(
5974 &mut self,
5975 item_ix: Option<usize>,
5976 intent: CompletionIntent,
5977 window: &mut Window,
5978 cx: &mut Context<Editor>,
5979 ) -> Option<Task<Result<()>>> {
5980 use language::ToOffset as _;
5981
5982 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5983 else {
5984 return None;
5985 };
5986
5987 let candidate_id = {
5988 let entries = completions_menu.entries.borrow();
5989 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5990 if self.show_edit_predictions_in_menu() {
5991 self.discard_edit_prediction(true, cx);
5992 }
5993 mat.candidate_id
5994 };
5995
5996 let completion = completions_menu
5997 .completions
5998 .borrow()
5999 .get(candidate_id)?
6000 .clone();
6001 cx.stop_propagation();
6002
6003 let buffer_handle = completions_menu.buffer.clone();
6004
6005 let CompletionEdit {
6006 new_text,
6007 snippet,
6008 replace_range,
6009 } = process_completion_for_edit(
6010 &completion,
6011 intent,
6012 &buffer_handle,
6013 &completions_menu.initial_position.text_anchor,
6014 cx,
6015 );
6016
6017 let buffer = buffer_handle.read(cx);
6018 let snapshot = self.buffer.read(cx).snapshot(cx);
6019 let newest_anchor = self.selections.newest_anchor();
6020 let replace_range_multibuffer = {
6021 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6022 excerpt.map_range_from_buffer(replace_range.clone())
6023 };
6024 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6025 return None;
6026 }
6027
6028 let old_text = buffer
6029 .text_for_range(replace_range.clone())
6030 .collect::<String>();
6031 let lookbehind = newest_anchor
6032 .start
6033 .text_anchor
6034 .to_offset(buffer)
6035 .saturating_sub(replace_range.start);
6036 let lookahead = replace_range
6037 .end
6038 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6039 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6040 let suffix = &old_text[lookbehind.min(old_text.len())..];
6041
6042 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
6043 let mut ranges = Vec::new();
6044 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6045
6046 for selection in &selections {
6047 let range = if selection.id == newest_anchor.id {
6048 replace_range_multibuffer.clone()
6049 } else {
6050 let mut range = selection.range();
6051
6052 // if prefix is present, don't duplicate it
6053 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
6054 range.start = range.start.saturating_sub(lookbehind);
6055
6056 // if suffix is also present, mimic the newest cursor and replace it
6057 if selection.id != newest_anchor.id
6058 && snapshot.contains_str_at(range.end, suffix)
6059 {
6060 range.end += lookahead;
6061 }
6062 }
6063 range
6064 };
6065
6066 ranges.push(range.clone());
6067
6068 if !self.linked_edit_ranges.is_empty() {
6069 let start_anchor = snapshot.anchor_before(range.start);
6070 let end_anchor = snapshot.anchor_after(range.end);
6071 if let Some(ranges) = self
6072 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6073 {
6074 for (buffer, edits) in ranges {
6075 linked_edits
6076 .entry(buffer.clone())
6077 .or_default()
6078 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6079 }
6080 }
6081 }
6082 }
6083
6084 let common_prefix_len = old_text
6085 .chars()
6086 .zip(new_text.chars())
6087 .take_while(|(a, b)| a == b)
6088 .map(|(a, _)| a.len_utf8())
6089 .sum::<usize>();
6090
6091 cx.emit(EditorEvent::InputHandled {
6092 utf16_range_to_replace: None,
6093 text: new_text[common_prefix_len..].into(),
6094 });
6095
6096 self.transact(window, cx, |editor, window, cx| {
6097 if let Some(mut snippet) = snippet {
6098 snippet.text = new_text.to_string();
6099 editor
6100 .insert_snippet(&ranges, snippet, window, cx)
6101 .log_err();
6102 } else {
6103 editor.buffer.update(cx, |multi_buffer, cx| {
6104 let auto_indent = match completion.insert_text_mode {
6105 Some(InsertTextMode::AS_IS) => None,
6106 _ => editor.autoindent_mode.clone(),
6107 };
6108 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6109 multi_buffer.edit(edits, auto_indent, cx);
6110 });
6111 }
6112 for (buffer, edits) in linked_edits {
6113 buffer.update(cx, |buffer, cx| {
6114 let snapshot = buffer.snapshot();
6115 let edits = edits
6116 .into_iter()
6117 .map(|(range, text)| {
6118 use text::ToPoint as TP;
6119 let end_point = TP::to_point(&range.end, &snapshot);
6120 let start_point = TP::to_point(&range.start, &snapshot);
6121 (start_point..end_point, text)
6122 })
6123 .sorted_by_key(|(range, _)| range.start);
6124 buffer.edit(edits, None, cx);
6125 })
6126 }
6127
6128 editor.refresh_edit_prediction(true, false, window, cx);
6129 });
6130 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6131
6132 let show_new_completions_on_confirm = completion
6133 .confirm
6134 .as_ref()
6135 .is_some_and(|confirm| confirm(intent, window, cx));
6136 if show_new_completions_on_confirm {
6137 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6138 }
6139
6140 let provider = self.completion_provider.as_ref()?;
6141 drop(completion);
6142 let apply_edits = provider.apply_additional_edits_for_completion(
6143 buffer_handle,
6144 completions_menu.completions.clone(),
6145 candidate_id,
6146 true,
6147 cx,
6148 );
6149
6150 let editor_settings = EditorSettings::get_global(cx);
6151 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6152 // After the code completion is finished, users often want to know what signatures are needed.
6153 // so we should automatically call signature_help
6154 self.show_signature_help(&ShowSignatureHelp, window, cx);
6155 }
6156
6157 Some(cx.foreground_executor().spawn(async move {
6158 apply_edits.await?;
6159 Ok(())
6160 }))
6161 }
6162
6163 pub fn toggle_code_actions(
6164 &mut self,
6165 action: &ToggleCodeActions,
6166 window: &mut Window,
6167 cx: &mut Context<Self>,
6168 ) {
6169 let quick_launch = action.quick_launch;
6170 let mut context_menu = self.context_menu.borrow_mut();
6171 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6172 if code_actions.deployed_from == action.deployed_from {
6173 // Toggle if we're selecting the same one
6174 *context_menu = None;
6175 cx.notify();
6176 return;
6177 } else {
6178 // Otherwise, clear it and start a new one
6179 *context_menu = None;
6180 cx.notify();
6181 }
6182 }
6183 drop(context_menu);
6184 let snapshot = self.snapshot(window, cx);
6185 let deployed_from = action.deployed_from.clone();
6186 let action = action.clone();
6187 self.completion_tasks.clear();
6188 self.discard_edit_prediction(false, cx);
6189
6190 let multibuffer_point = match &action.deployed_from {
6191 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6192 DisplayPoint::new(*row, 0).to_point(&snapshot)
6193 }
6194 _ => self
6195 .selections
6196 .newest::<Point>(&snapshot.display_snapshot)
6197 .head(),
6198 };
6199 let Some((buffer, buffer_row)) = snapshot
6200 .buffer_snapshot()
6201 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6202 .and_then(|(buffer_snapshot, range)| {
6203 self.buffer()
6204 .read(cx)
6205 .buffer(buffer_snapshot.remote_id())
6206 .map(|buffer| (buffer, range.start.row))
6207 })
6208 else {
6209 return;
6210 };
6211 let buffer_id = buffer.read(cx).remote_id();
6212 let tasks = self
6213 .tasks
6214 .get(&(buffer_id, buffer_row))
6215 .map(|t| Arc::new(t.to_owned()));
6216
6217 if !self.focus_handle.is_focused(window) {
6218 return;
6219 }
6220 let project = self.project.clone();
6221
6222 let code_actions_task = match deployed_from {
6223 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6224 _ => self.code_actions(buffer_row, window, cx),
6225 };
6226
6227 let runnable_task = match deployed_from {
6228 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6229 _ => {
6230 let mut task_context_task = Task::ready(None);
6231 if let Some(tasks) = &tasks
6232 && let Some(project) = project
6233 {
6234 task_context_task =
6235 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6236 }
6237
6238 cx.spawn_in(window, {
6239 let buffer = buffer.clone();
6240 async move |editor, cx| {
6241 let task_context = task_context_task.await;
6242
6243 let resolved_tasks =
6244 tasks
6245 .zip(task_context.clone())
6246 .map(|(tasks, task_context)| ResolvedTasks {
6247 templates: tasks.resolve(&task_context).collect(),
6248 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6249 multibuffer_point.row,
6250 tasks.column,
6251 )),
6252 });
6253 let debug_scenarios = editor
6254 .update(cx, |editor, cx| {
6255 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6256 })?
6257 .await;
6258 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6259 }
6260 })
6261 }
6262 };
6263
6264 cx.spawn_in(window, async move |editor, cx| {
6265 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6266 let code_actions = code_actions_task.await;
6267 let spawn_straight_away = quick_launch
6268 && resolved_tasks
6269 .as_ref()
6270 .is_some_and(|tasks| tasks.templates.len() == 1)
6271 && code_actions
6272 .as_ref()
6273 .is_none_or(|actions| actions.is_empty())
6274 && debug_scenarios.is_empty();
6275
6276 editor.update_in(cx, |editor, window, cx| {
6277 crate::hover_popover::hide_hover(editor, cx);
6278 let actions = CodeActionContents::new(
6279 resolved_tasks,
6280 code_actions,
6281 debug_scenarios,
6282 task_context.unwrap_or_default(),
6283 );
6284
6285 // Don't show the menu if there are no actions available
6286 if actions.is_empty() {
6287 cx.notify();
6288 return Task::ready(Ok(()));
6289 }
6290
6291 *editor.context_menu.borrow_mut() =
6292 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6293 buffer,
6294 actions,
6295 selected_item: Default::default(),
6296 scroll_handle: UniformListScrollHandle::default(),
6297 deployed_from,
6298 }));
6299 cx.notify();
6300 if spawn_straight_away
6301 && let Some(task) = editor.confirm_code_action(
6302 &ConfirmCodeAction { item_ix: Some(0) },
6303 window,
6304 cx,
6305 )
6306 {
6307 return task;
6308 }
6309
6310 Task::ready(Ok(()))
6311 })
6312 })
6313 .detach_and_log_err(cx);
6314 }
6315
6316 fn debug_scenarios(
6317 &mut self,
6318 resolved_tasks: &Option<ResolvedTasks>,
6319 buffer: &Entity<Buffer>,
6320 cx: &mut App,
6321 ) -> Task<Vec<task::DebugScenario>> {
6322 maybe!({
6323 let project = self.project()?;
6324 let dap_store = project.read(cx).dap_store();
6325 let mut scenarios = vec![];
6326 let resolved_tasks = resolved_tasks.as_ref()?;
6327 let buffer = buffer.read(cx);
6328 let language = buffer.language()?;
6329 let file = buffer.file();
6330 let debug_adapter = language_settings(language.name().into(), file, cx)
6331 .debuggers
6332 .first()
6333 .map(SharedString::from)
6334 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6335
6336 dap_store.update(cx, |dap_store, cx| {
6337 for (_, task) in &resolved_tasks.templates {
6338 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6339 task.original_task().clone(),
6340 debug_adapter.clone().into(),
6341 task.display_label().to_owned().into(),
6342 cx,
6343 );
6344 scenarios.push(maybe_scenario);
6345 }
6346 });
6347 Some(cx.background_spawn(async move {
6348 futures::future::join_all(scenarios)
6349 .await
6350 .into_iter()
6351 .flatten()
6352 .collect::<Vec<_>>()
6353 }))
6354 })
6355 .unwrap_or_else(|| Task::ready(vec![]))
6356 }
6357
6358 fn code_actions(
6359 &mut self,
6360 buffer_row: u32,
6361 window: &mut Window,
6362 cx: &mut Context<Self>,
6363 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6364 let mut task = self.code_actions_task.take();
6365 cx.spawn_in(window, async move |editor, cx| {
6366 while let Some(prev_task) = task {
6367 prev_task.await.log_err();
6368 task = editor
6369 .update(cx, |this, _| this.code_actions_task.take())
6370 .ok()?;
6371 }
6372
6373 editor
6374 .update(cx, |editor, cx| {
6375 editor
6376 .available_code_actions
6377 .clone()
6378 .and_then(|(location, code_actions)| {
6379 let snapshot = location.buffer.read(cx).snapshot();
6380 let point_range = location.range.to_point(&snapshot);
6381 let point_range = point_range.start.row..=point_range.end.row;
6382 if point_range.contains(&buffer_row) {
6383 Some(code_actions)
6384 } else {
6385 None
6386 }
6387 })
6388 })
6389 .ok()
6390 .flatten()
6391 })
6392 }
6393
6394 pub fn confirm_code_action(
6395 &mut self,
6396 action: &ConfirmCodeAction,
6397 window: &mut Window,
6398 cx: &mut Context<Self>,
6399 ) -> Option<Task<Result<()>>> {
6400 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6401
6402 let actions_menu =
6403 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6404 menu
6405 } else {
6406 return None;
6407 };
6408
6409 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6410 let action = actions_menu.actions.get(action_ix)?;
6411 let title = action.label();
6412 let buffer = actions_menu.buffer;
6413 let workspace = self.workspace()?;
6414
6415 match action {
6416 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6417 workspace.update(cx, |workspace, cx| {
6418 workspace.schedule_resolved_task(
6419 task_source_kind,
6420 resolved_task,
6421 false,
6422 window,
6423 cx,
6424 );
6425
6426 Some(Task::ready(Ok(())))
6427 })
6428 }
6429 CodeActionsItem::CodeAction {
6430 excerpt_id,
6431 action,
6432 provider,
6433 } => {
6434 let apply_code_action =
6435 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6436 let workspace = workspace.downgrade();
6437 Some(cx.spawn_in(window, async move |editor, cx| {
6438 let project_transaction = apply_code_action.await?;
6439 Self::open_project_transaction(
6440 &editor,
6441 workspace,
6442 project_transaction,
6443 title,
6444 cx,
6445 )
6446 .await
6447 }))
6448 }
6449 CodeActionsItem::DebugScenario(scenario) => {
6450 let context = actions_menu.actions.context;
6451
6452 workspace.update(cx, |workspace, cx| {
6453 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6454 workspace.start_debug_session(
6455 scenario,
6456 context,
6457 Some(buffer),
6458 None,
6459 window,
6460 cx,
6461 );
6462 });
6463 Some(Task::ready(Ok(())))
6464 }
6465 }
6466 }
6467
6468 pub async fn open_project_transaction(
6469 editor: &WeakEntity<Editor>,
6470 workspace: WeakEntity<Workspace>,
6471 transaction: ProjectTransaction,
6472 title: String,
6473 cx: &mut AsyncWindowContext,
6474 ) -> Result<()> {
6475 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6476 cx.update(|_, cx| {
6477 entries.sort_unstable_by_key(|(buffer, _)| {
6478 buffer.read(cx).file().map(|f| f.path().clone())
6479 });
6480 })?;
6481 if entries.is_empty() {
6482 return Ok(());
6483 }
6484
6485 // If the project transaction's edits are all contained within this editor, then
6486 // avoid opening a new editor to display them.
6487
6488 if let [(buffer, transaction)] = &*entries {
6489 let excerpt = editor.update(cx, |editor, cx| {
6490 editor
6491 .buffer()
6492 .read(cx)
6493 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6494 })?;
6495 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6496 && excerpted_buffer == *buffer
6497 {
6498 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6499 let excerpt_range = excerpt_range.to_offset(buffer);
6500 buffer
6501 .edited_ranges_for_transaction::<usize>(transaction)
6502 .all(|range| {
6503 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6504 })
6505 })?;
6506
6507 if all_edits_within_excerpt {
6508 return Ok(());
6509 }
6510 }
6511 }
6512
6513 let mut ranges_to_highlight = Vec::new();
6514 let excerpt_buffer = cx.new(|cx| {
6515 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6516 for (buffer_handle, transaction) in &entries {
6517 let edited_ranges = buffer_handle
6518 .read(cx)
6519 .edited_ranges_for_transaction::<Point>(transaction)
6520 .collect::<Vec<_>>();
6521 let (ranges, _) = multibuffer.set_excerpts_for_path(
6522 PathKey::for_buffer(buffer_handle, cx),
6523 buffer_handle.clone(),
6524 edited_ranges,
6525 multibuffer_context_lines(cx),
6526 cx,
6527 );
6528
6529 ranges_to_highlight.extend(ranges);
6530 }
6531 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6532 multibuffer
6533 })?;
6534
6535 workspace.update_in(cx, |workspace, window, cx| {
6536 let project = workspace.project().clone();
6537 let editor =
6538 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6539 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6540 editor.update(cx, |editor, cx| {
6541 editor.highlight_background::<Self>(
6542 &ranges_to_highlight,
6543 |theme| theme.colors().editor_highlighted_line_background,
6544 cx,
6545 );
6546 });
6547 })?;
6548
6549 Ok(())
6550 }
6551
6552 pub fn clear_code_action_providers(&mut self) {
6553 self.code_action_providers.clear();
6554 self.available_code_actions.take();
6555 }
6556
6557 pub fn add_code_action_provider(
6558 &mut self,
6559 provider: Rc<dyn CodeActionProvider>,
6560 window: &mut Window,
6561 cx: &mut Context<Self>,
6562 ) {
6563 if self
6564 .code_action_providers
6565 .iter()
6566 .any(|existing_provider| existing_provider.id() == provider.id())
6567 {
6568 return;
6569 }
6570
6571 self.code_action_providers.push(provider);
6572 self.refresh_code_actions(window, cx);
6573 }
6574
6575 pub fn remove_code_action_provider(
6576 &mut self,
6577 id: Arc<str>,
6578 window: &mut Window,
6579 cx: &mut Context<Self>,
6580 ) {
6581 self.code_action_providers
6582 .retain(|provider| provider.id() != id);
6583 self.refresh_code_actions(window, cx);
6584 }
6585
6586 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6587 !self.code_action_providers.is_empty()
6588 && EditorSettings::get_global(cx).toolbar.code_actions
6589 }
6590
6591 pub fn has_available_code_actions(&self) -> bool {
6592 self.available_code_actions
6593 .as_ref()
6594 .is_some_and(|(_, actions)| !actions.is_empty())
6595 }
6596
6597 fn render_inline_code_actions(
6598 &self,
6599 icon_size: ui::IconSize,
6600 display_row: DisplayRow,
6601 is_active: bool,
6602 cx: &mut Context<Self>,
6603 ) -> AnyElement {
6604 let show_tooltip = !self.context_menu_visible();
6605 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6606 .icon_size(icon_size)
6607 .shape(ui::IconButtonShape::Square)
6608 .icon_color(ui::Color::Hidden)
6609 .toggle_state(is_active)
6610 .when(show_tooltip, |this| {
6611 this.tooltip({
6612 let focus_handle = self.focus_handle.clone();
6613 move |window, cx| {
6614 Tooltip::for_action_in(
6615 "Toggle Code Actions",
6616 &ToggleCodeActions {
6617 deployed_from: None,
6618 quick_launch: false,
6619 },
6620 &focus_handle,
6621 window,
6622 cx,
6623 )
6624 }
6625 })
6626 })
6627 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6628 window.focus(&editor.focus_handle(cx));
6629 editor.toggle_code_actions(
6630 &crate::actions::ToggleCodeActions {
6631 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6632 display_row,
6633 )),
6634 quick_launch: false,
6635 },
6636 window,
6637 cx,
6638 );
6639 }))
6640 .into_any_element()
6641 }
6642
6643 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6644 &self.context_menu
6645 }
6646
6647 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6648 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6649 cx.background_executor()
6650 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6651 .await;
6652
6653 let (start_buffer, start, _, end, newest_selection) = this
6654 .update(cx, |this, cx| {
6655 let newest_selection = this.selections.newest_anchor().clone();
6656 if newest_selection.head().diff_base_anchor.is_some() {
6657 return None;
6658 }
6659 let display_snapshot = this.display_snapshot(cx);
6660 let newest_selection_adjusted =
6661 this.selections.newest_adjusted(&display_snapshot);
6662 let buffer = this.buffer.read(cx);
6663
6664 let (start_buffer, start) =
6665 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6666 let (end_buffer, end) =
6667 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6668
6669 Some((start_buffer, start, end_buffer, end, newest_selection))
6670 })?
6671 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6672 .context(
6673 "Expected selection to lie in a single buffer when refreshing code actions",
6674 )?;
6675 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6676 let providers = this.code_action_providers.clone();
6677 let tasks = this
6678 .code_action_providers
6679 .iter()
6680 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6681 .collect::<Vec<_>>();
6682 (providers, tasks)
6683 })?;
6684
6685 let mut actions = Vec::new();
6686 for (provider, provider_actions) in
6687 providers.into_iter().zip(future::join_all(tasks).await)
6688 {
6689 if let Some(provider_actions) = provider_actions.log_err() {
6690 actions.extend(provider_actions.into_iter().map(|action| {
6691 AvailableCodeAction {
6692 excerpt_id: newest_selection.start.excerpt_id,
6693 action,
6694 provider: provider.clone(),
6695 }
6696 }));
6697 }
6698 }
6699
6700 this.update(cx, |this, cx| {
6701 this.available_code_actions = if actions.is_empty() {
6702 None
6703 } else {
6704 Some((
6705 Location {
6706 buffer: start_buffer,
6707 range: start..end,
6708 },
6709 actions.into(),
6710 ))
6711 };
6712 cx.notify();
6713 })
6714 }));
6715 }
6716
6717 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6718 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6719 self.show_git_blame_inline = false;
6720
6721 self.show_git_blame_inline_delay_task =
6722 Some(cx.spawn_in(window, async move |this, cx| {
6723 cx.background_executor().timer(delay).await;
6724
6725 this.update(cx, |this, cx| {
6726 this.show_git_blame_inline = true;
6727 cx.notify();
6728 })
6729 .log_err();
6730 }));
6731 }
6732 }
6733
6734 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6735 let snapshot = self.snapshot(window, cx);
6736 let cursor = self
6737 .selections
6738 .newest::<Point>(&snapshot.display_snapshot)
6739 .head();
6740 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6741 else {
6742 return;
6743 };
6744
6745 let Some(blame) = self.blame.as_ref() else {
6746 return;
6747 };
6748
6749 let row_info = RowInfo {
6750 buffer_id: Some(buffer.remote_id()),
6751 buffer_row: Some(point.row),
6752 ..Default::default()
6753 };
6754 let Some((buffer, blame_entry)) = blame
6755 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6756 .flatten()
6757 else {
6758 return;
6759 };
6760
6761 let anchor = self.selections.newest_anchor().head();
6762 let position = self.to_pixel_point(anchor, &snapshot, window);
6763 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6764 self.show_blame_popover(
6765 buffer,
6766 &blame_entry,
6767 position + last_bounds.origin,
6768 true,
6769 cx,
6770 );
6771 };
6772 }
6773
6774 fn show_blame_popover(
6775 &mut self,
6776 buffer: BufferId,
6777 blame_entry: &BlameEntry,
6778 position: gpui::Point<Pixels>,
6779 ignore_timeout: bool,
6780 cx: &mut Context<Self>,
6781 ) {
6782 if let Some(state) = &mut self.inline_blame_popover {
6783 state.hide_task.take();
6784 } else {
6785 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6786 let blame_entry = blame_entry.clone();
6787 let show_task = cx.spawn(async move |editor, cx| {
6788 if !ignore_timeout {
6789 cx.background_executor()
6790 .timer(std::time::Duration::from_millis(blame_popover_delay))
6791 .await;
6792 }
6793 editor
6794 .update(cx, |editor, cx| {
6795 editor.inline_blame_popover_show_task.take();
6796 let Some(blame) = editor.blame.as_ref() else {
6797 return;
6798 };
6799 let blame = blame.read(cx);
6800 let details = blame.details_for_entry(buffer, &blame_entry);
6801 let markdown = cx.new(|cx| {
6802 Markdown::new(
6803 details
6804 .as_ref()
6805 .map(|message| message.message.clone())
6806 .unwrap_or_default(),
6807 None,
6808 None,
6809 cx,
6810 )
6811 });
6812 editor.inline_blame_popover = Some(InlineBlamePopover {
6813 position,
6814 hide_task: None,
6815 popover_bounds: None,
6816 popover_state: InlineBlamePopoverState {
6817 scroll_handle: ScrollHandle::new(),
6818 commit_message: details,
6819 markdown,
6820 },
6821 keyboard_grace: ignore_timeout,
6822 });
6823 cx.notify();
6824 })
6825 .ok();
6826 });
6827 self.inline_blame_popover_show_task = Some(show_task);
6828 }
6829 }
6830
6831 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6832 self.inline_blame_popover_show_task.take();
6833 if let Some(state) = &mut self.inline_blame_popover {
6834 let hide_task = cx.spawn(async move |editor, cx| {
6835 cx.background_executor()
6836 .timer(std::time::Duration::from_millis(100))
6837 .await;
6838 editor
6839 .update(cx, |editor, cx| {
6840 editor.inline_blame_popover.take();
6841 cx.notify();
6842 })
6843 .ok();
6844 });
6845 state.hide_task = Some(hide_task);
6846 }
6847 }
6848
6849 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6850 if self.pending_rename.is_some() {
6851 return None;
6852 }
6853
6854 let provider = self.semantics_provider.clone()?;
6855 let buffer = self.buffer.read(cx);
6856 let newest_selection = self.selections.newest_anchor().clone();
6857 let cursor_position = newest_selection.head();
6858 let (cursor_buffer, cursor_buffer_position) =
6859 buffer.text_anchor_for_position(cursor_position, cx)?;
6860 let (tail_buffer, tail_buffer_position) =
6861 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6862 if cursor_buffer != tail_buffer {
6863 return None;
6864 }
6865
6866 let snapshot = cursor_buffer.read(cx).snapshot();
6867 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6868 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6869 if start_word_range != end_word_range {
6870 self.document_highlights_task.take();
6871 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6872 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6873 return None;
6874 }
6875
6876 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6877 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6878 cx.background_executor()
6879 .timer(Duration::from_millis(debounce))
6880 .await;
6881
6882 let highlights = if let Some(highlights) = cx
6883 .update(|cx| {
6884 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6885 })
6886 .ok()
6887 .flatten()
6888 {
6889 highlights.await.log_err()
6890 } else {
6891 None
6892 };
6893
6894 if let Some(highlights) = highlights {
6895 this.update(cx, |this, cx| {
6896 if this.pending_rename.is_some() {
6897 return;
6898 }
6899
6900 let buffer = this.buffer.read(cx);
6901 if buffer
6902 .text_anchor_for_position(cursor_position, cx)
6903 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6904 {
6905 return;
6906 }
6907
6908 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6909 let mut write_ranges = Vec::new();
6910 let mut read_ranges = Vec::new();
6911 for highlight in highlights {
6912 let buffer_id = cursor_buffer.read(cx).remote_id();
6913 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6914 {
6915 let start = highlight
6916 .range
6917 .start
6918 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6919 let end = highlight
6920 .range
6921 .end
6922 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6923 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6924 continue;
6925 }
6926
6927 let range =
6928 Anchor::range_in_buffer(excerpt_id, buffer_id, *start..*end);
6929 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6930 write_ranges.push(range);
6931 } else {
6932 read_ranges.push(range);
6933 }
6934 }
6935 }
6936
6937 this.highlight_background::<DocumentHighlightRead>(
6938 &read_ranges,
6939 |theme| theme.colors().editor_document_highlight_read_background,
6940 cx,
6941 );
6942 this.highlight_background::<DocumentHighlightWrite>(
6943 &write_ranges,
6944 |theme| theme.colors().editor_document_highlight_write_background,
6945 cx,
6946 );
6947 cx.notify();
6948 })
6949 .log_err();
6950 }
6951 }));
6952 None
6953 }
6954
6955 fn prepare_highlight_query_from_selection(
6956 &mut self,
6957 cx: &mut Context<Editor>,
6958 ) -> Option<(String, Range<Anchor>)> {
6959 if matches!(self.mode, EditorMode::SingleLine) {
6960 return None;
6961 }
6962 if !EditorSettings::get_global(cx).selection_highlight {
6963 return None;
6964 }
6965 if self.selections.count() != 1 || self.selections.line_mode() {
6966 return None;
6967 }
6968 let selection = self.selections.newest_anchor();
6969 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6970 let selection_point_range = selection.start.to_point(&multi_buffer_snapshot)
6971 ..selection.end.to_point(&multi_buffer_snapshot);
6972 // If the selection spans multiple rows OR it is empty
6973 if selection_point_range.start.row != selection_point_range.end.row
6974 || selection_point_range.start.column == selection_point_range.end.column
6975 {
6976 return None;
6977 }
6978
6979 let query = multi_buffer_snapshot
6980 .text_for_range(selection.range())
6981 .collect::<String>();
6982 if query.trim().is_empty() {
6983 return None;
6984 }
6985 Some((query, selection.range()))
6986 }
6987
6988 fn update_selection_occurrence_highlights(
6989 &mut self,
6990 query_text: String,
6991 query_range: Range<Anchor>,
6992 multi_buffer_range_to_query: Range<Point>,
6993 use_debounce: bool,
6994 window: &mut Window,
6995 cx: &mut Context<Editor>,
6996 ) -> Task<()> {
6997 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6998 cx.spawn_in(window, async move |editor, cx| {
6999 if use_debounce {
7000 cx.background_executor()
7001 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7002 .await;
7003 }
7004 let match_task = cx.background_spawn(async move {
7005 let buffer_ranges = multi_buffer_snapshot
7006 .range_to_buffer_ranges(multi_buffer_range_to_query)
7007 .into_iter()
7008 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7009 let mut match_ranges = Vec::new();
7010 let Ok(regex) = project::search::SearchQuery::text(
7011 query_text.clone(),
7012 false,
7013 false,
7014 false,
7015 Default::default(),
7016 Default::default(),
7017 false,
7018 None,
7019 ) else {
7020 return Vec::default();
7021 };
7022 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7023 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7024 match_ranges.extend(
7025 regex
7026 .search(buffer_snapshot, Some(search_range.clone()))
7027 .await
7028 .into_iter()
7029 .filter_map(|match_range| {
7030 let match_start = buffer_snapshot
7031 .anchor_after(search_range.start + match_range.start);
7032 let match_end = buffer_snapshot
7033 .anchor_before(search_range.start + match_range.end);
7034 let match_anchor_range = Anchor::range_in_buffer(
7035 excerpt_id,
7036 buffer_snapshot.remote_id(),
7037 match_start..match_end,
7038 );
7039 (match_anchor_range != query_range).then_some(match_anchor_range)
7040 }),
7041 );
7042 }
7043 match_ranges
7044 });
7045 let match_ranges = match_task.await;
7046 editor
7047 .update_in(cx, |editor, _, cx| {
7048 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7049 if !match_ranges.is_empty() {
7050 editor.highlight_background::<SelectedTextHighlight>(
7051 &match_ranges,
7052 |theme| theme.colors().editor_document_highlight_bracket_background,
7053 cx,
7054 )
7055 }
7056 })
7057 .log_err();
7058 })
7059 }
7060
7061 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7062 struct NewlineFold;
7063 let type_id = std::any::TypeId::of::<NewlineFold>();
7064 if !self.mode.is_single_line() {
7065 return;
7066 }
7067 let snapshot = self.snapshot(window, cx);
7068 if snapshot.buffer_snapshot().max_point().row == 0 {
7069 return;
7070 }
7071 let task = cx.background_spawn(async move {
7072 let new_newlines = snapshot
7073 .buffer_chars_at(0)
7074 .filter_map(|(c, i)| {
7075 if c == '\n' {
7076 Some(
7077 snapshot.buffer_snapshot().anchor_after(i)
7078 ..snapshot.buffer_snapshot().anchor_before(i + 1),
7079 )
7080 } else {
7081 None
7082 }
7083 })
7084 .collect::<Vec<_>>();
7085 let existing_newlines = snapshot
7086 .folds_in_range(0..snapshot.buffer_snapshot().len())
7087 .filter_map(|fold| {
7088 if fold.placeholder.type_tag == Some(type_id) {
7089 Some(fold.range.start..fold.range.end)
7090 } else {
7091 None
7092 }
7093 })
7094 .collect::<Vec<_>>();
7095
7096 (new_newlines, existing_newlines)
7097 });
7098 self.folding_newlines = cx.spawn(async move |this, cx| {
7099 let (new_newlines, existing_newlines) = task.await;
7100 if new_newlines == existing_newlines {
7101 return;
7102 }
7103 let placeholder = FoldPlaceholder {
7104 render: Arc::new(move |_, _, cx| {
7105 div()
7106 .bg(cx.theme().status().hint_background)
7107 .border_b_1()
7108 .size_full()
7109 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7110 .border_color(cx.theme().status().hint)
7111 .child("\\n")
7112 .into_any()
7113 }),
7114 constrain_width: false,
7115 merge_adjacent: false,
7116 type_tag: Some(type_id),
7117 };
7118 let creases = new_newlines
7119 .into_iter()
7120 .map(|range| Crease::simple(range, placeholder.clone()))
7121 .collect();
7122 this.update(cx, |this, cx| {
7123 this.display_map.update(cx, |display_map, cx| {
7124 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7125 display_map.fold(creases, cx);
7126 });
7127 })
7128 .ok();
7129 });
7130 }
7131
7132 fn refresh_selected_text_highlights(
7133 &mut self,
7134 on_buffer_edit: bool,
7135 window: &mut Window,
7136 cx: &mut Context<Editor>,
7137 ) {
7138 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
7139 else {
7140 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7141 self.quick_selection_highlight_task.take();
7142 self.debounced_selection_highlight_task.take();
7143 return;
7144 };
7145 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7146 if on_buffer_edit
7147 || self
7148 .quick_selection_highlight_task
7149 .as_ref()
7150 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7151 {
7152 let multi_buffer_visible_start = self
7153 .scroll_manager
7154 .anchor()
7155 .anchor
7156 .to_point(&multi_buffer_snapshot);
7157 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7158 multi_buffer_visible_start
7159 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7160 Bias::Left,
7161 );
7162 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7163 self.quick_selection_highlight_task = Some((
7164 query_range.clone(),
7165 self.update_selection_occurrence_highlights(
7166 query_text.clone(),
7167 query_range.clone(),
7168 multi_buffer_visible_range,
7169 false,
7170 window,
7171 cx,
7172 ),
7173 ));
7174 }
7175 if on_buffer_edit
7176 || self
7177 .debounced_selection_highlight_task
7178 .as_ref()
7179 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7180 {
7181 let multi_buffer_start = multi_buffer_snapshot
7182 .anchor_before(0)
7183 .to_point(&multi_buffer_snapshot);
7184 let multi_buffer_end = multi_buffer_snapshot
7185 .anchor_after(multi_buffer_snapshot.len())
7186 .to_point(&multi_buffer_snapshot);
7187 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7188 self.debounced_selection_highlight_task = Some((
7189 query_range.clone(),
7190 self.update_selection_occurrence_highlights(
7191 query_text,
7192 query_range,
7193 multi_buffer_full_range,
7194 true,
7195 window,
7196 cx,
7197 ),
7198 ));
7199 }
7200 }
7201
7202 pub fn refresh_edit_prediction(
7203 &mut self,
7204 debounce: bool,
7205 user_requested: bool,
7206 window: &mut Window,
7207 cx: &mut Context<Self>,
7208 ) -> Option<()> {
7209 if DisableAiSettings::get_global(cx).disable_ai {
7210 return None;
7211 }
7212
7213 let provider = self.edit_prediction_provider()?;
7214 let cursor = self.selections.newest_anchor().head();
7215 let (buffer, cursor_buffer_position) =
7216 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7217
7218 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7219 self.discard_edit_prediction(false, cx);
7220 return None;
7221 }
7222
7223 self.update_visible_edit_prediction(window, cx);
7224
7225 if !user_requested
7226 && (!self.should_show_edit_predictions()
7227 || !self.is_focused(window)
7228 || buffer.read(cx).is_empty())
7229 {
7230 self.discard_edit_prediction(false, cx);
7231 return None;
7232 }
7233
7234 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7235 Some(())
7236 }
7237
7238 fn show_edit_predictions_in_menu(&self) -> bool {
7239 match self.edit_prediction_settings {
7240 EditPredictionSettings::Disabled => false,
7241 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7242 }
7243 }
7244
7245 pub fn edit_predictions_enabled(&self) -> bool {
7246 match self.edit_prediction_settings {
7247 EditPredictionSettings::Disabled => false,
7248 EditPredictionSettings::Enabled { .. } => true,
7249 }
7250 }
7251
7252 fn edit_prediction_requires_modifier(&self) -> bool {
7253 match self.edit_prediction_settings {
7254 EditPredictionSettings::Disabled => false,
7255 EditPredictionSettings::Enabled {
7256 preview_requires_modifier,
7257 ..
7258 } => preview_requires_modifier,
7259 }
7260 }
7261
7262 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7263 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7264 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7265 self.discard_edit_prediction(false, cx);
7266 } else {
7267 let selection = self.selections.newest_anchor();
7268 let cursor = selection.head();
7269
7270 if let Some((buffer, cursor_buffer_position)) =
7271 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7272 {
7273 self.edit_prediction_settings =
7274 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7275 }
7276 }
7277 }
7278
7279 fn edit_prediction_settings_at_position(
7280 &self,
7281 buffer: &Entity<Buffer>,
7282 buffer_position: language::Anchor,
7283 cx: &App,
7284 ) -> EditPredictionSettings {
7285 if !self.mode.is_full()
7286 || !self.show_edit_predictions_override.unwrap_or(true)
7287 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7288 {
7289 return EditPredictionSettings::Disabled;
7290 }
7291
7292 let buffer = buffer.read(cx);
7293
7294 let file = buffer.file();
7295
7296 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7297 return EditPredictionSettings::Disabled;
7298 };
7299
7300 let by_provider = matches!(
7301 self.menu_edit_predictions_policy,
7302 MenuEditPredictionsPolicy::ByProvider
7303 );
7304
7305 let show_in_menu = by_provider
7306 && self
7307 .edit_prediction_provider
7308 .as_ref()
7309 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7310
7311 let preview_requires_modifier =
7312 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7313
7314 EditPredictionSettings::Enabled {
7315 show_in_menu,
7316 preview_requires_modifier,
7317 }
7318 }
7319
7320 fn should_show_edit_predictions(&self) -> bool {
7321 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7322 }
7323
7324 pub fn edit_prediction_preview_is_active(&self) -> bool {
7325 matches!(
7326 self.edit_prediction_preview,
7327 EditPredictionPreview::Active { .. }
7328 )
7329 }
7330
7331 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7332 let cursor = self.selections.newest_anchor().head();
7333 if let Some((buffer, cursor_position)) =
7334 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7335 {
7336 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7337 } else {
7338 false
7339 }
7340 }
7341
7342 pub fn supports_minimap(&self, cx: &App) -> bool {
7343 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7344 }
7345
7346 fn edit_predictions_enabled_in_buffer(
7347 &self,
7348 buffer: &Entity<Buffer>,
7349 buffer_position: language::Anchor,
7350 cx: &App,
7351 ) -> bool {
7352 maybe!({
7353 if self.read_only(cx) {
7354 return Some(false);
7355 }
7356 let provider = self.edit_prediction_provider()?;
7357 if !provider.is_enabled(buffer, buffer_position, cx) {
7358 return Some(false);
7359 }
7360 let buffer = buffer.read(cx);
7361 let Some(file) = buffer.file() else {
7362 return Some(true);
7363 };
7364 let settings = all_language_settings(Some(file), cx);
7365 Some(settings.edit_predictions_enabled_for_file(file, cx))
7366 })
7367 .unwrap_or(false)
7368 }
7369
7370 fn cycle_edit_prediction(
7371 &mut self,
7372 direction: Direction,
7373 window: &mut Window,
7374 cx: &mut Context<Self>,
7375 ) -> Option<()> {
7376 let provider = self.edit_prediction_provider()?;
7377 let cursor = self.selections.newest_anchor().head();
7378 let (buffer, cursor_buffer_position) =
7379 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7380 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7381 return None;
7382 }
7383
7384 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7385 self.update_visible_edit_prediction(window, cx);
7386
7387 Some(())
7388 }
7389
7390 pub fn show_edit_prediction(
7391 &mut self,
7392 _: &ShowEditPrediction,
7393 window: &mut Window,
7394 cx: &mut Context<Self>,
7395 ) {
7396 if !self.has_active_edit_prediction() {
7397 self.refresh_edit_prediction(false, true, window, cx);
7398 return;
7399 }
7400
7401 self.update_visible_edit_prediction(window, cx);
7402 }
7403
7404 pub fn display_cursor_names(
7405 &mut self,
7406 _: &DisplayCursorNames,
7407 window: &mut Window,
7408 cx: &mut Context<Self>,
7409 ) {
7410 self.show_cursor_names(window, cx);
7411 }
7412
7413 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7414 self.show_cursor_names = true;
7415 cx.notify();
7416 cx.spawn_in(window, async move |this, cx| {
7417 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7418 this.update(cx, |this, cx| {
7419 this.show_cursor_names = false;
7420 cx.notify()
7421 })
7422 .ok()
7423 })
7424 .detach();
7425 }
7426
7427 pub fn next_edit_prediction(
7428 &mut self,
7429 _: &NextEditPrediction,
7430 window: &mut Window,
7431 cx: &mut Context<Self>,
7432 ) {
7433 if self.has_active_edit_prediction() {
7434 self.cycle_edit_prediction(Direction::Next, window, cx);
7435 } else {
7436 let is_copilot_disabled = self
7437 .refresh_edit_prediction(false, true, window, cx)
7438 .is_none();
7439 if is_copilot_disabled {
7440 cx.propagate();
7441 }
7442 }
7443 }
7444
7445 pub fn previous_edit_prediction(
7446 &mut self,
7447 _: &PreviousEditPrediction,
7448 window: &mut Window,
7449 cx: &mut Context<Self>,
7450 ) {
7451 if self.has_active_edit_prediction() {
7452 self.cycle_edit_prediction(Direction::Prev, window, cx);
7453 } else {
7454 let is_copilot_disabled = self
7455 .refresh_edit_prediction(false, true, window, cx)
7456 .is_none();
7457 if is_copilot_disabled {
7458 cx.propagate();
7459 }
7460 }
7461 }
7462
7463 pub fn accept_edit_prediction(
7464 &mut self,
7465 _: &AcceptEditPrediction,
7466 window: &mut Window,
7467 cx: &mut Context<Self>,
7468 ) {
7469 if self.show_edit_predictions_in_menu() {
7470 self.hide_context_menu(window, cx);
7471 }
7472
7473 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7474 return;
7475 };
7476
7477 match &active_edit_prediction.completion {
7478 EditPrediction::MoveWithin { target, .. } => {
7479 let target = *target;
7480
7481 if let Some(position_map) = &self.last_position_map {
7482 if position_map
7483 .visible_row_range
7484 .contains(&target.to_display_point(&position_map.snapshot).row())
7485 || !self.edit_prediction_requires_modifier()
7486 {
7487 self.unfold_ranges(&[target..target], true, false, cx);
7488 // Note that this is also done in vim's handler of the Tab action.
7489 self.change_selections(
7490 SelectionEffects::scroll(Autoscroll::newest()),
7491 window,
7492 cx,
7493 |selections| {
7494 selections.select_anchor_ranges([target..target]);
7495 },
7496 );
7497 self.clear_row_highlights::<EditPredictionPreview>();
7498
7499 self.edit_prediction_preview
7500 .set_previous_scroll_position(None);
7501 } else {
7502 self.edit_prediction_preview
7503 .set_previous_scroll_position(Some(
7504 position_map.snapshot.scroll_anchor,
7505 ));
7506
7507 self.highlight_rows::<EditPredictionPreview>(
7508 target..target,
7509 cx.theme().colors().editor_highlighted_line_background,
7510 RowHighlightOptions {
7511 autoscroll: true,
7512 ..Default::default()
7513 },
7514 cx,
7515 );
7516 self.request_autoscroll(Autoscroll::fit(), cx);
7517 }
7518 }
7519 }
7520 EditPrediction::MoveOutside { snapshot, target } => {
7521 if let Some(workspace) = self.workspace() {
7522 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7523 .detach_and_log_err(cx);
7524 }
7525 }
7526 EditPrediction::Edit { edits, .. } => {
7527 self.report_edit_prediction_event(
7528 active_edit_prediction.completion_id.clone(),
7529 true,
7530 cx,
7531 );
7532
7533 if let Some(provider) = self.edit_prediction_provider() {
7534 provider.accept(cx);
7535 }
7536
7537 // Store the transaction ID and selections before applying the edit
7538 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7539
7540 let snapshot = self.buffer.read(cx).snapshot(cx);
7541 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7542
7543 self.buffer.update(cx, |buffer, cx| {
7544 buffer.edit(edits.iter().cloned(), None, cx)
7545 });
7546
7547 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7548 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7549 });
7550
7551 let selections = self.selections.disjoint_anchors_arc();
7552 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7553 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7554 if has_new_transaction {
7555 self.selection_history
7556 .insert_transaction(transaction_id_now, selections);
7557 }
7558 }
7559
7560 self.update_visible_edit_prediction(window, cx);
7561 if self.active_edit_prediction.is_none() {
7562 self.refresh_edit_prediction(true, true, window, cx);
7563 }
7564
7565 cx.notify();
7566 }
7567 }
7568
7569 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7570 }
7571
7572 pub fn accept_partial_edit_prediction(
7573 &mut self,
7574 _: &AcceptPartialEditPrediction,
7575 window: &mut Window,
7576 cx: &mut Context<Self>,
7577 ) {
7578 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7579 return;
7580 };
7581 if self.selections.count() != 1 {
7582 return;
7583 }
7584
7585 match &active_edit_prediction.completion {
7586 EditPrediction::MoveWithin { target, .. } => {
7587 let target = *target;
7588 self.change_selections(
7589 SelectionEffects::scroll(Autoscroll::newest()),
7590 window,
7591 cx,
7592 |selections| {
7593 selections.select_anchor_ranges([target..target]);
7594 },
7595 );
7596 }
7597 EditPrediction::MoveOutside { snapshot, target } => {
7598 if let Some(workspace) = self.workspace() {
7599 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7600 .detach_and_log_err(cx);
7601 }
7602 }
7603 EditPrediction::Edit { edits, .. } => {
7604 self.report_edit_prediction_event(
7605 active_edit_prediction.completion_id.clone(),
7606 true,
7607 cx,
7608 );
7609
7610 // Find an insertion that starts at the cursor position.
7611 let snapshot = self.buffer.read(cx).snapshot(cx);
7612 let cursor_offset = self
7613 .selections
7614 .newest::<usize>(&self.display_snapshot(cx))
7615 .head();
7616 let insertion = edits.iter().find_map(|(range, text)| {
7617 let range = range.to_offset(&snapshot);
7618 if range.is_empty() && range.start == cursor_offset {
7619 Some(text)
7620 } else {
7621 None
7622 }
7623 });
7624
7625 if let Some(text) = insertion {
7626 let mut partial_completion = text
7627 .chars()
7628 .by_ref()
7629 .take_while(|c| c.is_alphabetic())
7630 .collect::<String>();
7631 if partial_completion.is_empty() {
7632 partial_completion = text
7633 .chars()
7634 .by_ref()
7635 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7636 .collect::<String>();
7637 }
7638
7639 cx.emit(EditorEvent::InputHandled {
7640 utf16_range_to_replace: None,
7641 text: partial_completion.clone().into(),
7642 });
7643
7644 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7645
7646 self.refresh_edit_prediction(true, true, window, cx);
7647 cx.notify();
7648 } else {
7649 self.accept_edit_prediction(&Default::default(), window, cx);
7650 }
7651 }
7652 }
7653 }
7654
7655 fn discard_edit_prediction(
7656 &mut self,
7657 should_report_edit_prediction_event: bool,
7658 cx: &mut Context<Self>,
7659 ) -> bool {
7660 if should_report_edit_prediction_event {
7661 let completion_id = self
7662 .active_edit_prediction
7663 .as_ref()
7664 .and_then(|active_completion| active_completion.completion_id.clone());
7665
7666 self.report_edit_prediction_event(completion_id, false, cx);
7667 }
7668
7669 if let Some(provider) = self.edit_prediction_provider() {
7670 provider.discard(cx);
7671 }
7672
7673 self.take_active_edit_prediction(cx)
7674 }
7675
7676 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7677 let Some(provider) = self.edit_prediction_provider() else {
7678 return;
7679 };
7680
7681 let Some((_, buffer, _)) = self
7682 .buffer
7683 .read(cx)
7684 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7685 else {
7686 return;
7687 };
7688
7689 let extension = buffer
7690 .read(cx)
7691 .file()
7692 .and_then(|file| Some(file.path().extension()?.to_string()));
7693
7694 let event_type = match accepted {
7695 true => "Edit Prediction Accepted",
7696 false => "Edit Prediction Discarded",
7697 };
7698 telemetry::event!(
7699 event_type,
7700 provider = provider.name(),
7701 prediction_id = id,
7702 suggestion_accepted = accepted,
7703 file_extension = extension,
7704 );
7705 }
7706
7707 fn open_editor_at_anchor(
7708 snapshot: &language::BufferSnapshot,
7709 target: language::Anchor,
7710 workspace: &Entity<Workspace>,
7711 window: &mut Window,
7712 cx: &mut App,
7713 ) -> Task<Result<()>> {
7714 workspace.update(cx, |workspace, cx| {
7715 let path = snapshot.file().map(|file| file.full_path(cx));
7716 let Some(path) =
7717 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7718 else {
7719 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7720 };
7721 let target = text::ToPoint::to_point(&target, snapshot);
7722 let item = workspace.open_path(path, None, true, window, cx);
7723 window.spawn(cx, async move |cx| {
7724 let Some(editor) = item.await?.downcast::<Editor>() else {
7725 return Ok(());
7726 };
7727 editor
7728 .update_in(cx, |editor, window, cx| {
7729 editor.go_to_singleton_buffer_point(target, window, cx);
7730 })
7731 .ok();
7732 anyhow::Ok(())
7733 })
7734 })
7735 }
7736
7737 pub fn has_active_edit_prediction(&self) -> bool {
7738 self.active_edit_prediction.is_some()
7739 }
7740
7741 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7742 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7743 return false;
7744 };
7745
7746 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7747 self.clear_highlights::<EditPredictionHighlight>(cx);
7748 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7749 true
7750 }
7751
7752 /// Returns true when we're displaying the edit prediction popover below the cursor
7753 /// like we are not previewing and the LSP autocomplete menu is visible
7754 /// or we are in `when_holding_modifier` mode.
7755 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7756 if self.edit_prediction_preview_is_active()
7757 || !self.show_edit_predictions_in_menu()
7758 || !self.edit_predictions_enabled()
7759 {
7760 return false;
7761 }
7762
7763 if self.has_visible_completions_menu() {
7764 return true;
7765 }
7766
7767 has_completion && self.edit_prediction_requires_modifier()
7768 }
7769
7770 fn handle_modifiers_changed(
7771 &mut self,
7772 modifiers: Modifiers,
7773 position_map: &PositionMap,
7774 window: &mut Window,
7775 cx: &mut Context<Self>,
7776 ) {
7777 if self.show_edit_predictions_in_menu() {
7778 self.update_edit_prediction_preview(&modifiers, window, cx);
7779 }
7780
7781 self.update_selection_mode(&modifiers, position_map, window, cx);
7782
7783 let mouse_position = window.mouse_position();
7784 if !position_map.text_hitbox.is_hovered(window) {
7785 return;
7786 }
7787
7788 self.update_hovered_link(
7789 position_map.point_for_position(mouse_position),
7790 &position_map.snapshot,
7791 modifiers,
7792 window,
7793 cx,
7794 )
7795 }
7796
7797 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7798 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7799 if invert {
7800 match multi_cursor_setting {
7801 MultiCursorModifier::Alt => modifiers.alt,
7802 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7803 }
7804 } else {
7805 match multi_cursor_setting {
7806 MultiCursorModifier::Alt => modifiers.secondary(),
7807 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7808 }
7809 }
7810 }
7811
7812 fn columnar_selection_mode(
7813 modifiers: &Modifiers,
7814 cx: &mut Context<Self>,
7815 ) -> Option<ColumnarMode> {
7816 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7817 if Self::multi_cursor_modifier(false, modifiers, cx) {
7818 Some(ColumnarMode::FromMouse)
7819 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7820 Some(ColumnarMode::FromSelection)
7821 } else {
7822 None
7823 }
7824 } else {
7825 None
7826 }
7827 }
7828
7829 fn update_selection_mode(
7830 &mut self,
7831 modifiers: &Modifiers,
7832 position_map: &PositionMap,
7833 window: &mut Window,
7834 cx: &mut Context<Self>,
7835 ) {
7836 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7837 return;
7838 };
7839 if self.selections.pending_anchor().is_none() {
7840 return;
7841 }
7842
7843 let mouse_position = window.mouse_position();
7844 let point_for_position = position_map.point_for_position(mouse_position);
7845 let position = point_for_position.previous_valid;
7846
7847 self.select(
7848 SelectPhase::BeginColumnar {
7849 position,
7850 reset: false,
7851 mode,
7852 goal_column: point_for_position.exact_unclipped.column(),
7853 },
7854 window,
7855 cx,
7856 );
7857 }
7858
7859 fn update_edit_prediction_preview(
7860 &mut self,
7861 modifiers: &Modifiers,
7862 window: &mut Window,
7863 cx: &mut Context<Self>,
7864 ) {
7865 let mut modifiers_held = false;
7866 if let Some(accept_keystroke) = self
7867 .accept_edit_prediction_keybind(false, window, cx)
7868 .keystroke()
7869 {
7870 modifiers_held = modifiers_held
7871 || (accept_keystroke.modifiers() == modifiers
7872 && accept_keystroke.modifiers().modified());
7873 };
7874 if let Some(accept_partial_keystroke) = self
7875 .accept_edit_prediction_keybind(true, window, cx)
7876 .keystroke()
7877 {
7878 modifiers_held = modifiers_held
7879 || (accept_partial_keystroke.modifiers() == modifiers
7880 && accept_partial_keystroke.modifiers().modified());
7881 }
7882
7883 if modifiers_held {
7884 if matches!(
7885 self.edit_prediction_preview,
7886 EditPredictionPreview::Inactive { .. }
7887 ) {
7888 self.edit_prediction_preview = EditPredictionPreview::Active {
7889 previous_scroll_position: None,
7890 since: Instant::now(),
7891 };
7892
7893 self.update_visible_edit_prediction(window, cx);
7894 cx.notify();
7895 }
7896 } else if let EditPredictionPreview::Active {
7897 previous_scroll_position,
7898 since,
7899 } = self.edit_prediction_preview
7900 {
7901 if let (Some(previous_scroll_position), Some(position_map)) =
7902 (previous_scroll_position, self.last_position_map.as_ref())
7903 {
7904 self.set_scroll_position(
7905 previous_scroll_position
7906 .scroll_position(&position_map.snapshot.display_snapshot),
7907 window,
7908 cx,
7909 );
7910 }
7911
7912 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7913 released_too_fast: since.elapsed() < Duration::from_millis(200),
7914 };
7915 self.clear_row_highlights::<EditPredictionPreview>();
7916 self.update_visible_edit_prediction(window, cx);
7917 cx.notify();
7918 }
7919 }
7920
7921 fn update_visible_edit_prediction(
7922 &mut self,
7923 _window: &mut Window,
7924 cx: &mut Context<Self>,
7925 ) -> Option<()> {
7926 if DisableAiSettings::get_global(cx).disable_ai {
7927 return None;
7928 }
7929
7930 if self.ime_transaction.is_some() {
7931 self.discard_edit_prediction(false, cx);
7932 return None;
7933 }
7934
7935 let selection = self.selections.newest_anchor();
7936 let cursor = selection.head();
7937 let multibuffer = self.buffer.read(cx).snapshot(cx);
7938 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7939 let excerpt_id = cursor.excerpt_id;
7940
7941 let show_in_menu = self.show_edit_predictions_in_menu();
7942 let completions_menu_has_precedence = !show_in_menu
7943 && (self.context_menu.borrow().is_some()
7944 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7945
7946 if completions_menu_has_precedence
7947 || !offset_selection.is_empty()
7948 || self
7949 .active_edit_prediction
7950 .as_ref()
7951 .is_some_and(|completion| {
7952 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7953 return false;
7954 };
7955 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7956 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7957 !invalidation_range.contains(&offset_selection.head())
7958 })
7959 {
7960 self.discard_edit_prediction(false, cx);
7961 return None;
7962 }
7963
7964 self.take_active_edit_prediction(cx);
7965 let Some(provider) = self.edit_prediction_provider() else {
7966 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7967 return None;
7968 };
7969
7970 let (buffer, cursor_buffer_position) =
7971 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7972
7973 self.edit_prediction_settings =
7974 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7975
7976 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7977
7978 if self.edit_prediction_indent_conflict {
7979 let cursor_point = cursor.to_point(&multibuffer);
7980
7981 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7982
7983 if let Some((_, indent)) = indents.iter().next()
7984 && indent.len == cursor_point.column
7985 {
7986 self.edit_prediction_indent_conflict = false;
7987 }
7988 }
7989
7990 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7991
7992 let (completion_id, edits, edit_preview) = match edit_prediction {
7993 edit_prediction::EditPrediction::Local {
7994 id,
7995 edits,
7996 edit_preview,
7997 } => (id, edits, edit_preview),
7998 edit_prediction::EditPrediction::Jump {
7999 id,
8000 snapshot,
8001 target,
8002 } => {
8003 self.stale_edit_prediction_in_menu = None;
8004 self.active_edit_prediction = Some(EditPredictionState {
8005 inlay_ids: vec![],
8006 completion: EditPrediction::MoveOutside { snapshot, target },
8007 completion_id: id,
8008 invalidation_range: None,
8009 });
8010 cx.notify();
8011 return Some(());
8012 }
8013 };
8014
8015 let edits = edits
8016 .into_iter()
8017 .flat_map(|(range, new_text)| {
8018 Some((
8019 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8020 new_text,
8021 ))
8022 })
8023 .collect::<Vec<_>>();
8024 if edits.is_empty() {
8025 return None;
8026 }
8027
8028 let first_edit_start = edits.first().unwrap().0.start;
8029 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8030 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8031
8032 let last_edit_end = edits.last().unwrap().0.end;
8033 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8034 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8035
8036 let cursor_row = cursor.to_point(&multibuffer).row;
8037
8038 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8039
8040 let mut inlay_ids = Vec::new();
8041 let invalidation_row_range;
8042 let move_invalidation_row_range = if cursor_row < edit_start_row {
8043 Some(cursor_row..edit_end_row)
8044 } else if cursor_row > edit_end_row {
8045 Some(edit_start_row..cursor_row)
8046 } else {
8047 None
8048 };
8049 let supports_jump = self
8050 .edit_prediction_provider
8051 .as_ref()
8052 .map(|provider| provider.provider.supports_jump_to_edit())
8053 .unwrap_or(true);
8054
8055 let is_move = supports_jump
8056 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8057 let completion = if is_move {
8058 invalidation_row_range =
8059 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8060 let target = first_edit_start;
8061 EditPrediction::MoveWithin { target, snapshot }
8062 } else {
8063 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8064 && !self.edit_predictions_hidden_for_vim_mode;
8065
8066 if show_completions_in_buffer {
8067 if edits
8068 .iter()
8069 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8070 {
8071 let mut inlays = Vec::new();
8072 for (range, new_text) in &edits {
8073 let inlay = Inlay::edit_prediction(
8074 post_inc(&mut self.next_inlay_id),
8075 range.start,
8076 new_text.as_str(),
8077 );
8078 inlay_ids.push(inlay.id);
8079 inlays.push(inlay);
8080 }
8081
8082 self.splice_inlays(&[], inlays, cx);
8083 } else {
8084 let background_color = cx.theme().status().deleted_background;
8085 self.highlight_text::<EditPredictionHighlight>(
8086 edits.iter().map(|(range, _)| range.clone()).collect(),
8087 HighlightStyle {
8088 background_color: Some(background_color),
8089 ..Default::default()
8090 },
8091 cx,
8092 );
8093 }
8094 }
8095
8096 invalidation_row_range = edit_start_row..edit_end_row;
8097
8098 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8099 if provider.show_tab_accept_marker() {
8100 EditDisplayMode::TabAccept
8101 } else {
8102 EditDisplayMode::Inline
8103 }
8104 } else {
8105 EditDisplayMode::DiffPopover
8106 };
8107
8108 EditPrediction::Edit {
8109 edits,
8110 edit_preview,
8111 display_mode,
8112 snapshot,
8113 }
8114 };
8115
8116 let invalidation_range = multibuffer
8117 .anchor_before(Point::new(invalidation_row_range.start, 0))
8118 ..multibuffer.anchor_after(Point::new(
8119 invalidation_row_range.end,
8120 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8121 ));
8122
8123 self.stale_edit_prediction_in_menu = None;
8124 self.active_edit_prediction = Some(EditPredictionState {
8125 inlay_ids,
8126 completion,
8127 completion_id,
8128 invalidation_range: Some(invalidation_range),
8129 });
8130
8131 cx.notify();
8132
8133 Some(())
8134 }
8135
8136 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8137 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8138 }
8139
8140 fn clear_tasks(&mut self) {
8141 self.tasks.clear()
8142 }
8143
8144 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8145 if self.tasks.insert(key, value).is_some() {
8146 // This case should hopefully be rare, but just in case...
8147 log::error!(
8148 "multiple different run targets found on a single line, only the last target will be rendered"
8149 )
8150 }
8151 }
8152
8153 /// Get all display points of breakpoints that will be rendered within editor
8154 ///
8155 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8156 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8157 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8158 fn active_breakpoints(
8159 &self,
8160 range: Range<DisplayRow>,
8161 window: &mut Window,
8162 cx: &mut Context<Self>,
8163 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8164 let mut breakpoint_display_points = HashMap::default();
8165
8166 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8167 return breakpoint_display_points;
8168 };
8169
8170 let snapshot = self.snapshot(window, cx);
8171
8172 let multi_buffer_snapshot = snapshot.display_snapshot.buffer_snapshot();
8173 let Some(project) = self.project() else {
8174 return breakpoint_display_points;
8175 };
8176
8177 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8178 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8179
8180 for (buffer_snapshot, range, excerpt_id) in
8181 multi_buffer_snapshot.range_to_buffer_ranges(range)
8182 {
8183 let Some(buffer) = project
8184 .read(cx)
8185 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8186 else {
8187 continue;
8188 };
8189 let breakpoints = breakpoint_store.read(cx).breakpoints(
8190 &buffer,
8191 Some(
8192 buffer_snapshot.anchor_before(range.start)
8193 ..buffer_snapshot.anchor_after(range.end),
8194 ),
8195 buffer_snapshot,
8196 cx,
8197 );
8198 for (breakpoint, state) in breakpoints {
8199 let multi_buffer_anchor =
8200 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8201 let position = multi_buffer_anchor
8202 .to_point(multi_buffer_snapshot)
8203 .to_display_point(&snapshot);
8204
8205 breakpoint_display_points.insert(
8206 position.row(),
8207 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8208 );
8209 }
8210 }
8211
8212 breakpoint_display_points
8213 }
8214
8215 fn breakpoint_context_menu(
8216 &self,
8217 anchor: Anchor,
8218 window: &mut Window,
8219 cx: &mut Context<Self>,
8220 ) -> Entity<ui::ContextMenu> {
8221 let weak_editor = cx.weak_entity();
8222 let focus_handle = self.focus_handle(cx);
8223
8224 let row = self
8225 .buffer
8226 .read(cx)
8227 .snapshot(cx)
8228 .summary_for_anchor::<Point>(&anchor)
8229 .row;
8230
8231 let breakpoint = self
8232 .breakpoint_at_row(row, window, cx)
8233 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8234
8235 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8236 "Edit Log Breakpoint"
8237 } else {
8238 "Set Log Breakpoint"
8239 };
8240
8241 let condition_breakpoint_msg = if breakpoint
8242 .as_ref()
8243 .is_some_and(|bp| bp.1.condition.is_some())
8244 {
8245 "Edit Condition Breakpoint"
8246 } else {
8247 "Set Condition Breakpoint"
8248 };
8249
8250 let hit_condition_breakpoint_msg = if breakpoint
8251 .as_ref()
8252 .is_some_and(|bp| bp.1.hit_condition.is_some())
8253 {
8254 "Edit Hit Condition Breakpoint"
8255 } else {
8256 "Set Hit Condition Breakpoint"
8257 };
8258
8259 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8260 "Unset Breakpoint"
8261 } else {
8262 "Set Breakpoint"
8263 };
8264
8265 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8266
8267 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8268 BreakpointState::Enabled => Some("Disable"),
8269 BreakpointState::Disabled => Some("Enable"),
8270 });
8271
8272 let (anchor, breakpoint) =
8273 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8274
8275 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8276 menu.on_blur_subscription(Subscription::new(|| {}))
8277 .context(focus_handle)
8278 .when(run_to_cursor, |this| {
8279 let weak_editor = weak_editor.clone();
8280 this.entry("Run to cursor", None, move |window, cx| {
8281 weak_editor
8282 .update(cx, |editor, cx| {
8283 editor.change_selections(
8284 SelectionEffects::no_scroll(),
8285 window,
8286 cx,
8287 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8288 );
8289 })
8290 .ok();
8291
8292 window.dispatch_action(Box::new(RunToCursor), cx);
8293 })
8294 .separator()
8295 })
8296 .when_some(toggle_state_msg, |this, msg| {
8297 this.entry(msg, None, {
8298 let weak_editor = weak_editor.clone();
8299 let breakpoint = breakpoint.clone();
8300 move |_window, cx| {
8301 weak_editor
8302 .update(cx, |this, cx| {
8303 this.edit_breakpoint_at_anchor(
8304 anchor,
8305 breakpoint.as_ref().clone(),
8306 BreakpointEditAction::InvertState,
8307 cx,
8308 );
8309 })
8310 .log_err();
8311 }
8312 })
8313 })
8314 .entry(set_breakpoint_msg, None, {
8315 let weak_editor = weak_editor.clone();
8316 let breakpoint = breakpoint.clone();
8317 move |_window, cx| {
8318 weak_editor
8319 .update(cx, |this, cx| {
8320 this.edit_breakpoint_at_anchor(
8321 anchor,
8322 breakpoint.as_ref().clone(),
8323 BreakpointEditAction::Toggle,
8324 cx,
8325 );
8326 })
8327 .log_err();
8328 }
8329 })
8330 .entry(log_breakpoint_msg, None, {
8331 let breakpoint = breakpoint.clone();
8332 let weak_editor = weak_editor.clone();
8333 move |window, cx| {
8334 weak_editor
8335 .update(cx, |this, cx| {
8336 this.add_edit_breakpoint_block(
8337 anchor,
8338 breakpoint.as_ref(),
8339 BreakpointPromptEditAction::Log,
8340 window,
8341 cx,
8342 );
8343 })
8344 .log_err();
8345 }
8346 })
8347 .entry(condition_breakpoint_msg, None, {
8348 let breakpoint = breakpoint.clone();
8349 let weak_editor = weak_editor.clone();
8350 move |window, cx| {
8351 weak_editor
8352 .update(cx, |this, cx| {
8353 this.add_edit_breakpoint_block(
8354 anchor,
8355 breakpoint.as_ref(),
8356 BreakpointPromptEditAction::Condition,
8357 window,
8358 cx,
8359 );
8360 })
8361 .log_err();
8362 }
8363 })
8364 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8365 weak_editor
8366 .update(cx, |this, cx| {
8367 this.add_edit_breakpoint_block(
8368 anchor,
8369 breakpoint.as_ref(),
8370 BreakpointPromptEditAction::HitCondition,
8371 window,
8372 cx,
8373 );
8374 })
8375 .log_err();
8376 })
8377 })
8378 }
8379
8380 fn render_breakpoint(
8381 &self,
8382 position: Anchor,
8383 row: DisplayRow,
8384 breakpoint: &Breakpoint,
8385 state: Option<BreakpointSessionState>,
8386 cx: &mut Context<Self>,
8387 ) -> IconButton {
8388 let is_rejected = state.is_some_and(|s| !s.verified);
8389 // Is it a breakpoint that shows up when hovering over gutter?
8390 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8391 (false, false),
8392 |PhantomBreakpointIndicator {
8393 is_active,
8394 display_row,
8395 collides_with_existing_breakpoint,
8396 }| {
8397 (
8398 is_active && display_row == row,
8399 collides_with_existing_breakpoint,
8400 )
8401 },
8402 );
8403
8404 let (color, icon) = {
8405 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8406 (false, false) => ui::IconName::DebugBreakpoint,
8407 (true, false) => ui::IconName::DebugLogBreakpoint,
8408 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8409 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8410 };
8411
8412 let color = if is_phantom {
8413 Color::Hint
8414 } else if is_rejected {
8415 Color::Disabled
8416 } else {
8417 Color::Debugger
8418 };
8419
8420 (color, icon)
8421 };
8422
8423 let breakpoint = Arc::from(breakpoint.clone());
8424
8425 let alt_as_text = gpui::Keystroke {
8426 modifiers: Modifiers::secondary_key(),
8427 ..Default::default()
8428 };
8429 let primary_action_text = if breakpoint.is_disabled() {
8430 "Enable breakpoint"
8431 } else if is_phantom && !collides_with_existing {
8432 "Set breakpoint"
8433 } else {
8434 "Unset breakpoint"
8435 };
8436 let focus_handle = self.focus_handle.clone();
8437
8438 let meta = if is_rejected {
8439 SharedString::from("No executable code is associated with this line.")
8440 } else if collides_with_existing && !breakpoint.is_disabled() {
8441 SharedString::from(format!(
8442 "{alt_as_text}-click to disable,\nright-click for more options."
8443 ))
8444 } else {
8445 SharedString::from("Right-click for more options.")
8446 };
8447 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8448 .icon_size(IconSize::XSmall)
8449 .size(ui::ButtonSize::None)
8450 .when(is_rejected, |this| {
8451 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8452 })
8453 .icon_color(color)
8454 .style(ButtonStyle::Transparent)
8455 .on_click(cx.listener({
8456 move |editor, event: &ClickEvent, window, cx| {
8457 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8458 BreakpointEditAction::InvertState
8459 } else {
8460 BreakpointEditAction::Toggle
8461 };
8462
8463 window.focus(&editor.focus_handle(cx));
8464 editor.edit_breakpoint_at_anchor(
8465 position,
8466 breakpoint.as_ref().clone(),
8467 edit_action,
8468 cx,
8469 );
8470 }
8471 }))
8472 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8473 editor.set_breakpoint_context_menu(
8474 row,
8475 Some(position),
8476 event.position(),
8477 window,
8478 cx,
8479 );
8480 }))
8481 .tooltip(move |window, cx| {
8482 Tooltip::with_meta_in(
8483 primary_action_text,
8484 Some(&ToggleBreakpoint),
8485 meta.clone(),
8486 &focus_handle,
8487 window,
8488 cx,
8489 )
8490 })
8491 }
8492
8493 fn build_tasks_context(
8494 project: &Entity<Project>,
8495 buffer: &Entity<Buffer>,
8496 buffer_row: u32,
8497 tasks: &Arc<RunnableTasks>,
8498 cx: &mut Context<Self>,
8499 ) -> Task<Option<task::TaskContext>> {
8500 let position = Point::new(buffer_row, tasks.column);
8501 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8502 let location = Location {
8503 buffer: buffer.clone(),
8504 range: range_start..range_start,
8505 };
8506 // Fill in the environmental variables from the tree-sitter captures
8507 let mut captured_task_variables = TaskVariables::default();
8508 for (capture_name, value) in tasks.extra_variables.clone() {
8509 captured_task_variables.insert(
8510 task::VariableName::Custom(capture_name.into()),
8511 value.clone(),
8512 );
8513 }
8514 project.update(cx, |project, cx| {
8515 project.task_store().update(cx, |task_store, cx| {
8516 task_store.task_context_for_location(captured_task_variables, location, cx)
8517 })
8518 })
8519 }
8520
8521 pub fn spawn_nearest_task(
8522 &mut self,
8523 action: &SpawnNearestTask,
8524 window: &mut Window,
8525 cx: &mut Context<Self>,
8526 ) {
8527 let Some((workspace, _)) = self.workspace.clone() else {
8528 return;
8529 };
8530 let Some(project) = self.project.clone() else {
8531 return;
8532 };
8533
8534 // Try to find a closest, enclosing node using tree-sitter that has a task
8535 let Some((buffer, buffer_row, tasks)) = self
8536 .find_enclosing_node_task(cx)
8537 // Or find the task that's closest in row-distance.
8538 .or_else(|| self.find_closest_task(cx))
8539 else {
8540 return;
8541 };
8542
8543 let reveal_strategy = action.reveal;
8544 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8545 cx.spawn_in(window, async move |_, cx| {
8546 let context = task_context.await?;
8547 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8548
8549 let resolved = &mut resolved_task.resolved;
8550 resolved.reveal = reveal_strategy;
8551
8552 workspace
8553 .update_in(cx, |workspace, window, cx| {
8554 workspace.schedule_resolved_task(
8555 task_source_kind,
8556 resolved_task,
8557 false,
8558 window,
8559 cx,
8560 );
8561 })
8562 .ok()
8563 })
8564 .detach();
8565 }
8566
8567 fn find_closest_task(
8568 &mut self,
8569 cx: &mut Context<Self>,
8570 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8571 let cursor_row = self
8572 .selections
8573 .newest_adjusted(&self.display_snapshot(cx))
8574 .head()
8575 .row;
8576
8577 let ((buffer_id, row), tasks) = self
8578 .tasks
8579 .iter()
8580 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8581
8582 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8583 let tasks = Arc::new(tasks.to_owned());
8584 Some((buffer, *row, tasks))
8585 }
8586
8587 fn find_enclosing_node_task(
8588 &mut self,
8589 cx: &mut Context<Self>,
8590 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8591 let snapshot = self.buffer.read(cx).snapshot(cx);
8592 let offset = self
8593 .selections
8594 .newest::<usize>(&self.display_snapshot(cx))
8595 .head();
8596 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8597 let buffer_id = excerpt.buffer().remote_id();
8598
8599 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8600 let mut cursor = layer.node().walk();
8601
8602 while cursor.goto_first_child_for_byte(offset).is_some() {
8603 if cursor.node().end_byte() == offset {
8604 cursor.goto_next_sibling();
8605 }
8606 }
8607
8608 // Ascend to the smallest ancestor that contains the range and has a task.
8609 loop {
8610 let node = cursor.node();
8611 let node_range = node.byte_range();
8612 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8613
8614 // Check if this node contains our offset
8615 if node_range.start <= offset && node_range.end >= offset {
8616 // If it contains offset, check for task
8617 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8618 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8619 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8620 }
8621 }
8622
8623 if !cursor.goto_parent() {
8624 break;
8625 }
8626 }
8627 None
8628 }
8629
8630 fn render_run_indicator(
8631 &self,
8632 _style: &EditorStyle,
8633 is_active: bool,
8634 row: DisplayRow,
8635 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8636 cx: &mut Context<Self>,
8637 ) -> IconButton {
8638 let color = Color::Muted;
8639 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8640
8641 IconButton::new(
8642 ("run_indicator", row.0 as usize),
8643 ui::IconName::PlayOutlined,
8644 )
8645 .shape(ui::IconButtonShape::Square)
8646 .icon_size(IconSize::XSmall)
8647 .icon_color(color)
8648 .toggle_state(is_active)
8649 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8650 let quick_launch = match e {
8651 ClickEvent::Keyboard(_) => true,
8652 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8653 };
8654
8655 window.focus(&editor.focus_handle(cx));
8656 editor.toggle_code_actions(
8657 &ToggleCodeActions {
8658 deployed_from: Some(CodeActionSource::RunMenu(row)),
8659 quick_launch,
8660 },
8661 window,
8662 cx,
8663 );
8664 }))
8665 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8666 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8667 }))
8668 }
8669
8670 pub fn context_menu_visible(&self) -> bool {
8671 !self.edit_prediction_preview_is_active()
8672 && self
8673 .context_menu
8674 .borrow()
8675 .as_ref()
8676 .is_some_and(|menu| menu.visible())
8677 }
8678
8679 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8680 self.context_menu
8681 .borrow()
8682 .as_ref()
8683 .map(|menu| menu.origin())
8684 }
8685
8686 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8687 self.context_menu_options = Some(options);
8688 }
8689
8690 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8691 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8692
8693 fn render_edit_prediction_popover(
8694 &mut self,
8695 text_bounds: &Bounds<Pixels>,
8696 content_origin: gpui::Point<Pixels>,
8697 right_margin: Pixels,
8698 editor_snapshot: &EditorSnapshot,
8699 visible_row_range: Range<DisplayRow>,
8700 scroll_top: ScrollOffset,
8701 scroll_bottom: ScrollOffset,
8702 line_layouts: &[LineWithInvisibles],
8703 line_height: Pixels,
8704 scroll_position: gpui::Point<ScrollOffset>,
8705 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8706 newest_selection_head: Option<DisplayPoint>,
8707 editor_width: Pixels,
8708 style: &EditorStyle,
8709 window: &mut Window,
8710 cx: &mut App,
8711 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8712 if self.mode().is_minimap() {
8713 return None;
8714 }
8715 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8716
8717 if self.edit_prediction_visible_in_cursor_popover(true) {
8718 return None;
8719 }
8720
8721 match &active_edit_prediction.completion {
8722 EditPrediction::MoveWithin { target, .. } => {
8723 let target_display_point = target.to_display_point(editor_snapshot);
8724
8725 if self.edit_prediction_requires_modifier() {
8726 if !self.edit_prediction_preview_is_active() {
8727 return None;
8728 }
8729
8730 self.render_edit_prediction_modifier_jump_popover(
8731 text_bounds,
8732 content_origin,
8733 visible_row_range,
8734 line_layouts,
8735 line_height,
8736 scroll_pixel_position,
8737 newest_selection_head,
8738 target_display_point,
8739 window,
8740 cx,
8741 )
8742 } else {
8743 self.render_edit_prediction_eager_jump_popover(
8744 text_bounds,
8745 content_origin,
8746 editor_snapshot,
8747 visible_row_range,
8748 scroll_top,
8749 scroll_bottom,
8750 line_height,
8751 scroll_pixel_position,
8752 target_display_point,
8753 editor_width,
8754 window,
8755 cx,
8756 )
8757 }
8758 }
8759 EditPrediction::Edit {
8760 display_mode: EditDisplayMode::Inline,
8761 ..
8762 } => None,
8763 EditPrediction::Edit {
8764 display_mode: EditDisplayMode::TabAccept,
8765 edits,
8766 ..
8767 } => {
8768 let range = &edits.first()?.0;
8769 let target_display_point = range.end.to_display_point(editor_snapshot);
8770
8771 self.render_edit_prediction_end_of_line_popover(
8772 "Accept",
8773 editor_snapshot,
8774 visible_row_range,
8775 target_display_point,
8776 line_height,
8777 scroll_pixel_position,
8778 content_origin,
8779 editor_width,
8780 window,
8781 cx,
8782 )
8783 }
8784 EditPrediction::Edit {
8785 edits,
8786 edit_preview,
8787 display_mode: EditDisplayMode::DiffPopover,
8788 snapshot,
8789 } => self.render_edit_prediction_diff_popover(
8790 text_bounds,
8791 content_origin,
8792 right_margin,
8793 editor_snapshot,
8794 visible_row_range,
8795 line_layouts,
8796 line_height,
8797 scroll_position,
8798 scroll_pixel_position,
8799 newest_selection_head,
8800 editor_width,
8801 style,
8802 edits,
8803 edit_preview,
8804 snapshot,
8805 window,
8806 cx,
8807 ),
8808 EditPrediction::MoveOutside { snapshot, .. } => {
8809 let file_name = snapshot
8810 .file()
8811 .map(|file| file.file_name(cx))
8812 .unwrap_or("untitled");
8813 let mut element = self
8814 .render_edit_prediction_line_popover(
8815 format!("Jump to {file_name}"),
8816 Some(IconName::ZedPredict),
8817 window,
8818 cx,
8819 )
8820 .into_any();
8821
8822 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8823 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8824 let origin_y = text_bounds.size.height - size.height - px(30.);
8825 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8826 element.prepaint_at(origin, window, cx);
8827
8828 Some((element, origin))
8829 }
8830 }
8831 }
8832
8833 fn render_edit_prediction_modifier_jump_popover(
8834 &mut self,
8835 text_bounds: &Bounds<Pixels>,
8836 content_origin: gpui::Point<Pixels>,
8837 visible_row_range: Range<DisplayRow>,
8838 line_layouts: &[LineWithInvisibles],
8839 line_height: Pixels,
8840 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8841 newest_selection_head: Option<DisplayPoint>,
8842 target_display_point: DisplayPoint,
8843 window: &mut Window,
8844 cx: &mut App,
8845 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8846 let scrolled_content_origin =
8847 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8848
8849 const SCROLL_PADDING_Y: Pixels = px(12.);
8850
8851 if target_display_point.row() < visible_row_range.start {
8852 return self.render_edit_prediction_scroll_popover(
8853 |_| SCROLL_PADDING_Y,
8854 IconName::ArrowUp,
8855 visible_row_range,
8856 line_layouts,
8857 newest_selection_head,
8858 scrolled_content_origin,
8859 window,
8860 cx,
8861 );
8862 } else if target_display_point.row() >= visible_row_range.end {
8863 return self.render_edit_prediction_scroll_popover(
8864 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8865 IconName::ArrowDown,
8866 visible_row_range,
8867 line_layouts,
8868 newest_selection_head,
8869 scrolled_content_origin,
8870 window,
8871 cx,
8872 );
8873 }
8874
8875 const POLE_WIDTH: Pixels = px(2.);
8876
8877 let line_layout =
8878 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8879 let target_column = target_display_point.column() as usize;
8880
8881 let target_x = line_layout.x_for_index(target_column);
8882 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8883 - scroll_pixel_position.y;
8884
8885 let flag_on_right = target_x < text_bounds.size.width / 2.;
8886
8887 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8888 border_color.l += 0.001;
8889
8890 let mut element = v_flex()
8891 .items_end()
8892 .when(flag_on_right, |el| el.items_start())
8893 .child(if flag_on_right {
8894 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8895 .rounded_bl(px(0.))
8896 .rounded_tl(px(0.))
8897 .border_l_2()
8898 .border_color(border_color)
8899 } else {
8900 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8901 .rounded_br(px(0.))
8902 .rounded_tr(px(0.))
8903 .border_r_2()
8904 .border_color(border_color)
8905 })
8906 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8907 .into_any();
8908
8909 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8910
8911 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8912 - point(
8913 if flag_on_right {
8914 POLE_WIDTH
8915 } else {
8916 size.width - POLE_WIDTH
8917 },
8918 size.height - line_height,
8919 );
8920
8921 origin.x = origin.x.max(content_origin.x);
8922
8923 element.prepaint_at(origin, window, cx);
8924
8925 Some((element, origin))
8926 }
8927
8928 fn render_edit_prediction_scroll_popover(
8929 &mut self,
8930 to_y: impl Fn(Size<Pixels>) -> Pixels,
8931 scroll_icon: IconName,
8932 visible_row_range: Range<DisplayRow>,
8933 line_layouts: &[LineWithInvisibles],
8934 newest_selection_head: Option<DisplayPoint>,
8935 scrolled_content_origin: gpui::Point<Pixels>,
8936 window: &mut Window,
8937 cx: &mut App,
8938 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8939 let mut element = self
8940 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8941 .into_any();
8942
8943 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8944
8945 let cursor = newest_selection_head?;
8946 let cursor_row_layout =
8947 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8948 let cursor_column = cursor.column() as usize;
8949
8950 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8951
8952 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8953
8954 element.prepaint_at(origin, window, cx);
8955 Some((element, origin))
8956 }
8957
8958 fn render_edit_prediction_eager_jump_popover(
8959 &mut self,
8960 text_bounds: &Bounds<Pixels>,
8961 content_origin: gpui::Point<Pixels>,
8962 editor_snapshot: &EditorSnapshot,
8963 visible_row_range: Range<DisplayRow>,
8964 scroll_top: ScrollOffset,
8965 scroll_bottom: ScrollOffset,
8966 line_height: Pixels,
8967 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8968 target_display_point: DisplayPoint,
8969 editor_width: Pixels,
8970 window: &mut Window,
8971 cx: &mut App,
8972 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8973 if target_display_point.row().as_f64() < scroll_top {
8974 let mut element = self
8975 .render_edit_prediction_line_popover(
8976 "Jump to Edit",
8977 Some(IconName::ArrowUp),
8978 window,
8979 cx,
8980 )
8981 .into_any();
8982
8983 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8984 let offset = point(
8985 (text_bounds.size.width - size.width) / 2.,
8986 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8987 );
8988
8989 let origin = text_bounds.origin + offset;
8990 element.prepaint_at(origin, window, cx);
8991 Some((element, origin))
8992 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8993 let mut element = self
8994 .render_edit_prediction_line_popover(
8995 "Jump to Edit",
8996 Some(IconName::ArrowDown),
8997 window,
8998 cx,
8999 )
9000 .into_any();
9001
9002 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9003 let offset = point(
9004 (text_bounds.size.width - size.width) / 2.,
9005 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9006 );
9007
9008 let origin = text_bounds.origin + offset;
9009 element.prepaint_at(origin, window, cx);
9010 Some((element, origin))
9011 } else {
9012 self.render_edit_prediction_end_of_line_popover(
9013 "Jump to Edit",
9014 editor_snapshot,
9015 visible_row_range,
9016 target_display_point,
9017 line_height,
9018 scroll_pixel_position,
9019 content_origin,
9020 editor_width,
9021 window,
9022 cx,
9023 )
9024 }
9025 }
9026
9027 fn render_edit_prediction_end_of_line_popover(
9028 self: &mut Editor,
9029 label: &'static str,
9030 editor_snapshot: &EditorSnapshot,
9031 visible_row_range: Range<DisplayRow>,
9032 target_display_point: DisplayPoint,
9033 line_height: Pixels,
9034 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9035 content_origin: gpui::Point<Pixels>,
9036 editor_width: Pixels,
9037 window: &mut Window,
9038 cx: &mut App,
9039 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9040 let target_line_end = DisplayPoint::new(
9041 target_display_point.row(),
9042 editor_snapshot.line_len(target_display_point.row()),
9043 );
9044
9045 let mut element = self
9046 .render_edit_prediction_line_popover(label, None, window, cx)
9047 .into_any();
9048
9049 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9050
9051 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
9052
9053 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9054 let mut origin = start_point
9055 + line_origin
9056 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9057 origin.x = origin.x.max(content_origin.x);
9058
9059 let max_x = content_origin.x + editor_width - size.width;
9060
9061 if origin.x > max_x {
9062 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9063
9064 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9065 origin.y += offset;
9066 IconName::ArrowUp
9067 } else {
9068 origin.y -= offset;
9069 IconName::ArrowDown
9070 };
9071
9072 element = self
9073 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9074 .into_any();
9075
9076 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9077
9078 origin.x = content_origin.x + editor_width - size.width - px(2.);
9079 }
9080
9081 element.prepaint_at(origin, window, cx);
9082 Some((element, origin))
9083 }
9084
9085 fn render_edit_prediction_diff_popover(
9086 self: &Editor,
9087 text_bounds: &Bounds<Pixels>,
9088 content_origin: gpui::Point<Pixels>,
9089 right_margin: Pixels,
9090 editor_snapshot: &EditorSnapshot,
9091 visible_row_range: Range<DisplayRow>,
9092 line_layouts: &[LineWithInvisibles],
9093 line_height: Pixels,
9094 scroll_position: gpui::Point<ScrollOffset>,
9095 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9096 newest_selection_head: Option<DisplayPoint>,
9097 editor_width: Pixels,
9098 style: &EditorStyle,
9099 edits: &Vec<(Range<Anchor>, String)>,
9100 edit_preview: &Option<language::EditPreview>,
9101 snapshot: &language::BufferSnapshot,
9102 window: &mut Window,
9103 cx: &mut App,
9104 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9105 let edit_start = edits
9106 .first()
9107 .unwrap()
9108 .0
9109 .start
9110 .to_display_point(editor_snapshot);
9111 let edit_end = edits
9112 .last()
9113 .unwrap()
9114 .0
9115 .end
9116 .to_display_point(editor_snapshot);
9117
9118 let is_visible = visible_row_range.contains(&edit_start.row())
9119 || visible_row_range.contains(&edit_end.row());
9120 if !is_visible {
9121 return None;
9122 }
9123
9124 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9125 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9126 } else {
9127 // Fallback for providers without edit_preview
9128 crate::edit_prediction_fallback_text(edits, cx)
9129 };
9130
9131 let styled_text = highlighted_edits.to_styled_text(&style.text);
9132 let line_count = highlighted_edits.text.lines().count();
9133
9134 const BORDER_WIDTH: Pixels = px(1.);
9135
9136 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9137 let has_keybind = keybind.is_some();
9138
9139 let mut element = h_flex()
9140 .items_start()
9141 .child(
9142 h_flex()
9143 .bg(cx.theme().colors().editor_background)
9144 .border(BORDER_WIDTH)
9145 .shadow_xs()
9146 .border_color(cx.theme().colors().border)
9147 .rounded_l_lg()
9148 .when(line_count > 1, |el| el.rounded_br_lg())
9149 .pr_1()
9150 .child(styled_text),
9151 )
9152 .child(
9153 h_flex()
9154 .h(line_height + BORDER_WIDTH * 2.)
9155 .px_1p5()
9156 .gap_1()
9157 // Workaround: For some reason, there's a gap if we don't do this
9158 .ml(-BORDER_WIDTH)
9159 .shadow(vec![gpui::BoxShadow {
9160 color: gpui::black().opacity(0.05),
9161 offset: point(px(1.), px(1.)),
9162 blur_radius: px(2.),
9163 spread_radius: px(0.),
9164 }])
9165 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9166 .border(BORDER_WIDTH)
9167 .border_color(cx.theme().colors().border)
9168 .rounded_r_lg()
9169 .id("edit_prediction_diff_popover_keybind")
9170 .when(!has_keybind, |el| {
9171 let status_colors = cx.theme().status();
9172
9173 el.bg(status_colors.error_background)
9174 .border_color(status_colors.error.opacity(0.6))
9175 .child(Icon::new(IconName::Info).color(Color::Error))
9176 .cursor_default()
9177 .hoverable_tooltip(move |_window, cx| {
9178 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9179 })
9180 })
9181 .children(keybind),
9182 )
9183 .into_any();
9184
9185 let longest_row =
9186 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9187 let longest_line_width = if visible_row_range.contains(&longest_row) {
9188 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9189 } else {
9190 layout_line(
9191 longest_row,
9192 editor_snapshot,
9193 style,
9194 editor_width,
9195 |_| false,
9196 window,
9197 cx,
9198 )
9199 .width
9200 };
9201
9202 let viewport_bounds =
9203 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9204 right: -right_margin,
9205 ..Default::default()
9206 });
9207
9208 let x_after_longest = Pixels::from(
9209 ScrollPixelOffset::from(
9210 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9211 ) - scroll_pixel_position.x,
9212 );
9213
9214 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9215
9216 // Fully visible if it can be displayed within the window (allow overlapping other
9217 // panes). However, this is only allowed if the popover starts within text_bounds.
9218 let can_position_to_the_right = x_after_longest < text_bounds.right()
9219 && x_after_longest + element_bounds.width < viewport_bounds.right();
9220
9221 let mut origin = if can_position_to_the_right {
9222 point(
9223 x_after_longest,
9224 text_bounds.origin.y
9225 + Pixels::from(
9226 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9227 - scroll_pixel_position.y,
9228 ),
9229 )
9230 } else {
9231 let cursor_row = newest_selection_head.map(|head| head.row());
9232 let above_edit = edit_start
9233 .row()
9234 .0
9235 .checked_sub(line_count as u32)
9236 .map(DisplayRow);
9237 let below_edit = Some(edit_end.row() + 1);
9238 let above_cursor =
9239 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9240 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9241
9242 // Place the edit popover adjacent to the edit if there is a location
9243 // available that is onscreen and does not obscure the cursor. Otherwise,
9244 // place it adjacent to the cursor.
9245 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9246 .into_iter()
9247 .flatten()
9248 .find(|&start_row| {
9249 let end_row = start_row + line_count as u32;
9250 visible_row_range.contains(&start_row)
9251 && visible_row_range.contains(&end_row)
9252 && cursor_row
9253 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9254 })?;
9255
9256 content_origin
9257 + point(
9258 Pixels::from(-scroll_pixel_position.x),
9259 Pixels::from(
9260 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9261 ),
9262 )
9263 };
9264
9265 origin.x -= BORDER_WIDTH;
9266
9267 window.defer_draw(element, origin, 1);
9268
9269 // Do not return an element, since it will already be drawn due to defer_draw.
9270 None
9271 }
9272
9273 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9274 px(30.)
9275 }
9276
9277 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9278 if self.read_only(cx) {
9279 cx.theme().players().read_only()
9280 } else {
9281 self.style.as_ref().unwrap().local_player
9282 }
9283 }
9284
9285 fn render_edit_prediction_accept_keybind(
9286 &self,
9287 window: &mut Window,
9288 cx: &App,
9289 ) -> Option<AnyElement> {
9290 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9291 let accept_keystroke = accept_binding.keystroke()?;
9292
9293 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9294
9295 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9296 Color::Accent
9297 } else {
9298 Color::Muted
9299 };
9300
9301 h_flex()
9302 .px_0p5()
9303 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9304 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9305 .text_size(TextSize::XSmall.rems(cx))
9306 .child(h_flex().children(ui::render_modifiers(
9307 accept_keystroke.modifiers(),
9308 PlatformStyle::platform(),
9309 Some(modifiers_color),
9310 Some(IconSize::XSmall.rems().into()),
9311 true,
9312 )))
9313 .when(is_platform_style_mac, |parent| {
9314 parent.child(accept_keystroke.key().to_string())
9315 })
9316 .when(!is_platform_style_mac, |parent| {
9317 parent.child(
9318 Key::new(
9319 util::capitalize(accept_keystroke.key()),
9320 Some(Color::Default),
9321 )
9322 .size(Some(IconSize::XSmall.rems().into())),
9323 )
9324 })
9325 .into_any()
9326 .into()
9327 }
9328
9329 fn render_edit_prediction_line_popover(
9330 &self,
9331 label: impl Into<SharedString>,
9332 icon: Option<IconName>,
9333 window: &mut Window,
9334 cx: &App,
9335 ) -> Stateful<Div> {
9336 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9337
9338 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9339 let has_keybind = keybind.is_some();
9340
9341 h_flex()
9342 .id("ep-line-popover")
9343 .py_0p5()
9344 .pl_1()
9345 .pr(padding_right)
9346 .gap_1()
9347 .rounded_md()
9348 .border_1()
9349 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9350 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9351 .shadow_xs()
9352 .when(!has_keybind, |el| {
9353 let status_colors = cx.theme().status();
9354
9355 el.bg(status_colors.error_background)
9356 .border_color(status_colors.error.opacity(0.6))
9357 .pl_2()
9358 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9359 .cursor_default()
9360 .hoverable_tooltip(move |_window, cx| {
9361 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9362 })
9363 })
9364 .children(keybind)
9365 .child(
9366 Label::new(label)
9367 .size(LabelSize::Small)
9368 .when(!has_keybind, |el| {
9369 el.color(cx.theme().status().error.into()).strikethrough()
9370 }),
9371 )
9372 .when(!has_keybind, |el| {
9373 el.child(
9374 h_flex().ml_1().child(
9375 Icon::new(IconName::Info)
9376 .size(IconSize::Small)
9377 .color(cx.theme().status().error.into()),
9378 ),
9379 )
9380 })
9381 .when_some(icon, |element, icon| {
9382 element.child(
9383 div()
9384 .mt(px(1.5))
9385 .child(Icon::new(icon).size(IconSize::Small)),
9386 )
9387 })
9388 }
9389
9390 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9391 let accent_color = cx.theme().colors().text_accent;
9392 let editor_bg_color = cx.theme().colors().editor_background;
9393 editor_bg_color.blend(accent_color.opacity(0.1))
9394 }
9395
9396 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9397 let accent_color = cx.theme().colors().text_accent;
9398 let editor_bg_color = cx.theme().colors().editor_background;
9399 editor_bg_color.blend(accent_color.opacity(0.6))
9400 }
9401 fn get_prediction_provider_icon_name(
9402 provider: &Option<RegisteredEditPredictionProvider>,
9403 ) -> IconName {
9404 match provider {
9405 Some(provider) => match provider.provider.name() {
9406 "copilot" => IconName::Copilot,
9407 "supermaven" => IconName::Supermaven,
9408 _ => IconName::ZedPredict,
9409 },
9410 None => IconName::ZedPredict,
9411 }
9412 }
9413
9414 fn render_edit_prediction_cursor_popover(
9415 &self,
9416 min_width: Pixels,
9417 max_width: Pixels,
9418 cursor_point: Point,
9419 style: &EditorStyle,
9420 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9421 _window: &Window,
9422 cx: &mut Context<Editor>,
9423 ) -> Option<AnyElement> {
9424 let provider = self.edit_prediction_provider.as_ref()?;
9425 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9426
9427 let is_refreshing = provider.provider.is_refreshing(cx);
9428
9429 fn pending_completion_container(icon: IconName) -> Div {
9430 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9431 }
9432
9433 let completion = match &self.active_edit_prediction {
9434 Some(prediction) => {
9435 if !self.has_visible_completions_menu() {
9436 const RADIUS: Pixels = px(6.);
9437 const BORDER_WIDTH: Pixels = px(1.);
9438
9439 return Some(
9440 h_flex()
9441 .elevation_2(cx)
9442 .border(BORDER_WIDTH)
9443 .border_color(cx.theme().colors().border)
9444 .when(accept_keystroke.is_none(), |el| {
9445 el.border_color(cx.theme().status().error)
9446 })
9447 .rounded(RADIUS)
9448 .rounded_tl(px(0.))
9449 .overflow_hidden()
9450 .child(div().px_1p5().child(match &prediction.completion {
9451 EditPrediction::MoveWithin { target, snapshot } => {
9452 use text::ToPoint as _;
9453 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9454 {
9455 Icon::new(IconName::ZedPredictDown)
9456 } else {
9457 Icon::new(IconName::ZedPredictUp)
9458 }
9459 }
9460 EditPrediction::MoveOutside { .. } => {
9461 // TODO [zeta2] custom icon for external jump?
9462 Icon::new(provider_icon)
9463 }
9464 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9465 }))
9466 .child(
9467 h_flex()
9468 .gap_1()
9469 .py_1()
9470 .px_2()
9471 .rounded_r(RADIUS - BORDER_WIDTH)
9472 .border_l_1()
9473 .border_color(cx.theme().colors().border)
9474 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9475 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9476 el.child(
9477 Label::new("Hold")
9478 .size(LabelSize::Small)
9479 .when(accept_keystroke.is_none(), |el| {
9480 el.strikethrough()
9481 })
9482 .line_height_style(LineHeightStyle::UiLabel),
9483 )
9484 })
9485 .id("edit_prediction_cursor_popover_keybind")
9486 .when(accept_keystroke.is_none(), |el| {
9487 let status_colors = cx.theme().status();
9488
9489 el.bg(status_colors.error_background)
9490 .border_color(status_colors.error.opacity(0.6))
9491 .child(Icon::new(IconName::Info).color(Color::Error))
9492 .cursor_default()
9493 .hoverable_tooltip(move |_window, cx| {
9494 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9495 .into()
9496 })
9497 })
9498 .when_some(
9499 accept_keystroke.as_ref(),
9500 |el, accept_keystroke| {
9501 el.child(h_flex().children(ui::render_modifiers(
9502 accept_keystroke.modifiers(),
9503 PlatformStyle::platform(),
9504 Some(Color::Default),
9505 Some(IconSize::XSmall.rems().into()),
9506 false,
9507 )))
9508 },
9509 ),
9510 )
9511 .into_any(),
9512 );
9513 }
9514
9515 self.render_edit_prediction_cursor_popover_preview(
9516 prediction,
9517 cursor_point,
9518 style,
9519 cx,
9520 )?
9521 }
9522
9523 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9524 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9525 stale_completion,
9526 cursor_point,
9527 style,
9528 cx,
9529 )?,
9530
9531 None => pending_completion_container(provider_icon)
9532 .child(Label::new("...").size(LabelSize::Small)),
9533 },
9534
9535 None => pending_completion_container(provider_icon)
9536 .child(Label::new("...").size(LabelSize::Small)),
9537 };
9538
9539 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9540 completion
9541 .with_animation(
9542 "loading-completion",
9543 Animation::new(Duration::from_secs(2))
9544 .repeat()
9545 .with_easing(pulsating_between(0.4, 0.8)),
9546 |label, delta| label.opacity(delta),
9547 )
9548 .into_any_element()
9549 } else {
9550 completion.into_any_element()
9551 };
9552
9553 let has_completion = self.active_edit_prediction.is_some();
9554
9555 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9556 Some(
9557 h_flex()
9558 .min_w(min_width)
9559 .max_w(max_width)
9560 .flex_1()
9561 .elevation_2(cx)
9562 .border_color(cx.theme().colors().border)
9563 .child(
9564 div()
9565 .flex_1()
9566 .py_1()
9567 .px_2()
9568 .overflow_hidden()
9569 .child(completion),
9570 )
9571 .when_some(accept_keystroke, |el, accept_keystroke| {
9572 if !accept_keystroke.modifiers().modified() {
9573 return el;
9574 }
9575
9576 el.child(
9577 h_flex()
9578 .h_full()
9579 .border_l_1()
9580 .rounded_r_lg()
9581 .border_color(cx.theme().colors().border)
9582 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9583 .gap_1()
9584 .py_1()
9585 .px_2()
9586 .child(
9587 h_flex()
9588 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9589 .when(is_platform_style_mac, |parent| parent.gap_1())
9590 .child(h_flex().children(ui::render_modifiers(
9591 accept_keystroke.modifiers(),
9592 PlatformStyle::platform(),
9593 Some(if !has_completion {
9594 Color::Muted
9595 } else {
9596 Color::Default
9597 }),
9598 None,
9599 false,
9600 ))),
9601 )
9602 .child(Label::new("Preview").into_any_element())
9603 .opacity(if has_completion { 1.0 } else { 0.4 }),
9604 )
9605 })
9606 .into_any(),
9607 )
9608 }
9609
9610 fn render_edit_prediction_cursor_popover_preview(
9611 &self,
9612 completion: &EditPredictionState,
9613 cursor_point: Point,
9614 style: &EditorStyle,
9615 cx: &mut Context<Editor>,
9616 ) -> Option<Div> {
9617 use text::ToPoint as _;
9618
9619 fn render_relative_row_jump(
9620 prefix: impl Into<String>,
9621 current_row: u32,
9622 target_row: u32,
9623 ) -> Div {
9624 let (row_diff, arrow) = if target_row < current_row {
9625 (current_row - target_row, IconName::ArrowUp)
9626 } else {
9627 (target_row - current_row, IconName::ArrowDown)
9628 };
9629
9630 h_flex()
9631 .child(
9632 Label::new(format!("{}{}", prefix.into(), row_diff))
9633 .color(Color::Muted)
9634 .size(LabelSize::Small),
9635 )
9636 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9637 }
9638
9639 let supports_jump = self
9640 .edit_prediction_provider
9641 .as_ref()
9642 .map(|provider| provider.provider.supports_jump_to_edit())
9643 .unwrap_or(true);
9644
9645 match &completion.completion {
9646 EditPrediction::MoveWithin {
9647 target, snapshot, ..
9648 } => {
9649 if !supports_jump {
9650 return None;
9651 }
9652
9653 Some(
9654 h_flex()
9655 .px_2()
9656 .gap_2()
9657 .flex_1()
9658 .child(
9659 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9660 Icon::new(IconName::ZedPredictDown)
9661 } else {
9662 Icon::new(IconName::ZedPredictUp)
9663 },
9664 )
9665 .child(Label::new("Jump to Edit")),
9666 )
9667 }
9668 EditPrediction::MoveOutside { snapshot, .. } => {
9669 let file_name = snapshot
9670 .file()
9671 .map(|file| file.file_name(cx))
9672 .unwrap_or("untitled");
9673 Some(
9674 h_flex()
9675 .px_2()
9676 .gap_2()
9677 .flex_1()
9678 .child(Icon::new(IconName::ZedPredict))
9679 .child(Label::new(format!("Jump to {file_name}"))),
9680 )
9681 }
9682 EditPrediction::Edit {
9683 edits,
9684 edit_preview,
9685 snapshot,
9686 display_mode: _,
9687 } => {
9688 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9689
9690 let (highlighted_edits, has_more_lines) =
9691 if let Some(edit_preview) = edit_preview.as_ref() {
9692 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9693 .first_line_preview()
9694 } else {
9695 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9696 };
9697
9698 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9699 .with_default_highlights(&style.text, highlighted_edits.highlights);
9700
9701 let preview = h_flex()
9702 .gap_1()
9703 .min_w_16()
9704 .child(styled_text)
9705 .when(has_more_lines, |parent| parent.child("…"));
9706
9707 let left = if supports_jump && first_edit_row != cursor_point.row {
9708 render_relative_row_jump("", cursor_point.row, first_edit_row)
9709 .into_any_element()
9710 } else {
9711 let icon_name =
9712 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9713 Icon::new(icon_name).into_any_element()
9714 };
9715
9716 Some(
9717 h_flex()
9718 .h_full()
9719 .flex_1()
9720 .gap_2()
9721 .pr_1()
9722 .overflow_x_hidden()
9723 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9724 .child(left)
9725 .child(preview),
9726 )
9727 }
9728 }
9729 }
9730
9731 pub fn render_context_menu(
9732 &self,
9733 style: &EditorStyle,
9734 max_height_in_lines: u32,
9735 window: &mut Window,
9736 cx: &mut Context<Editor>,
9737 ) -> Option<AnyElement> {
9738 let menu = self.context_menu.borrow();
9739 let menu = menu.as_ref()?;
9740 if !menu.visible() {
9741 return None;
9742 };
9743 Some(menu.render(style, max_height_in_lines, window, cx))
9744 }
9745
9746 fn render_context_menu_aside(
9747 &mut self,
9748 max_size: Size<Pixels>,
9749 window: &mut Window,
9750 cx: &mut Context<Editor>,
9751 ) -> Option<AnyElement> {
9752 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9753 if menu.visible() {
9754 menu.render_aside(max_size, window, cx)
9755 } else {
9756 None
9757 }
9758 })
9759 }
9760
9761 fn hide_context_menu(
9762 &mut self,
9763 window: &mut Window,
9764 cx: &mut Context<Self>,
9765 ) -> Option<CodeContextMenu> {
9766 cx.notify();
9767 self.completion_tasks.clear();
9768 let context_menu = self.context_menu.borrow_mut().take();
9769 self.stale_edit_prediction_in_menu.take();
9770 self.update_visible_edit_prediction(window, cx);
9771 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9772 && let Some(completion_provider) = &self.completion_provider
9773 {
9774 completion_provider.selection_changed(None, window, cx);
9775 }
9776 context_menu
9777 }
9778
9779 fn show_snippet_choices(
9780 &mut self,
9781 choices: &Vec<String>,
9782 selection: Range<Anchor>,
9783 cx: &mut Context<Self>,
9784 ) {
9785 let Some((_, buffer, _)) = self
9786 .buffer()
9787 .read(cx)
9788 .excerpt_containing(selection.start, cx)
9789 else {
9790 return;
9791 };
9792 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9793 else {
9794 return;
9795 };
9796 if buffer != end_buffer {
9797 log::error!("expected anchor range to have matching buffer IDs");
9798 return;
9799 }
9800
9801 let id = post_inc(&mut self.next_completion_id);
9802 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9803 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9804 CompletionsMenu::new_snippet_choices(
9805 id,
9806 true,
9807 choices,
9808 selection,
9809 buffer,
9810 snippet_sort_order,
9811 ),
9812 ));
9813 }
9814
9815 pub fn insert_snippet(
9816 &mut self,
9817 insertion_ranges: &[Range<usize>],
9818 snippet: Snippet,
9819 window: &mut Window,
9820 cx: &mut Context<Self>,
9821 ) -> Result<()> {
9822 struct Tabstop<T> {
9823 is_end_tabstop: bool,
9824 ranges: Vec<Range<T>>,
9825 choices: Option<Vec<String>>,
9826 }
9827
9828 let tabstops = self.buffer.update(cx, |buffer, cx| {
9829 let snippet_text: Arc<str> = snippet.text.clone().into();
9830 let edits = insertion_ranges
9831 .iter()
9832 .cloned()
9833 .map(|range| (range, snippet_text.clone()));
9834 let autoindent_mode = AutoindentMode::Block {
9835 original_indent_columns: Vec::new(),
9836 };
9837 buffer.edit(edits, Some(autoindent_mode), cx);
9838
9839 let snapshot = &*buffer.read(cx);
9840 let snippet = &snippet;
9841 snippet
9842 .tabstops
9843 .iter()
9844 .map(|tabstop| {
9845 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9846 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9847 });
9848 let mut tabstop_ranges = tabstop
9849 .ranges
9850 .iter()
9851 .flat_map(|tabstop_range| {
9852 let mut delta = 0_isize;
9853 insertion_ranges.iter().map(move |insertion_range| {
9854 let insertion_start = insertion_range.start as isize + delta;
9855 delta +=
9856 snippet.text.len() as isize - insertion_range.len() as isize;
9857
9858 let start = ((insertion_start + tabstop_range.start) as usize)
9859 .min(snapshot.len());
9860 let end = ((insertion_start + tabstop_range.end) as usize)
9861 .min(snapshot.len());
9862 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9863 })
9864 })
9865 .collect::<Vec<_>>();
9866 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9867
9868 Tabstop {
9869 is_end_tabstop,
9870 ranges: tabstop_ranges,
9871 choices: tabstop.choices.clone(),
9872 }
9873 })
9874 .collect::<Vec<_>>()
9875 });
9876 if let Some(tabstop) = tabstops.first() {
9877 self.change_selections(Default::default(), window, cx, |s| {
9878 // Reverse order so that the first range is the newest created selection.
9879 // Completions will use it and autoscroll will prioritize it.
9880 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9881 });
9882
9883 if let Some(choices) = &tabstop.choices
9884 && let Some(selection) = tabstop.ranges.first()
9885 {
9886 self.show_snippet_choices(choices, selection.clone(), cx)
9887 }
9888
9889 // If we're already at the last tabstop and it's at the end of the snippet,
9890 // we're done, we don't need to keep the state around.
9891 if !tabstop.is_end_tabstop {
9892 let choices = tabstops
9893 .iter()
9894 .map(|tabstop| tabstop.choices.clone())
9895 .collect();
9896
9897 let ranges = tabstops
9898 .into_iter()
9899 .map(|tabstop| tabstop.ranges)
9900 .collect::<Vec<_>>();
9901
9902 self.snippet_stack.push(SnippetState {
9903 active_index: 0,
9904 ranges,
9905 choices,
9906 });
9907 }
9908
9909 // Check whether the just-entered snippet ends with an auto-closable bracket.
9910 if self.autoclose_regions.is_empty() {
9911 let snapshot = self.buffer.read(cx).snapshot(cx);
9912 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
9913 let selection_head = selection.head();
9914 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9915 continue;
9916 };
9917
9918 let mut bracket_pair = None;
9919 let max_lookup_length = scope
9920 .brackets()
9921 .map(|(pair, _)| {
9922 pair.start
9923 .as_str()
9924 .chars()
9925 .count()
9926 .max(pair.end.as_str().chars().count())
9927 })
9928 .max();
9929 if let Some(max_lookup_length) = max_lookup_length {
9930 let next_text = snapshot
9931 .chars_at(selection_head)
9932 .take(max_lookup_length)
9933 .collect::<String>();
9934 let prev_text = snapshot
9935 .reversed_chars_at(selection_head)
9936 .take(max_lookup_length)
9937 .collect::<String>();
9938
9939 for (pair, enabled) in scope.brackets() {
9940 if enabled
9941 && pair.close
9942 && prev_text.starts_with(pair.start.as_str())
9943 && next_text.starts_with(pair.end.as_str())
9944 {
9945 bracket_pair = Some(pair.clone());
9946 break;
9947 }
9948 }
9949 }
9950
9951 if let Some(pair) = bracket_pair {
9952 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9953 let autoclose_enabled =
9954 self.use_autoclose && snapshot_settings.use_autoclose;
9955 if autoclose_enabled {
9956 let start = snapshot.anchor_after(selection_head);
9957 let end = snapshot.anchor_after(selection_head);
9958 self.autoclose_regions.push(AutocloseRegion {
9959 selection_id: selection.id,
9960 range: start..end,
9961 pair,
9962 });
9963 }
9964 }
9965 }
9966 }
9967 }
9968 Ok(())
9969 }
9970
9971 pub fn move_to_next_snippet_tabstop(
9972 &mut self,
9973 window: &mut Window,
9974 cx: &mut Context<Self>,
9975 ) -> bool {
9976 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9977 }
9978
9979 pub fn move_to_prev_snippet_tabstop(
9980 &mut self,
9981 window: &mut Window,
9982 cx: &mut Context<Self>,
9983 ) -> bool {
9984 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9985 }
9986
9987 pub fn move_to_snippet_tabstop(
9988 &mut self,
9989 bias: Bias,
9990 window: &mut Window,
9991 cx: &mut Context<Self>,
9992 ) -> bool {
9993 if let Some(mut snippet) = self.snippet_stack.pop() {
9994 match bias {
9995 Bias::Left => {
9996 if snippet.active_index > 0 {
9997 snippet.active_index -= 1;
9998 } else {
9999 self.snippet_stack.push(snippet);
10000 return false;
10001 }
10002 }
10003 Bias::Right => {
10004 if snippet.active_index + 1 < snippet.ranges.len() {
10005 snippet.active_index += 1;
10006 } else {
10007 self.snippet_stack.push(snippet);
10008 return false;
10009 }
10010 }
10011 }
10012 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10013 self.change_selections(Default::default(), window, cx, |s| {
10014 // Reverse order so that the first range is the newest created selection.
10015 // Completions will use it and autoscroll will prioritize it.
10016 s.select_ranges(current_ranges.iter().rev().cloned())
10017 });
10018
10019 if let Some(choices) = &snippet.choices[snippet.active_index]
10020 && let Some(selection) = current_ranges.first()
10021 {
10022 self.show_snippet_choices(choices, selection.clone(), cx);
10023 }
10024
10025 // If snippet state is not at the last tabstop, push it back on the stack
10026 if snippet.active_index + 1 < snippet.ranges.len() {
10027 self.snippet_stack.push(snippet);
10028 }
10029 return true;
10030 }
10031 }
10032
10033 false
10034 }
10035
10036 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10037 self.transact(window, cx, |this, window, cx| {
10038 this.select_all(&SelectAll, window, cx);
10039 this.insert("", window, cx);
10040 });
10041 }
10042
10043 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10044 if self.read_only(cx) {
10045 return;
10046 }
10047 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10048 self.transact(window, cx, |this, window, cx| {
10049 this.select_autoclose_pair(window, cx);
10050
10051 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10052
10053 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10054 if !this.linked_edit_ranges.is_empty() {
10055 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10056 let snapshot = this.buffer.read(cx).snapshot(cx);
10057
10058 for selection in selections.iter() {
10059 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10060 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10061 if selection_start.buffer_id != selection_end.buffer_id {
10062 continue;
10063 }
10064 if let Some(ranges) =
10065 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10066 {
10067 for (buffer, entries) in ranges {
10068 linked_ranges.entry(buffer).or_default().extend(entries);
10069 }
10070 }
10071 }
10072 }
10073
10074 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10075 for selection in &mut selections {
10076 if selection.is_empty() {
10077 let old_head = selection.head();
10078 let mut new_head =
10079 movement::left(&display_map, old_head.to_display_point(&display_map))
10080 .to_point(&display_map);
10081 if let Some((buffer, line_buffer_range)) = display_map
10082 .buffer_snapshot()
10083 .buffer_line_for_row(MultiBufferRow(old_head.row))
10084 {
10085 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10086 let indent_len = match indent_size.kind {
10087 IndentKind::Space => {
10088 buffer.settings_at(line_buffer_range.start, cx).tab_size
10089 }
10090 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10091 };
10092 if old_head.column <= indent_size.len && old_head.column > 0 {
10093 let indent_len = indent_len.get();
10094 new_head = cmp::min(
10095 new_head,
10096 MultiBufferPoint::new(
10097 old_head.row,
10098 ((old_head.column - 1) / indent_len) * indent_len,
10099 ),
10100 );
10101 }
10102 }
10103
10104 selection.set_head(new_head, SelectionGoal::None);
10105 }
10106 }
10107
10108 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10109 this.insert("", window, cx);
10110 let empty_str: Arc<str> = Arc::from("");
10111 for (buffer, edits) in linked_ranges {
10112 let snapshot = buffer.read(cx).snapshot();
10113 use text::ToPoint as TP;
10114
10115 let edits = edits
10116 .into_iter()
10117 .map(|range| {
10118 let end_point = TP::to_point(&range.end, &snapshot);
10119 let mut start_point = TP::to_point(&range.start, &snapshot);
10120
10121 if end_point == start_point {
10122 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10123 .saturating_sub(1);
10124 start_point =
10125 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10126 };
10127
10128 (start_point..end_point, empty_str.clone())
10129 })
10130 .sorted_by_key(|(range, _)| range.start)
10131 .collect::<Vec<_>>();
10132 buffer.update(cx, |this, cx| {
10133 this.edit(edits, None, cx);
10134 })
10135 }
10136 this.refresh_edit_prediction(true, false, window, cx);
10137 refresh_linked_ranges(this, window, cx);
10138 });
10139 }
10140
10141 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10142 if self.read_only(cx) {
10143 return;
10144 }
10145 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10146 self.transact(window, cx, |this, window, cx| {
10147 this.change_selections(Default::default(), window, cx, |s| {
10148 s.move_with(|map, selection| {
10149 if selection.is_empty() {
10150 let cursor = movement::right(map, selection.head());
10151 selection.end = cursor;
10152 selection.reversed = true;
10153 selection.goal = SelectionGoal::None;
10154 }
10155 })
10156 });
10157 this.insert("", window, cx);
10158 this.refresh_edit_prediction(true, false, window, cx);
10159 });
10160 }
10161
10162 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10163 if self.mode.is_single_line() {
10164 cx.propagate();
10165 return;
10166 }
10167
10168 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10169 if self.move_to_prev_snippet_tabstop(window, cx) {
10170 return;
10171 }
10172 self.outdent(&Outdent, window, cx);
10173 }
10174
10175 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10176 if self.mode.is_single_line() {
10177 cx.propagate();
10178 return;
10179 }
10180
10181 if self.move_to_next_snippet_tabstop(window, cx) {
10182 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10183 return;
10184 }
10185 if self.read_only(cx) {
10186 return;
10187 }
10188 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10189 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10190 let buffer = self.buffer.read(cx);
10191 let snapshot = buffer.snapshot(cx);
10192 let rows_iter = selections.iter().map(|s| s.head().row);
10193 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10194
10195 let has_some_cursor_in_whitespace = selections
10196 .iter()
10197 .filter(|selection| selection.is_empty())
10198 .any(|selection| {
10199 let cursor = selection.head();
10200 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10201 cursor.column < current_indent.len
10202 });
10203
10204 let mut edits = Vec::new();
10205 let mut prev_edited_row = 0;
10206 let mut row_delta = 0;
10207 for selection in &mut selections {
10208 if selection.start.row != prev_edited_row {
10209 row_delta = 0;
10210 }
10211 prev_edited_row = selection.end.row;
10212
10213 // If the selection is non-empty, then increase the indentation of the selected lines.
10214 if !selection.is_empty() {
10215 row_delta =
10216 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10217 continue;
10218 }
10219
10220 let cursor = selection.head();
10221 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10222 if let Some(suggested_indent) =
10223 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10224 {
10225 // Don't do anything if already at suggested indent
10226 // and there is any other cursor which is not
10227 if has_some_cursor_in_whitespace
10228 && cursor.column == current_indent.len
10229 && current_indent.len == suggested_indent.len
10230 {
10231 continue;
10232 }
10233
10234 // Adjust line and move cursor to suggested indent
10235 // if cursor is not at suggested indent
10236 if cursor.column < suggested_indent.len
10237 && cursor.column <= current_indent.len
10238 && current_indent.len <= suggested_indent.len
10239 {
10240 selection.start = Point::new(cursor.row, suggested_indent.len);
10241 selection.end = selection.start;
10242 if row_delta == 0 {
10243 edits.extend(Buffer::edit_for_indent_size_adjustment(
10244 cursor.row,
10245 current_indent,
10246 suggested_indent,
10247 ));
10248 row_delta = suggested_indent.len - current_indent.len;
10249 }
10250 continue;
10251 }
10252
10253 // If current indent is more than suggested indent
10254 // only move cursor to current indent and skip indent
10255 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10256 selection.start = Point::new(cursor.row, current_indent.len);
10257 selection.end = selection.start;
10258 continue;
10259 }
10260 }
10261
10262 // Otherwise, insert a hard or soft tab.
10263 let settings = buffer.language_settings_at(cursor, cx);
10264 let tab_size = if settings.hard_tabs {
10265 IndentSize::tab()
10266 } else {
10267 let tab_size = settings.tab_size.get();
10268 let indent_remainder = snapshot
10269 .text_for_range(Point::new(cursor.row, 0)..cursor)
10270 .flat_map(str::chars)
10271 .fold(row_delta % tab_size, |counter: u32, c| {
10272 if c == '\t' {
10273 0
10274 } else {
10275 (counter + 1) % tab_size
10276 }
10277 });
10278
10279 let chars_to_next_tab_stop = tab_size - indent_remainder;
10280 IndentSize::spaces(chars_to_next_tab_stop)
10281 };
10282 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10283 selection.end = selection.start;
10284 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10285 row_delta += tab_size.len;
10286 }
10287
10288 self.transact(window, cx, |this, window, cx| {
10289 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10290 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10291 this.refresh_edit_prediction(true, false, window, cx);
10292 });
10293 }
10294
10295 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10296 if self.read_only(cx) {
10297 return;
10298 }
10299 if self.mode.is_single_line() {
10300 cx.propagate();
10301 return;
10302 }
10303
10304 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10305 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10306 let mut prev_edited_row = 0;
10307 let mut row_delta = 0;
10308 let mut edits = Vec::new();
10309 let buffer = self.buffer.read(cx);
10310 let snapshot = buffer.snapshot(cx);
10311 for selection in &mut selections {
10312 if selection.start.row != prev_edited_row {
10313 row_delta = 0;
10314 }
10315 prev_edited_row = selection.end.row;
10316
10317 row_delta =
10318 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10319 }
10320
10321 self.transact(window, cx, |this, window, cx| {
10322 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10323 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10324 });
10325 }
10326
10327 fn indent_selection(
10328 buffer: &MultiBuffer,
10329 snapshot: &MultiBufferSnapshot,
10330 selection: &mut Selection<Point>,
10331 edits: &mut Vec<(Range<Point>, String)>,
10332 delta_for_start_row: u32,
10333 cx: &App,
10334 ) -> u32 {
10335 let settings = buffer.language_settings_at(selection.start, cx);
10336 let tab_size = settings.tab_size.get();
10337 let indent_kind = if settings.hard_tabs {
10338 IndentKind::Tab
10339 } else {
10340 IndentKind::Space
10341 };
10342 let mut start_row = selection.start.row;
10343 let mut end_row = selection.end.row + 1;
10344
10345 // If a selection ends at the beginning of a line, don't indent
10346 // that last line.
10347 if selection.end.column == 0 && selection.end.row > selection.start.row {
10348 end_row -= 1;
10349 }
10350
10351 // Avoid re-indenting a row that has already been indented by a
10352 // previous selection, but still update this selection's column
10353 // to reflect that indentation.
10354 if delta_for_start_row > 0 {
10355 start_row += 1;
10356 selection.start.column += delta_for_start_row;
10357 if selection.end.row == selection.start.row {
10358 selection.end.column += delta_for_start_row;
10359 }
10360 }
10361
10362 let mut delta_for_end_row = 0;
10363 let has_multiple_rows = start_row + 1 != end_row;
10364 for row in start_row..end_row {
10365 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10366 let indent_delta = match (current_indent.kind, indent_kind) {
10367 (IndentKind::Space, IndentKind::Space) => {
10368 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10369 IndentSize::spaces(columns_to_next_tab_stop)
10370 }
10371 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10372 (_, IndentKind::Tab) => IndentSize::tab(),
10373 };
10374
10375 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10376 0
10377 } else {
10378 selection.start.column
10379 };
10380 let row_start = Point::new(row, start);
10381 edits.push((
10382 row_start..row_start,
10383 indent_delta.chars().collect::<String>(),
10384 ));
10385
10386 // Update this selection's endpoints to reflect the indentation.
10387 if row == selection.start.row {
10388 selection.start.column += indent_delta.len;
10389 }
10390 if row == selection.end.row {
10391 selection.end.column += indent_delta.len;
10392 delta_for_end_row = indent_delta.len;
10393 }
10394 }
10395
10396 if selection.start.row == selection.end.row {
10397 delta_for_start_row + delta_for_end_row
10398 } else {
10399 delta_for_end_row
10400 }
10401 }
10402
10403 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10404 if self.read_only(cx) {
10405 return;
10406 }
10407 if self.mode.is_single_line() {
10408 cx.propagate();
10409 return;
10410 }
10411
10412 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10413 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10414 let selections = self.selections.all::<Point>(&display_map);
10415 let mut deletion_ranges = Vec::new();
10416 let mut last_outdent = None;
10417 {
10418 let buffer = self.buffer.read(cx);
10419 let snapshot = buffer.snapshot(cx);
10420 for selection in &selections {
10421 let settings = buffer.language_settings_at(selection.start, cx);
10422 let tab_size = settings.tab_size.get();
10423 let mut rows = selection.spanned_rows(false, &display_map);
10424
10425 // Avoid re-outdenting a row that has already been outdented by a
10426 // previous selection.
10427 if let Some(last_row) = last_outdent
10428 && last_row == rows.start
10429 {
10430 rows.start = rows.start.next_row();
10431 }
10432 let has_multiple_rows = rows.len() > 1;
10433 for row in rows.iter_rows() {
10434 let indent_size = snapshot.indent_size_for_line(row);
10435 if indent_size.len > 0 {
10436 let deletion_len = match indent_size.kind {
10437 IndentKind::Space => {
10438 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10439 if columns_to_prev_tab_stop == 0 {
10440 tab_size
10441 } else {
10442 columns_to_prev_tab_stop
10443 }
10444 }
10445 IndentKind::Tab => 1,
10446 };
10447 let start = if has_multiple_rows
10448 || deletion_len > selection.start.column
10449 || indent_size.len < selection.start.column
10450 {
10451 0
10452 } else {
10453 selection.start.column - deletion_len
10454 };
10455 deletion_ranges.push(
10456 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10457 );
10458 last_outdent = Some(row);
10459 }
10460 }
10461 }
10462 }
10463
10464 self.transact(window, cx, |this, window, cx| {
10465 this.buffer.update(cx, |buffer, cx| {
10466 let empty_str: Arc<str> = Arc::default();
10467 buffer.edit(
10468 deletion_ranges
10469 .into_iter()
10470 .map(|range| (range, empty_str.clone())),
10471 None,
10472 cx,
10473 );
10474 });
10475 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10476 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10477 });
10478 }
10479
10480 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10481 if self.read_only(cx) {
10482 return;
10483 }
10484 if self.mode.is_single_line() {
10485 cx.propagate();
10486 return;
10487 }
10488
10489 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10490 let selections = self
10491 .selections
10492 .all::<usize>(&self.display_snapshot(cx))
10493 .into_iter()
10494 .map(|s| s.range());
10495
10496 self.transact(window, cx, |this, window, cx| {
10497 this.buffer.update(cx, |buffer, cx| {
10498 buffer.autoindent_ranges(selections, cx);
10499 });
10500 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10501 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10502 });
10503 }
10504
10505 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10506 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10507 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10508 let selections = self.selections.all::<Point>(&display_map);
10509
10510 let mut new_cursors = Vec::new();
10511 let mut edit_ranges = Vec::new();
10512 let mut selections = selections.iter().peekable();
10513 while let Some(selection) = selections.next() {
10514 let mut rows = selection.spanned_rows(false, &display_map);
10515
10516 // Accumulate contiguous regions of rows that we want to delete.
10517 while let Some(next_selection) = selections.peek() {
10518 let next_rows = next_selection.spanned_rows(false, &display_map);
10519 if next_rows.start <= rows.end {
10520 rows.end = next_rows.end;
10521 selections.next().unwrap();
10522 } else {
10523 break;
10524 }
10525 }
10526
10527 let buffer = display_map.buffer_snapshot();
10528 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10529 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10530 // If there's a line after the range, delete the \n from the end of the row range
10531 (
10532 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10533 rows.end,
10534 )
10535 } else {
10536 // If there isn't a line after the range, delete the \n from the line before the
10537 // start of the row range
10538 edit_start = edit_start.saturating_sub(1);
10539 (buffer.len(), rows.start.previous_row())
10540 };
10541
10542 let text_layout_details = self.text_layout_details(window);
10543 let x = display_map.x_for_display_point(
10544 selection.head().to_display_point(&display_map),
10545 &text_layout_details,
10546 );
10547 let row = Point::new(target_row.0, 0)
10548 .to_display_point(&display_map)
10549 .row();
10550 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10551
10552 new_cursors.push((
10553 selection.id,
10554 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10555 SelectionGoal::None,
10556 ));
10557 edit_ranges.push(edit_start..edit_end);
10558 }
10559
10560 self.transact(window, cx, |this, window, cx| {
10561 let buffer = this.buffer.update(cx, |buffer, cx| {
10562 let empty_str: Arc<str> = Arc::default();
10563 buffer.edit(
10564 edit_ranges
10565 .into_iter()
10566 .map(|range| (range, empty_str.clone())),
10567 None,
10568 cx,
10569 );
10570 buffer.snapshot(cx)
10571 });
10572 let new_selections = new_cursors
10573 .into_iter()
10574 .map(|(id, cursor, goal)| {
10575 let cursor = cursor.to_point(&buffer);
10576 Selection {
10577 id,
10578 start: cursor,
10579 end: cursor,
10580 reversed: false,
10581 goal,
10582 }
10583 })
10584 .collect();
10585
10586 this.change_selections(Default::default(), window, cx, |s| {
10587 s.select(new_selections);
10588 });
10589 });
10590 }
10591
10592 pub fn join_lines_impl(
10593 &mut self,
10594 insert_whitespace: bool,
10595 window: &mut Window,
10596 cx: &mut Context<Self>,
10597 ) {
10598 if self.read_only(cx) {
10599 return;
10600 }
10601 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10602 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10603 let start = MultiBufferRow(selection.start.row);
10604 // Treat single line selections as if they include the next line. Otherwise this action
10605 // would do nothing for single line selections individual cursors.
10606 let end = if selection.start.row == selection.end.row {
10607 MultiBufferRow(selection.start.row + 1)
10608 } else {
10609 MultiBufferRow(selection.end.row)
10610 };
10611
10612 if let Some(last_row_range) = row_ranges.last_mut()
10613 && start <= last_row_range.end
10614 {
10615 last_row_range.end = end;
10616 continue;
10617 }
10618 row_ranges.push(start..end);
10619 }
10620
10621 let snapshot = self.buffer.read(cx).snapshot(cx);
10622 let mut cursor_positions = Vec::new();
10623 for row_range in &row_ranges {
10624 let anchor = snapshot.anchor_before(Point::new(
10625 row_range.end.previous_row().0,
10626 snapshot.line_len(row_range.end.previous_row()),
10627 ));
10628 cursor_positions.push(anchor..anchor);
10629 }
10630
10631 self.transact(window, cx, |this, window, cx| {
10632 for row_range in row_ranges.into_iter().rev() {
10633 for row in row_range.iter_rows().rev() {
10634 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10635 let next_line_row = row.next_row();
10636 let indent = snapshot.indent_size_for_line(next_line_row);
10637 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10638
10639 let replace =
10640 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10641 " "
10642 } else {
10643 ""
10644 };
10645
10646 this.buffer.update(cx, |buffer, cx| {
10647 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10648 });
10649 }
10650 }
10651
10652 this.change_selections(Default::default(), window, cx, |s| {
10653 s.select_anchor_ranges(cursor_positions)
10654 });
10655 });
10656 }
10657
10658 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10659 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10660 self.join_lines_impl(true, window, cx);
10661 }
10662
10663 pub fn sort_lines_case_sensitive(
10664 &mut self,
10665 _: &SortLinesCaseSensitive,
10666 window: &mut Window,
10667 cx: &mut Context<Self>,
10668 ) {
10669 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10670 }
10671
10672 pub fn sort_lines_by_length(
10673 &mut self,
10674 _: &SortLinesByLength,
10675 window: &mut Window,
10676 cx: &mut Context<Self>,
10677 ) {
10678 self.manipulate_immutable_lines(window, cx, |lines| {
10679 lines.sort_by_key(|&line| line.chars().count())
10680 })
10681 }
10682
10683 pub fn sort_lines_case_insensitive(
10684 &mut self,
10685 _: &SortLinesCaseInsensitive,
10686 window: &mut Window,
10687 cx: &mut Context<Self>,
10688 ) {
10689 self.manipulate_immutable_lines(window, cx, |lines| {
10690 lines.sort_by_key(|line| line.to_lowercase())
10691 })
10692 }
10693
10694 pub fn unique_lines_case_insensitive(
10695 &mut self,
10696 _: &UniqueLinesCaseInsensitive,
10697 window: &mut Window,
10698 cx: &mut Context<Self>,
10699 ) {
10700 self.manipulate_immutable_lines(window, cx, |lines| {
10701 let mut seen = HashSet::default();
10702 lines.retain(|line| seen.insert(line.to_lowercase()));
10703 })
10704 }
10705
10706 pub fn unique_lines_case_sensitive(
10707 &mut self,
10708 _: &UniqueLinesCaseSensitive,
10709 window: &mut Window,
10710 cx: &mut Context<Self>,
10711 ) {
10712 self.manipulate_immutable_lines(window, cx, |lines| {
10713 let mut seen = HashSet::default();
10714 lines.retain(|line| seen.insert(*line));
10715 })
10716 }
10717
10718 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10719 let snapshot = self.buffer.read(cx).snapshot(cx);
10720 for selection in self.selections.disjoint_anchors_arc().iter() {
10721 if snapshot
10722 .language_at(selection.start)
10723 .and_then(|lang| lang.config().wrap_characters.as_ref())
10724 .is_some()
10725 {
10726 return true;
10727 }
10728 }
10729 false
10730 }
10731
10732 fn wrap_selections_in_tag(
10733 &mut self,
10734 _: &WrapSelectionsInTag,
10735 window: &mut Window,
10736 cx: &mut Context<Self>,
10737 ) {
10738 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10739
10740 let snapshot = self.buffer.read(cx).snapshot(cx);
10741
10742 let mut edits = Vec::new();
10743 let mut boundaries = Vec::new();
10744
10745 for selection in self
10746 .selections
10747 .all::<Point>(&self.display_snapshot(cx))
10748 .iter()
10749 {
10750 let Some(wrap_config) = snapshot
10751 .language_at(selection.start)
10752 .and_then(|lang| lang.config().wrap_characters.clone())
10753 else {
10754 continue;
10755 };
10756
10757 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10758 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10759
10760 let start_before = snapshot.anchor_before(selection.start);
10761 let end_after = snapshot.anchor_after(selection.end);
10762
10763 edits.push((start_before..start_before, open_tag));
10764 edits.push((end_after..end_after, close_tag));
10765
10766 boundaries.push((
10767 start_before,
10768 end_after,
10769 wrap_config.start_prefix.len(),
10770 wrap_config.end_suffix.len(),
10771 ));
10772 }
10773
10774 if edits.is_empty() {
10775 return;
10776 }
10777
10778 self.transact(window, cx, |this, window, cx| {
10779 let buffer = this.buffer.update(cx, |buffer, cx| {
10780 buffer.edit(edits, None, cx);
10781 buffer.snapshot(cx)
10782 });
10783
10784 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10785 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10786 boundaries.into_iter()
10787 {
10788 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10789 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10790 new_selections.push(open_offset..open_offset);
10791 new_selections.push(close_offset..close_offset);
10792 }
10793
10794 this.change_selections(Default::default(), window, cx, |s| {
10795 s.select_ranges(new_selections);
10796 });
10797
10798 this.request_autoscroll(Autoscroll::fit(), cx);
10799 });
10800 }
10801
10802 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10803 let Some(project) = self.project.clone() else {
10804 return;
10805 };
10806 self.reload(project, window, cx)
10807 .detach_and_notify_err(window, cx);
10808 }
10809
10810 pub fn restore_file(
10811 &mut self,
10812 _: &::git::RestoreFile,
10813 window: &mut Window,
10814 cx: &mut Context<Self>,
10815 ) {
10816 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10817 let mut buffer_ids = HashSet::default();
10818 let snapshot = self.buffer().read(cx).snapshot(cx);
10819 for selection in self.selections.all::<usize>(&self.display_snapshot(cx)) {
10820 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10821 }
10822
10823 let buffer = self.buffer().read(cx);
10824 let ranges = buffer_ids
10825 .into_iter()
10826 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10827 .collect::<Vec<_>>();
10828
10829 self.restore_hunks_in_ranges(ranges, window, cx);
10830 }
10831
10832 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10833 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10834 let selections = self
10835 .selections
10836 .all(&self.display_snapshot(cx))
10837 .into_iter()
10838 .map(|s| s.range())
10839 .collect();
10840 self.restore_hunks_in_ranges(selections, window, cx);
10841 }
10842
10843 pub fn restore_hunks_in_ranges(
10844 &mut self,
10845 ranges: Vec<Range<Point>>,
10846 window: &mut Window,
10847 cx: &mut Context<Editor>,
10848 ) {
10849 let mut revert_changes = HashMap::default();
10850 let chunk_by = self
10851 .snapshot(window, cx)
10852 .hunks_for_ranges(ranges)
10853 .into_iter()
10854 .chunk_by(|hunk| hunk.buffer_id);
10855 for (buffer_id, hunks) in &chunk_by {
10856 let hunks = hunks.collect::<Vec<_>>();
10857 for hunk in &hunks {
10858 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10859 }
10860 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10861 }
10862 drop(chunk_by);
10863 if !revert_changes.is_empty() {
10864 self.transact(window, cx, |editor, window, cx| {
10865 editor.restore(revert_changes, window, cx);
10866 });
10867 }
10868 }
10869
10870 pub fn open_active_item_in_terminal(
10871 &mut self,
10872 _: &OpenInTerminal,
10873 window: &mut Window,
10874 cx: &mut Context<Self>,
10875 ) {
10876 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10877 let project_path = buffer.read(cx).project_path(cx)?;
10878 let project = self.project()?.read(cx);
10879 let entry = project.entry_for_path(&project_path, cx)?;
10880 let parent = match &entry.canonical_path {
10881 Some(canonical_path) => canonical_path.to_path_buf(),
10882 None => project.absolute_path(&project_path, cx)?,
10883 }
10884 .parent()?
10885 .to_path_buf();
10886 Some(parent)
10887 }) {
10888 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10889 }
10890 }
10891
10892 fn set_breakpoint_context_menu(
10893 &mut self,
10894 display_row: DisplayRow,
10895 position: Option<Anchor>,
10896 clicked_point: gpui::Point<Pixels>,
10897 window: &mut Window,
10898 cx: &mut Context<Self>,
10899 ) {
10900 let source = self
10901 .buffer
10902 .read(cx)
10903 .snapshot(cx)
10904 .anchor_before(Point::new(display_row.0, 0u32));
10905
10906 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10907
10908 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10909 self,
10910 source,
10911 clicked_point,
10912 context_menu,
10913 window,
10914 cx,
10915 );
10916 }
10917
10918 fn add_edit_breakpoint_block(
10919 &mut self,
10920 anchor: Anchor,
10921 breakpoint: &Breakpoint,
10922 edit_action: BreakpointPromptEditAction,
10923 window: &mut Window,
10924 cx: &mut Context<Self>,
10925 ) {
10926 let weak_editor = cx.weak_entity();
10927 let bp_prompt = cx.new(|cx| {
10928 BreakpointPromptEditor::new(
10929 weak_editor,
10930 anchor,
10931 breakpoint.clone(),
10932 edit_action,
10933 window,
10934 cx,
10935 )
10936 });
10937
10938 let height = bp_prompt.update(cx, |this, cx| {
10939 this.prompt
10940 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10941 });
10942 let cloned_prompt = bp_prompt.clone();
10943 let blocks = vec![BlockProperties {
10944 style: BlockStyle::Sticky,
10945 placement: BlockPlacement::Above(anchor),
10946 height: Some(height),
10947 render: Arc::new(move |cx| {
10948 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10949 cloned_prompt.clone().into_any_element()
10950 }),
10951 priority: 0,
10952 }];
10953
10954 let focus_handle = bp_prompt.focus_handle(cx);
10955 window.focus(&focus_handle);
10956
10957 let block_ids = self.insert_blocks(blocks, None, cx);
10958 bp_prompt.update(cx, |prompt, _| {
10959 prompt.add_block_ids(block_ids);
10960 });
10961 }
10962
10963 pub(crate) fn breakpoint_at_row(
10964 &self,
10965 row: u32,
10966 window: &mut Window,
10967 cx: &mut Context<Self>,
10968 ) -> Option<(Anchor, Breakpoint)> {
10969 let snapshot = self.snapshot(window, cx);
10970 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
10971
10972 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10973 }
10974
10975 pub(crate) fn breakpoint_at_anchor(
10976 &self,
10977 breakpoint_position: Anchor,
10978 snapshot: &EditorSnapshot,
10979 cx: &mut Context<Self>,
10980 ) -> Option<(Anchor, Breakpoint)> {
10981 let buffer = self
10982 .buffer
10983 .read(cx)
10984 .buffer_for_anchor(breakpoint_position, cx)?;
10985
10986 let enclosing_excerpt = breakpoint_position.excerpt_id;
10987 let buffer_snapshot = buffer.read(cx).snapshot();
10988
10989 let row = buffer_snapshot
10990 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10991 .row;
10992
10993 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
10994 let anchor_end = snapshot
10995 .buffer_snapshot()
10996 .anchor_after(Point::new(row, line_len));
10997
10998 self.breakpoint_store
10999 .as_ref()?
11000 .read_with(cx, |breakpoint_store, cx| {
11001 breakpoint_store
11002 .breakpoints(
11003 &buffer,
11004 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11005 &buffer_snapshot,
11006 cx,
11007 )
11008 .next()
11009 .and_then(|(bp, _)| {
11010 let breakpoint_row = buffer_snapshot
11011 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11012 .row;
11013
11014 if breakpoint_row == row {
11015 snapshot
11016 .buffer_snapshot()
11017 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11018 .map(|position| (position, bp.bp.clone()))
11019 } else {
11020 None
11021 }
11022 })
11023 })
11024 }
11025
11026 pub fn edit_log_breakpoint(
11027 &mut self,
11028 _: &EditLogBreakpoint,
11029 window: &mut Window,
11030 cx: &mut Context<Self>,
11031 ) {
11032 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11033 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11034 message: None,
11035 state: BreakpointState::Enabled,
11036 condition: None,
11037 hit_condition: None,
11038 });
11039
11040 self.add_edit_breakpoint_block(
11041 anchor,
11042 &breakpoint,
11043 BreakpointPromptEditAction::Log,
11044 window,
11045 cx,
11046 );
11047 }
11048 }
11049
11050 fn breakpoints_at_cursors(
11051 &self,
11052 window: &mut Window,
11053 cx: &mut Context<Self>,
11054 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11055 let snapshot = self.snapshot(window, cx);
11056 let cursors = self
11057 .selections
11058 .disjoint_anchors_arc()
11059 .iter()
11060 .map(|selection| {
11061 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11062
11063 let breakpoint_position = self
11064 .breakpoint_at_row(cursor_position.row, window, cx)
11065 .map(|bp| bp.0)
11066 .unwrap_or_else(|| {
11067 snapshot
11068 .display_snapshot
11069 .buffer_snapshot()
11070 .anchor_after(Point::new(cursor_position.row, 0))
11071 });
11072
11073 let breakpoint = self
11074 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11075 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11076
11077 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11078 })
11079 // There might be multiple cursors on the same line; all of them should have the same anchors though as their breakpoints positions, which makes it possible to sort and dedup the list.
11080 .collect::<HashMap<Anchor, _>>();
11081
11082 cursors.into_iter().collect()
11083 }
11084
11085 pub fn enable_breakpoint(
11086 &mut self,
11087 _: &crate::actions::EnableBreakpoint,
11088 window: &mut Window,
11089 cx: &mut Context<Self>,
11090 ) {
11091 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11092 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11093 continue;
11094 };
11095 self.edit_breakpoint_at_anchor(
11096 anchor,
11097 breakpoint,
11098 BreakpointEditAction::InvertState,
11099 cx,
11100 );
11101 }
11102 }
11103
11104 pub fn disable_breakpoint(
11105 &mut self,
11106 _: &crate::actions::DisableBreakpoint,
11107 window: &mut Window,
11108 cx: &mut Context<Self>,
11109 ) {
11110 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11111 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11112 continue;
11113 };
11114 self.edit_breakpoint_at_anchor(
11115 anchor,
11116 breakpoint,
11117 BreakpointEditAction::InvertState,
11118 cx,
11119 );
11120 }
11121 }
11122
11123 pub fn toggle_breakpoint(
11124 &mut self,
11125 _: &crate::actions::ToggleBreakpoint,
11126 window: &mut Window,
11127 cx: &mut Context<Self>,
11128 ) {
11129 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11130 if let Some(breakpoint) = breakpoint {
11131 self.edit_breakpoint_at_anchor(
11132 anchor,
11133 breakpoint,
11134 BreakpointEditAction::Toggle,
11135 cx,
11136 );
11137 } else {
11138 self.edit_breakpoint_at_anchor(
11139 anchor,
11140 Breakpoint::new_standard(),
11141 BreakpointEditAction::Toggle,
11142 cx,
11143 );
11144 }
11145 }
11146 }
11147
11148 pub fn edit_breakpoint_at_anchor(
11149 &mut self,
11150 breakpoint_position: Anchor,
11151 breakpoint: Breakpoint,
11152 edit_action: BreakpointEditAction,
11153 cx: &mut Context<Self>,
11154 ) {
11155 let Some(breakpoint_store) = &self.breakpoint_store else {
11156 return;
11157 };
11158
11159 let Some(buffer) = self
11160 .buffer
11161 .read(cx)
11162 .buffer_for_anchor(breakpoint_position, cx)
11163 else {
11164 return;
11165 };
11166
11167 breakpoint_store.update(cx, |breakpoint_store, cx| {
11168 breakpoint_store.toggle_breakpoint(
11169 buffer,
11170 BreakpointWithPosition {
11171 position: breakpoint_position.text_anchor,
11172 bp: breakpoint,
11173 },
11174 edit_action,
11175 cx,
11176 );
11177 });
11178
11179 cx.notify();
11180 }
11181
11182 #[cfg(any(test, feature = "test-support"))]
11183 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11184 self.breakpoint_store.clone()
11185 }
11186
11187 pub fn prepare_restore_change(
11188 &self,
11189 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11190 hunk: &MultiBufferDiffHunk,
11191 cx: &mut App,
11192 ) -> Option<()> {
11193 if hunk.is_created_file() {
11194 return None;
11195 }
11196 let buffer = self.buffer.read(cx);
11197 let diff = buffer.diff_for(hunk.buffer_id)?;
11198 let buffer = buffer.buffer(hunk.buffer_id)?;
11199 let buffer = buffer.read(cx);
11200 let original_text = diff
11201 .read(cx)
11202 .base_text()
11203 .as_rope()
11204 .slice(hunk.diff_base_byte_range.clone());
11205 let buffer_snapshot = buffer.snapshot();
11206 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11207 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11208 probe
11209 .0
11210 .start
11211 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11212 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11213 }) {
11214 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11215 Some(())
11216 } else {
11217 None
11218 }
11219 }
11220
11221 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11222 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11223 }
11224
11225 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11226 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11227 }
11228
11229 fn manipulate_lines<M>(
11230 &mut self,
11231 window: &mut Window,
11232 cx: &mut Context<Self>,
11233 mut manipulate: M,
11234 ) where
11235 M: FnMut(&str) -> LineManipulationResult,
11236 {
11237 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11238
11239 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11240 let buffer = self.buffer.read(cx).snapshot(cx);
11241
11242 let mut edits = Vec::new();
11243
11244 let selections = self.selections.all::<Point>(&display_map);
11245 let mut selections = selections.iter().peekable();
11246 let mut contiguous_row_selections = Vec::new();
11247 let mut new_selections = Vec::new();
11248 let mut added_lines = 0;
11249 let mut removed_lines = 0;
11250
11251 while let Some(selection) = selections.next() {
11252 let (start_row, end_row) = consume_contiguous_rows(
11253 &mut contiguous_row_selections,
11254 selection,
11255 &display_map,
11256 &mut selections,
11257 );
11258
11259 let start_point = Point::new(start_row.0, 0);
11260 let end_point = Point::new(
11261 end_row.previous_row().0,
11262 buffer.line_len(end_row.previous_row()),
11263 );
11264 let text = buffer
11265 .text_for_range(start_point..end_point)
11266 .collect::<String>();
11267
11268 let LineManipulationResult {
11269 new_text,
11270 line_count_before,
11271 line_count_after,
11272 } = manipulate(&text);
11273
11274 edits.push((start_point..end_point, new_text));
11275
11276 // Selections must change based on added and removed line count
11277 let start_row =
11278 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11279 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11280 new_selections.push(Selection {
11281 id: selection.id,
11282 start: start_row,
11283 end: end_row,
11284 goal: SelectionGoal::None,
11285 reversed: selection.reversed,
11286 });
11287
11288 if line_count_after > line_count_before {
11289 added_lines += line_count_after - line_count_before;
11290 } else if line_count_before > line_count_after {
11291 removed_lines += line_count_before - line_count_after;
11292 }
11293 }
11294
11295 self.transact(window, cx, |this, window, cx| {
11296 let buffer = this.buffer.update(cx, |buffer, cx| {
11297 buffer.edit(edits, None, cx);
11298 buffer.snapshot(cx)
11299 });
11300
11301 // Recalculate offsets on newly edited buffer
11302 let new_selections = new_selections
11303 .iter()
11304 .map(|s| {
11305 let start_point = Point::new(s.start.0, 0);
11306 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11307 Selection {
11308 id: s.id,
11309 start: buffer.point_to_offset(start_point),
11310 end: buffer.point_to_offset(end_point),
11311 goal: s.goal,
11312 reversed: s.reversed,
11313 }
11314 })
11315 .collect();
11316
11317 this.change_selections(Default::default(), window, cx, |s| {
11318 s.select(new_selections);
11319 });
11320
11321 this.request_autoscroll(Autoscroll::fit(), cx);
11322 });
11323 }
11324
11325 fn manipulate_immutable_lines<Fn>(
11326 &mut self,
11327 window: &mut Window,
11328 cx: &mut Context<Self>,
11329 mut callback: Fn,
11330 ) where
11331 Fn: FnMut(&mut Vec<&str>),
11332 {
11333 self.manipulate_lines(window, cx, |text| {
11334 let mut lines: Vec<&str> = text.split('\n').collect();
11335 let line_count_before = lines.len();
11336
11337 callback(&mut lines);
11338
11339 LineManipulationResult {
11340 new_text: lines.join("\n"),
11341 line_count_before,
11342 line_count_after: lines.len(),
11343 }
11344 });
11345 }
11346
11347 fn manipulate_mutable_lines<Fn>(
11348 &mut self,
11349 window: &mut Window,
11350 cx: &mut Context<Self>,
11351 mut callback: Fn,
11352 ) where
11353 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11354 {
11355 self.manipulate_lines(window, cx, |text| {
11356 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11357 let line_count_before = lines.len();
11358
11359 callback(&mut lines);
11360
11361 LineManipulationResult {
11362 new_text: lines.join("\n"),
11363 line_count_before,
11364 line_count_after: lines.len(),
11365 }
11366 });
11367 }
11368
11369 pub fn convert_indentation_to_spaces(
11370 &mut self,
11371 _: &ConvertIndentationToSpaces,
11372 window: &mut Window,
11373 cx: &mut Context<Self>,
11374 ) {
11375 let settings = self.buffer.read(cx).language_settings(cx);
11376 let tab_size = settings.tab_size.get() as usize;
11377
11378 self.manipulate_mutable_lines(window, cx, |lines| {
11379 // Allocates a reasonably sized scratch buffer once for the whole loop
11380 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11381 // Avoids recomputing spaces that could be inserted many times
11382 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11383 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11384 .collect();
11385
11386 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11387 let mut chars = line.as_ref().chars();
11388 let mut col = 0;
11389 let mut changed = false;
11390
11391 for ch in chars.by_ref() {
11392 match ch {
11393 ' ' => {
11394 reindented_line.push(' ');
11395 col += 1;
11396 }
11397 '\t' => {
11398 // \t are converted to spaces depending on the current column
11399 let spaces_len = tab_size - (col % tab_size);
11400 reindented_line.extend(&space_cache[spaces_len - 1]);
11401 col += spaces_len;
11402 changed = true;
11403 }
11404 _ => {
11405 // If we dont append before break, the character is consumed
11406 reindented_line.push(ch);
11407 break;
11408 }
11409 }
11410 }
11411
11412 if !changed {
11413 reindented_line.clear();
11414 continue;
11415 }
11416 // Append the rest of the line and replace old reference with new one
11417 reindented_line.extend(chars);
11418 *line = Cow::Owned(reindented_line.clone());
11419 reindented_line.clear();
11420 }
11421 });
11422 }
11423
11424 pub fn convert_indentation_to_tabs(
11425 &mut self,
11426 _: &ConvertIndentationToTabs,
11427 window: &mut Window,
11428 cx: &mut Context<Self>,
11429 ) {
11430 let settings = self.buffer.read(cx).language_settings(cx);
11431 let tab_size = settings.tab_size.get() as usize;
11432
11433 self.manipulate_mutable_lines(window, cx, |lines| {
11434 // Allocates a reasonably sized buffer once for the whole loop
11435 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11436 // Avoids recomputing spaces that could be inserted many times
11437 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11438 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11439 .collect();
11440
11441 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11442 let mut chars = line.chars();
11443 let mut spaces_count = 0;
11444 let mut first_non_indent_char = None;
11445 let mut changed = false;
11446
11447 for ch in chars.by_ref() {
11448 match ch {
11449 ' ' => {
11450 // Keep track of spaces. Append \t when we reach tab_size
11451 spaces_count += 1;
11452 changed = true;
11453 if spaces_count == tab_size {
11454 reindented_line.push('\t');
11455 spaces_count = 0;
11456 }
11457 }
11458 '\t' => {
11459 reindented_line.push('\t');
11460 spaces_count = 0;
11461 }
11462 _ => {
11463 // Dont append it yet, we might have remaining spaces
11464 first_non_indent_char = Some(ch);
11465 break;
11466 }
11467 }
11468 }
11469
11470 if !changed {
11471 reindented_line.clear();
11472 continue;
11473 }
11474 // Remaining spaces that didn't make a full tab stop
11475 if spaces_count > 0 {
11476 reindented_line.extend(&space_cache[spaces_count - 1]);
11477 }
11478 // If we consume an extra character that was not indentation, add it back
11479 if let Some(extra_char) = first_non_indent_char {
11480 reindented_line.push(extra_char);
11481 }
11482 // Append the rest of the line and replace old reference with new one
11483 reindented_line.extend(chars);
11484 *line = Cow::Owned(reindented_line.clone());
11485 reindented_line.clear();
11486 }
11487 });
11488 }
11489
11490 pub fn convert_to_upper_case(
11491 &mut self,
11492 _: &ConvertToUpperCase,
11493 window: &mut Window,
11494 cx: &mut Context<Self>,
11495 ) {
11496 self.manipulate_text(window, cx, |text| text.to_uppercase())
11497 }
11498
11499 pub fn convert_to_lower_case(
11500 &mut self,
11501 _: &ConvertToLowerCase,
11502 window: &mut Window,
11503 cx: &mut Context<Self>,
11504 ) {
11505 self.manipulate_text(window, cx, |text| text.to_lowercase())
11506 }
11507
11508 pub fn convert_to_title_case(
11509 &mut self,
11510 _: &ConvertToTitleCase,
11511 window: &mut Window,
11512 cx: &mut Context<Self>,
11513 ) {
11514 self.manipulate_text(window, cx, |text| {
11515 text.split('\n')
11516 .map(|line| line.to_case(Case::Title))
11517 .join("\n")
11518 })
11519 }
11520
11521 pub fn convert_to_snake_case(
11522 &mut self,
11523 _: &ConvertToSnakeCase,
11524 window: &mut Window,
11525 cx: &mut Context<Self>,
11526 ) {
11527 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11528 }
11529
11530 pub fn convert_to_kebab_case(
11531 &mut self,
11532 _: &ConvertToKebabCase,
11533 window: &mut Window,
11534 cx: &mut Context<Self>,
11535 ) {
11536 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11537 }
11538
11539 pub fn convert_to_upper_camel_case(
11540 &mut self,
11541 _: &ConvertToUpperCamelCase,
11542 window: &mut Window,
11543 cx: &mut Context<Self>,
11544 ) {
11545 self.manipulate_text(window, cx, |text| {
11546 text.split('\n')
11547 .map(|line| line.to_case(Case::UpperCamel))
11548 .join("\n")
11549 })
11550 }
11551
11552 pub fn convert_to_lower_camel_case(
11553 &mut self,
11554 _: &ConvertToLowerCamelCase,
11555 window: &mut Window,
11556 cx: &mut Context<Self>,
11557 ) {
11558 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11559 }
11560
11561 pub fn convert_to_opposite_case(
11562 &mut self,
11563 _: &ConvertToOppositeCase,
11564 window: &mut Window,
11565 cx: &mut Context<Self>,
11566 ) {
11567 self.manipulate_text(window, cx, |text| {
11568 text.chars()
11569 .fold(String::with_capacity(text.len()), |mut t, c| {
11570 if c.is_uppercase() {
11571 t.extend(c.to_lowercase());
11572 } else {
11573 t.extend(c.to_uppercase());
11574 }
11575 t
11576 })
11577 })
11578 }
11579
11580 pub fn convert_to_sentence_case(
11581 &mut self,
11582 _: &ConvertToSentenceCase,
11583 window: &mut Window,
11584 cx: &mut Context<Self>,
11585 ) {
11586 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11587 }
11588
11589 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11590 self.manipulate_text(window, cx, |text| {
11591 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11592 if has_upper_case_characters {
11593 text.to_lowercase()
11594 } else {
11595 text.to_uppercase()
11596 }
11597 })
11598 }
11599
11600 pub fn convert_to_rot13(
11601 &mut self,
11602 _: &ConvertToRot13,
11603 window: &mut Window,
11604 cx: &mut Context<Self>,
11605 ) {
11606 self.manipulate_text(window, cx, |text| {
11607 text.chars()
11608 .map(|c| match c {
11609 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11610 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11611 _ => c,
11612 })
11613 .collect()
11614 })
11615 }
11616
11617 pub fn convert_to_rot47(
11618 &mut self,
11619 _: &ConvertToRot47,
11620 window: &mut Window,
11621 cx: &mut Context<Self>,
11622 ) {
11623 self.manipulate_text(window, cx, |text| {
11624 text.chars()
11625 .map(|c| {
11626 let code_point = c as u32;
11627 if code_point >= 33 && code_point <= 126 {
11628 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11629 }
11630 c
11631 })
11632 .collect()
11633 })
11634 }
11635
11636 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11637 where
11638 Fn: FnMut(&str) -> String,
11639 {
11640 let buffer = self.buffer.read(cx).snapshot(cx);
11641
11642 let mut new_selections = Vec::new();
11643 let mut edits = Vec::new();
11644 let mut selection_adjustment = 0i32;
11645
11646 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11647 let selection_is_empty = selection.is_empty();
11648
11649 let (start, end) = if selection_is_empty {
11650 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11651 (word_range.start, word_range.end)
11652 } else {
11653 (
11654 buffer.point_to_offset(selection.start),
11655 buffer.point_to_offset(selection.end),
11656 )
11657 };
11658
11659 let text = buffer.text_for_range(start..end).collect::<String>();
11660 let old_length = text.len() as i32;
11661 let text = callback(&text);
11662
11663 new_selections.push(Selection {
11664 start: (start as i32 - selection_adjustment) as usize,
11665 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11666 goal: SelectionGoal::None,
11667 id: selection.id,
11668 reversed: selection.reversed,
11669 });
11670
11671 selection_adjustment += old_length - text.len() as i32;
11672
11673 edits.push((start..end, text));
11674 }
11675
11676 self.transact(window, cx, |this, window, cx| {
11677 this.buffer.update(cx, |buffer, cx| {
11678 buffer.edit(edits, None, cx);
11679 });
11680
11681 this.change_selections(Default::default(), window, cx, |s| {
11682 s.select(new_selections);
11683 });
11684
11685 this.request_autoscroll(Autoscroll::fit(), cx);
11686 });
11687 }
11688
11689 pub fn move_selection_on_drop(
11690 &mut self,
11691 selection: &Selection<Anchor>,
11692 target: DisplayPoint,
11693 is_cut: bool,
11694 window: &mut Window,
11695 cx: &mut Context<Self>,
11696 ) {
11697 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11698 let buffer = display_map.buffer_snapshot();
11699 let mut edits = Vec::new();
11700 let insert_point = display_map
11701 .clip_point(target, Bias::Left)
11702 .to_point(&display_map);
11703 let text = buffer
11704 .text_for_range(selection.start..selection.end)
11705 .collect::<String>();
11706 if is_cut {
11707 edits.push(((selection.start..selection.end), String::new()));
11708 }
11709 let insert_anchor = buffer.anchor_before(insert_point);
11710 edits.push(((insert_anchor..insert_anchor), text));
11711 let last_edit_start = insert_anchor.bias_left(buffer);
11712 let last_edit_end = insert_anchor.bias_right(buffer);
11713 self.transact(window, cx, |this, window, cx| {
11714 this.buffer.update(cx, |buffer, cx| {
11715 buffer.edit(edits, None, cx);
11716 });
11717 this.change_selections(Default::default(), window, cx, |s| {
11718 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11719 });
11720 });
11721 }
11722
11723 pub fn clear_selection_drag_state(&mut self) {
11724 self.selection_drag_state = SelectionDragState::None;
11725 }
11726
11727 pub fn duplicate(
11728 &mut self,
11729 upwards: bool,
11730 whole_lines: bool,
11731 window: &mut Window,
11732 cx: &mut Context<Self>,
11733 ) {
11734 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11735
11736 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11737 let buffer = display_map.buffer_snapshot();
11738 let selections = self.selections.all::<Point>(&display_map);
11739
11740 let mut edits = Vec::new();
11741 let mut selections_iter = selections.iter().peekable();
11742 while let Some(selection) = selections_iter.next() {
11743 let mut rows = selection.spanned_rows(false, &display_map);
11744 // duplicate line-wise
11745 if whole_lines || selection.start == selection.end {
11746 // Avoid duplicating the same lines twice.
11747 while let Some(next_selection) = selections_iter.peek() {
11748 let next_rows = next_selection.spanned_rows(false, &display_map);
11749 if next_rows.start < rows.end {
11750 rows.end = next_rows.end;
11751 selections_iter.next().unwrap();
11752 } else {
11753 break;
11754 }
11755 }
11756
11757 // Copy the text from the selected row region and splice it either at the start
11758 // or end of the region.
11759 let start = Point::new(rows.start.0, 0);
11760 let end = Point::new(
11761 rows.end.previous_row().0,
11762 buffer.line_len(rows.end.previous_row()),
11763 );
11764
11765 let mut text = buffer.text_for_range(start..end).collect::<String>();
11766
11767 let insert_location = if upwards {
11768 // When duplicating upward, we need to insert before the current line.
11769 // If we're on the last line and it doesn't end with a newline,
11770 // we need to add a newline before the duplicated content.
11771 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11772 && buffer.max_point().column > 0
11773 && !text.ends_with('\n');
11774
11775 if needs_leading_newline {
11776 text.insert(0, '\n');
11777 end
11778 } else {
11779 text.push('\n');
11780 Point::new(rows.end.0, 0)
11781 }
11782 } else {
11783 text.push('\n');
11784 start
11785 };
11786 edits.push((insert_location..insert_location, text));
11787 } else {
11788 // duplicate character-wise
11789 let start = selection.start;
11790 let end = selection.end;
11791 let text = buffer.text_for_range(start..end).collect::<String>();
11792 edits.push((selection.end..selection.end, text));
11793 }
11794 }
11795
11796 self.transact(window, cx, |this, _, cx| {
11797 this.buffer.update(cx, |buffer, cx| {
11798 buffer.edit(edits, None, cx);
11799 });
11800
11801 this.request_autoscroll(Autoscroll::fit(), cx);
11802 });
11803 }
11804
11805 pub fn duplicate_line_up(
11806 &mut self,
11807 _: &DuplicateLineUp,
11808 window: &mut Window,
11809 cx: &mut Context<Self>,
11810 ) {
11811 self.duplicate(true, true, window, cx);
11812 }
11813
11814 pub fn duplicate_line_down(
11815 &mut self,
11816 _: &DuplicateLineDown,
11817 window: &mut Window,
11818 cx: &mut Context<Self>,
11819 ) {
11820 self.duplicate(false, true, window, cx);
11821 }
11822
11823 pub fn duplicate_selection(
11824 &mut self,
11825 _: &DuplicateSelection,
11826 window: &mut Window,
11827 cx: &mut Context<Self>,
11828 ) {
11829 self.duplicate(false, false, window, cx);
11830 }
11831
11832 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11833 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11834 if self.mode.is_single_line() {
11835 cx.propagate();
11836 return;
11837 }
11838
11839 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11840 let buffer = self.buffer.read(cx).snapshot(cx);
11841
11842 let mut edits = Vec::new();
11843 let mut unfold_ranges = Vec::new();
11844 let mut refold_creases = Vec::new();
11845
11846 let selections = self.selections.all::<Point>(&display_map);
11847 let mut selections = selections.iter().peekable();
11848 let mut contiguous_row_selections = Vec::new();
11849 let mut new_selections = Vec::new();
11850
11851 while let Some(selection) = selections.next() {
11852 // Find all the selections that span a contiguous row range
11853 let (start_row, end_row) = consume_contiguous_rows(
11854 &mut contiguous_row_selections,
11855 selection,
11856 &display_map,
11857 &mut selections,
11858 );
11859
11860 // Move the text spanned by the row range to be before the line preceding the row range
11861 if start_row.0 > 0 {
11862 let range_to_move = Point::new(
11863 start_row.previous_row().0,
11864 buffer.line_len(start_row.previous_row()),
11865 )
11866 ..Point::new(
11867 end_row.previous_row().0,
11868 buffer.line_len(end_row.previous_row()),
11869 );
11870 let insertion_point = display_map
11871 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11872 .0;
11873
11874 // Don't move lines across excerpts
11875 if buffer
11876 .excerpt_containing(insertion_point..range_to_move.end)
11877 .is_some()
11878 {
11879 let text = buffer
11880 .text_for_range(range_to_move.clone())
11881 .flat_map(|s| s.chars())
11882 .skip(1)
11883 .chain(['\n'])
11884 .collect::<String>();
11885
11886 edits.push((
11887 buffer.anchor_after(range_to_move.start)
11888 ..buffer.anchor_before(range_to_move.end),
11889 String::new(),
11890 ));
11891 let insertion_anchor = buffer.anchor_after(insertion_point);
11892 edits.push((insertion_anchor..insertion_anchor, text));
11893
11894 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11895
11896 // Move selections up
11897 new_selections.extend(contiguous_row_selections.drain(..).map(
11898 |mut selection| {
11899 selection.start.row -= row_delta;
11900 selection.end.row -= row_delta;
11901 selection
11902 },
11903 ));
11904
11905 // Move folds up
11906 unfold_ranges.push(range_to_move.clone());
11907 for fold in display_map.folds_in_range(
11908 buffer.anchor_before(range_to_move.start)
11909 ..buffer.anchor_after(range_to_move.end),
11910 ) {
11911 let mut start = fold.range.start.to_point(&buffer);
11912 let mut end = fold.range.end.to_point(&buffer);
11913 start.row -= row_delta;
11914 end.row -= row_delta;
11915 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11916 }
11917 }
11918 }
11919
11920 // If we didn't move line(s), preserve the existing selections
11921 new_selections.append(&mut contiguous_row_selections);
11922 }
11923
11924 self.transact(window, cx, |this, window, cx| {
11925 this.unfold_ranges(&unfold_ranges, true, true, cx);
11926 this.buffer.update(cx, |buffer, cx| {
11927 for (range, text) in edits {
11928 buffer.edit([(range, text)], None, cx);
11929 }
11930 });
11931 this.fold_creases(refold_creases, true, window, cx);
11932 this.change_selections(Default::default(), window, cx, |s| {
11933 s.select(new_selections);
11934 })
11935 });
11936 }
11937
11938 pub fn move_line_down(
11939 &mut self,
11940 _: &MoveLineDown,
11941 window: &mut Window,
11942 cx: &mut Context<Self>,
11943 ) {
11944 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11945 if self.mode.is_single_line() {
11946 cx.propagate();
11947 return;
11948 }
11949
11950 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11951 let buffer = self.buffer.read(cx).snapshot(cx);
11952
11953 let mut edits = Vec::new();
11954 let mut unfold_ranges = Vec::new();
11955 let mut refold_creases = Vec::new();
11956
11957 let selections = self.selections.all::<Point>(&display_map);
11958 let mut selections = selections.iter().peekable();
11959 let mut contiguous_row_selections = Vec::new();
11960 let mut new_selections = Vec::new();
11961
11962 while let Some(selection) = selections.next() {
11963 // Find all the selections that span a contiguous row range
11964 let (start_row, end_row) = consume_contiguous_rows(
11965 &mut contiguous_row_selections,
11966 selection,
11967 &display_map,
11968 &mut selections,
11969 );
11970
11971 // Move the text spanned by the row range to be after the last line of the row range
11972 if end_row.0 <= buffer.max_point().row {
11973 let range_to_move =
11974 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11975 let insertion_point = display_map
11976 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11977 .0;
11978
11979 // Don't move lines across excerpt boundaries
11980 if buffer
11981 .excerpt_containing(range_to_move.start..insertion_point)
11982 .is_some()
11983 {
11984 let mut text = String::from("\n");
11985 text.extend(buffer.text_for_range(range_to_move.clone()));
11986 text.pop(); // Drop trailing newline
11987 edits.push((
11988 buffer.anchor_after(range_to_move.start)
11989 ..buffer.anchor_before(range_to_move.end),
11990 String::new(),
11991 ));
11992 let insertion_anchor = buffer.anchor_after(insertion_point);
11993 edits.push((insertion_anchor..insertion_anchor, text));
11994
11995 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11996
11997 // Move selections down
11998 new_selections.extend(contiguous_row_selections.drain(..).map(
11999 |mut selection| {
12000 selection.start.row += row_delta;
12001 selection.end.row += row_delta;
12002 selection
12003 },
12004 ));
12005
12006 // Move folds down
12007 unfold_ranges.push(range_to_move.clone());
12008 for fold in display_map.folds_in_range(
12009 buffer.anchor_before(range_to_move.start)
12010 ..buffer.anchor_after(range_to_move.end),
12011 ) {
12012 let mut start = fold.range.start.to_point(&buffer);
12013 let mut end = fold.range.end.to_point(&buffer);
12014 start.row += row_delta;
12015 end.row += row_delta;
12016 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12017 }
12018 }
12019 }
12020
12021 // If we didn't move line(s), preserve the existing selections
12022 new_selections.append(&mut contiguous_row_selections);
12023 }
12024
12025 self.transact(window, cx, |this, window, cx| {
12026 this.unfold_ranges(&unfold_ranges, true, true, cx);
12027 this.buffer.update(cx, |buffer, cx| {
12028 for (range, text) in edits {
12029 buffer.edit([(range, text)], None, cx);
12030 }
12031 });
12032 this.fold_creases(refold_creases, true, window, cx);
12033 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12034 });
12035 }
12036
12037 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12038 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12039 let text_layout_details = &self.text_layout_details(window);
12040 self.transact(window, cx, |this, window, cx| {
12041 let edits = this.change_selections(Default::default(), window, cx, |s| {
12042 let mut edits: Vec<(Range<usize>, String)> = Default::default();
12043 s.move_with(|display_map, selection| {
12044 if !selection.is_empty() {
12045 return;
12046 }
12047
12048 let mut head = selection.head();
12049 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12050 if head.column() == display_map.line_len(head.row()) {
12051 transpose_offset = display_map
12052 .buffer_snapshot()
12053 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12054 }
12055
12056 if transpose_offset == 0 {
12057 return;
12058 }
12059
12060 *head.column_mut() += 1;
12061 head = display_map.clip_point(head, Bias::Right);
12062 let goal = SelectionGoal::HorizontalPosition(
12063 display_map
12064 .x_for_display_point(head, text_layout_details)
12065 .into(),
12066 );
12067 selection.collapse_to(head, goal);
12068
12069 let transpose_start = display_map
12070 .buffer_snapshot()
12071 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12072 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12073 let transpose_end = display_map
12074 .buffer_snapshot()
12075 .clip_offset(transpose_offset + 1, Bias::Right);
12076 if let Some(ch) = display_map
12077 .buffer_snapshot()
12078 .chars_at(transpose_start)
12079 .next()
12080 {
12081 edits.push((transpose_start..transpose_offset, String::new()));
12082 edits.push((transpose_end..transpose_end, ch.to_string()));
12083 }
12084 }
12085 });
12086 edits
12087 });
12088 this.buffer
12089 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12090 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12091 this.change_selections(Default::default(), window, cx, |s| {
12092 s.select(selections);
12093 });
12094 });
12095 }
12096
12097 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12098 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12099 if self.mode.is_single_line() {
12100 cx.propagate();
12101 return;
12102 }
12103
12104 self.rewrap_impl(RewrapOptions::default(), cx)
12105 }
12106
12107 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12108 let buffer = self.buffer.read(cx).snapshot(cx);
12109 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12110
12111 #[derive(Clone, Debug, PartialEq)]
12112 enum CommentFormat {
12113 /// single line comment, with prefix for line
12114 Line(String),
12115 /// single line within a block comment, with prefix for line
12116 BlockLine(String),
12117 /// a single line of a block comment that includes the initial delimiter
12118 BlockCommentWithStart(BlockCommentConfig),
12119 /// a single line of a block comment that includes the ending delimiter
12120 BlockCommentWithEnd(BlockCommentConfig),
12121 }
12122
12123 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12124 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12125 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12126 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12127 .peekable();
12128
12129 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12130 row
12131 } else {
12132 return Vec::new();
12133 };
12134
12135 let language_settings = buffer.language_settings_at(selection.head(), cx);
12136 let language_scope = buffer.language_scope_at(selection.head());
12137
12138 let indent_and_prefix_for_row =
12139 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12140 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12141 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12142 &language_scope
12143 {
12144 let indent_end = Point::new(row, indent.len);
12145 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12146 let line_text_after_indent = buffer
12147 .text_for_range(indent_end..line_end)
12148 .collect::<String>();
12149
12150 let is_within_comment_override = buffer
12151 .language_scope_at(indent_end)
12152 .is_some_and(|scope| scope.override_name() == Some("comment"));
12153 let comment_delimiters = if is_within_comment_override {
12154 // we are within a comment syntax node, but we don't
12155 // yet know what kind of comment: block, doc or line
12156 match (
12157 language_scope.documentation_comment(),
12158 language_scope.block_comment(),
12159 ) {
12160 (Some(config), _) | (_, Some(config))
12161 if buffer.contains_str_at(indent_end, &config.start) =>
12162 {
12163 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12164 }
12165 (Some(config), _) | (_, Some(config))
12166 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12167 {
12168 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12169 }
12170 (Some(config), _) | (_, Some(config))
12171 if buffer.contains_str_at(indent_end, &config.prefix) =>
12172 {
12173 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12174 }
12175 (_, _) => language_scope
12176 .line_comment_prefixes()
12177 .iter()
12178 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12179 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12180 }
12181 } else {
12182 // we not in an overridden comment node, but we may
12183 // be within a non-overridden line comment node
12184 language_scope
12185 .line_comment_prefixes()
12186 .iter()
12187 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12188 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12189 };
12190
12191 let rewrap_prefix = language_scope
12192 .rewrap_prefixes()
12193 .iter()
12194 .find_map(|prefix_regex| {
12195 prefix_regex.find(&line_text_after_indent).map(|mat| {
12196 if mat.start() == 0 {
12197 Some(mat.as_str().to_string())
12198 } else {
12199 None
12200 }
12201 })
12202 })
12203 .flatten();
12204 (comment_delimiters, rewrap_prefix)
12205 } else {
12206 (None, None)
12207 };
12208 (indent, comment_prefix, rewrap_prefix)
12209 };
12210
12211 let mut ranges = Vec::new();
12212 let from_empty_selection = selection.is_empty();
12213
12214 let mut current_range_start = first_row;
12215 let mut prev_row = first_row;
12216 let (
12217 mut current_range_indent,
12218 mut current_range_comment_delimiters,
12219 mut current_range_rewrap_prefix,
12220 ) = indent_and_prefix_for_row(first_row);
12221
12222 for row in non_blank_rows_iter.skip(1) {
12223 let has_paragraph_break = row > prev_row + 1;
12224
12225 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12226 indent_and_prefix_for_row(row);
12227
12228 let has_indent_change = row_indent != current_range_indent;
12229 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12230
12231 let has_boundary_change = has_comment_change
12232 || row_rewrap_prefix.is_some()
12233 || (has_indent_change && current_range_comment_delimiters.is_some());
12234
12235 if has_paragraph_break || has_boundary_change {
12236 ranges.push((
12237 language_settings.clone(),
12238 Point::new(current_range_start, 0)
12239 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12240 current_range_indent,
12241 current_range_comment_delimiters.clone(),
12242 current_range_rewrap_prefix.clone(),
12243 from_empty_selection,
12244 ));
12245 current_range_start = row;
12246 current_range_indent = row_indent;
12247 current_range_comment_delimiters = row_comment_delimiters;
12248 current_range_rewrap_prefix = row_rewrap_prefix;
12249 }
12250 prev_row = row;
12251 }
12252
12253 ranges.push((
12254 language_settings.clone(),
12255 Point::new(current_range_start, 0)
12256 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12257 current_range_indent,
12258 current_range_comment_delimiters,
12259 current_range_rewrap_prefix,
12260 from_empty_selection,
12261 ));
12262
12263 ranges
12264 });
12265
12266 let mut edits = Vec::new();
12267 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12268
12269 for (
12270 language_settings,
12271 wrap_range,
12272 mut indent_size,
12273 comment_prefix,
12274 rewrap_prefix,
12275 from_empty_selection,
12276 ) in wrap_ranges
12277 {
12278 let mut start_row = wrap_range.start.row;
12279 let mut end_row = wrap_range.end.row;
12280
12281 // Skip selections that overlap with a range that has already been rewrapped.
12282 let selection_range = start_row..end_row;
12283 if rewrapped_row_ranges
12284 .iter()
12285 .any(|range| range.overlaps(&selection_range))
12286 {
12287 continue;
12288 }
12289
12290 let tab_size = language_settings.tab_size;
12291
12292 let (line_prefix, inside_comment) = match &comment_prefix {
12293 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12294 (Some(prefix.as_str()), true)
12295 }
12296 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12297 (Some(prefix.as_ref()), true)
12298 }
12299 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12300 start: _,
12301 end: _,
12302 prefix,
12303 tab_size,
12304 })) => {
12305 indent_size.len += tab_size;
12306 (Some(prefix.as_ref()), true)
12307 }
12308 None => (None, false),
12309 };
12310 let indent_prefix = indent_size.chars().collect::<String>();
12311 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12312
12313 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12314 RewrapBehavior::InComments => inside_comment,
12315 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12316 RewrapBehavior::Anywhere => true,
12317 };
12318
12319 let should_rewrap = options.override_language_settings
12320 || allow_rewrap_based_on_language
12321 || self.hard_wrap.is_some();
12322 if !should_rewrap {
12323 continue;
12324 }
12325
12326 if from_empty_selection {
12327 'expand_upwards: while start_row > 0 {
12328 let prev_row = start_row - 1;
12329 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12330 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12331 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12332 {
12333 start_row = prev_row;
12334 } else {
12335 break 'expand_upwards;
12336 }
12337 }
12338
12339 'expand_downwards: while end_row < buffer.max_point().row {
12340 let next_row = end_row + 1;
12341 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12342 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12343 && !buffer.is_line_blank(MultiBufferRow(next_row))
12344 {
12345 end_row = next_row;
12346 } else {
12347 break 'expand_downwards;
12348 }
12349 }
12350 }
12351
12352 let start = Point::new(start_row, 0);
12353 let start_offset = ToOffset::to_offset(&start, &buffer);
12354 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12355 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12356 let mut first_line_delimiter = None;
12357 let mut last_line_delimiter = None;
12358 let Some(lines_without_prefixes) = selection_text
12359 .lines()
12360 .enumerate()
12361 .map(|(ix, line)| {
12362 let line_trimmed = line.trim_start();
12363 if rewrap_prefix.is_some() && ix > 0 {
12364 Ok(line_trimmed)
12365 } else if let Some(
12366 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12367 start,
12368 prefix,
12369 end,
12370 tab_size,
12371 })
12372 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12373 start,
12374 prefix,
12375 end,
12376 tab_size,
12377 }),
12378 ) = &comment_prefix
12379 {
12380 let line_trimmed = line_trimmed
12381 .strip_prefix(start.as_ref())
12382 .map(|s| {
12383 let mut indent_size = indent_size;
12384 indent_size.len -= tab_size;
12385 let indent_prefix: String = indent_size.chars().collect();
12386 first_line_delimiter = Some((indent_prefix, start));
12387 s.trim_start()
12388 })
12389 .unwrap_or(line_trimmed);
12390 let line_trimmed = line_trimmed
12391 .strip_suffix(end.as_ref())
12392 .map(|s| {
12393 last_line_delimiter = Some(end);
12394 s.trim_end()
12395 })
12396 .unwrap_or(line_trimmed);
12397 let line_trimmed = line_trimmed
12398 .strip_prefix(prefix.as_ref())
12399 .unwrap_or(line_trimmed);
12400 Ok(line_trimmed)
12401 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12402 line_trimmed.strip_prefix(prefix).with_context(|| {
12403 format!("line did not start with prefix {prefix:?}: {line:?}")
12404 })
12405 } else {
12406 line_trimmed
12407 .strip_prefix(&line_prefix.trim_start())
12408 .with_context(|| {
12409 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12410 })
12411 }
12412 })
12413 .collect::<Result<Vec<_>, _>>()
12414 .log_err()
12415 else {
12416 continue;
12417 };
12418
12419 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12420 buffer
12421 .language_settings_at(Point::new(start_row, 0), cx)
12422 .preferred_line_length as usize
12423 });
12424
12425 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12426 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12427 } else {
12428 line_prefix.clone()
12429 };
12430
12431 let wrapped_text = {
12432 let mut wrapped_text = wrap_with_prefix(
12433 line_prefix,
12434 subsequent_lines_prefix,
12435 lines_without_prefixes.join("\n"),
12436 wrap_column,
12437 tab_size,
12438 options.preserve_existing_whitespace,
12439 );
12440
12441 if let Some((indent, delimiter)) = first_line_delimiter {
12442 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12443 }
12444 if let Some(last_line) = last_line_delimiter {
12445 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12446 }
12447
12448 wrapped_text
12449 };
12450
12451 // TODO: should always use char-based diff while still supporting cursor behavior that
12452 // matches vim.
12453 let mut diff_options = DiffOptions::default();
12454 if options.override_language_settings {
12455 diff_options.max_word_diff_len = 0;
12456 diff_options.max_word_diff_line_count = 0;
12457 } else {
12458 diff_options.max_word_diff_len = usize::MAX;
12459 diff_options.max_word_diff_line_count = usize::MAX;
12460 }
12461
12462 for (old_range, new_text) in
12463 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12464 {
12465 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12466 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12467 edits.push((edit_start..edit_end, new_text));
12468 }
12469
12470 rewrapped_row_ranges.push(start_row..=end_row);
12471 }
12472
12473 self.buffer
12474 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12475 }
12476
12477 pub fn cut_common(
12478 &mut self,
12479 cut_no_selection_line: bool,
12480 window: &mut Window,
12481 cx: &mut Context<Self>,
12482 ) -> ClipboardItem {
12483 let mut text = String::new();
12484 let buffer = self.buffer.read(cx).snapshot(cx);
12485 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12486 let mut clipboard_selections = Vec::with_capacity(selections.len());
12487 {
12488 let max_point = buffer.max_point();
12489 let mut is_first = true;
12490 for selection in &mut selections {
12491 let is_entire_line =
12492 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12493 if is_entire_line {
12494 selection.start = Point::new(selection.start.row, 0);
12495 if !selection.is_empty() && selection.end.column == 0 {
12496 selection.end = cmp::min(max_point, selection.end);
12497 } else {
12498 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12499 }
12500 selection.goal = SelectionGoal::None;
12501 }
12502 if is_first {
12503 is_first = false;
12504 } else {
12505 text += "\n";
12506 }
12507 let mut len = 0;
12508 for chunk in buffer.text_for_range(selection.start..selection.end) {
12509 text.push_str(chunk);
12510 len += chunk.len();
12511 }
12512 clipboard_selections.push(ClipboardSelection {
12513 len,
12514 is_entire_line,
12515 first_line_indent: buffer
12516 .indent_size_for_line(MultiBufferRow(selection.start.row))
12517 .len,
12518 });
12519 }
12520 }
12521
12522 self.transact(window, cx, |this, window, cx| {
12523 this.change_selections(Default::default(), window, cx, |s| {
12524 s.select(selections);
12525 });
12526 this.insert("", window, cx);
12527 });
12528 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12529 }
12530
12531 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12532 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12533 let item = self.cut_common(true, window, cx);
12534 cx.write_to_clipboard(item);
12535 }
12536
12537 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12538 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12539 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12540 s.move_with(|snapshot, sel| {
12541 if sel.is_empty() {
12542 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12543 }
12544 if sel.is_empty() {
12545 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12546 }
12547 });
12548 });
12549 let item = self.cut_common(false, window, cx);
12550 cx.set_global(KillRing(item))
12551 }
12552
12553 pub fn kill_ring_yank(
12554 &mut self,
12555 _: &KillRingYank,
12556 window: &mut Window,
12557 cx: &mut Context<Self>,
12558 ) {
12559 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12560 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12561 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12562 (kill_ring.text().to_string(), kill_ring.metadata_json())
12563 } else {
12564 return;
12565 }
12566 } else {
12567 return;
12568 };
12569 self.do_paste(&text, metadata, false, window, cx);
12570 }
12571
12572 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12573 self.do_copy(true, cx);
12574 }
12575
12576 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12577 self.do_copy(false, cx);
12578 }
12579
12580 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12581 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12582 let buffer = self.buffer.read(cx).read(cx);
12583 let mut text = String::new();
12584
12585 let mut clipboard_selections = Vec::with_capacity(selections.len());
12586 {
12587 let max_point = buffer.max_point();
12588 let mut is_first = true;
12589 for selection in &selections {
12590 let mut start = selection.start;
12591 let mut end = selection.end;
12592 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12593 let mut add_trailing_newline = false;
12594 if is_entire_line {
12595 start = Point::new(start.row, 0);
12596 let next_line_start = Point::new(end.row + 1, 0);
12597 if next_line_start <= max_point {
12598 end = next_line_start;
12599 } else {
12600 // We're on the last line without a trailing newline.
12601 // Copy to the end of the line and add a newline afterwards.
12602 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12603 add_trailing_newline = true;
12604 }
12605 }
12606
12607 let mut trimmed_selections = Vec::new();
12608 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12609 let row = MultiBufferRow(start.row);
12610 let first_indent = buffer.indent_size_for_line(row);
12611 if first_indent.len == 0 || start.column > first_indent.len {
12612 trimmed_selections.push(start..end);
12613 } else {
12614 trimmed_selections.push(
12615 Point::new(row.0, first_indent.len)
12616 ..Point::new(row.0, buffer.line_len(row)),
12617 );
12618 for row in start.row + 1..=end.row {
12619 let mut line_len = buffer.line_len(MultiBufferRow(row));
12620 if row == end.row {
12621 line_len = end.column;
12622 }
12623 if line_len == 0 {
12624 trimmed_selections
12625 .push(Point::new(row, 0)..Point::new(row, line_len));
12626 continue;
12627 }
12628 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12629 if row_indent_size.len >= first_indent.len {
12630 trimmed_selections.push(
12631 Point::new(row, first_indent.len)..Point::new(row, line_len),
12632 );
12633 } else {
12634 trimmed_selections.clear();
12635 trimmed_selections.push(start..end);
12636 break;
12637 }
12638 }
12639 }
12640 } else {
12641 trimmed_selections.push(start..end);
12642 }
12643
12644 for trimmed_range in trimmed_selections {
12645 if is_first {
12646 is_first = false;
12647 } else {
12648 text += "\n";
12649 }
12650 let mut len = 0;
12651 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12652 text.push_str(chunk);
12653 len += chunk.len();
12654 }
12655 if add_trailing_newline {
12656 text.push('\n');
12657 len += 1;
12658 }
12659 clipboard_selections.push(ClipboardSelection {
12660 len,
12661 is_entire_line,
12662 first_line_indent: buffer
12663 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12664 .len,
12665 });
12666 }
12667 }
12668 }
12669
12670 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12671 text,
12672 clipboard_selections,
12673 ));
12674 }
12675
12676 pub fn do_paste(
12677 &mut self,
12678 text: &String,
12679 clipboard_selections: Option<Vec<ClipboardSelection>>,
12680 handle_entire_lines: bool,
12681 window: &mut Window,
12682 cx: &mut Context<Self>,
12683 ) {
12684 if self.read_only(cx) {
12685 return;
12686 }
12687
12688 let clipboard_text = Cow::Borrowed(text.as_str());
12689
12690 self.transact(window, cx, |this, window, cx| {
12691 let had_active_edit_prediction = this.has_active_edit_prediction();
12692 let display_map = this.display_snapshot(cx);
12693 let old_selections = this.selections.all::<usize>(&display_map);
12694 let cursor_offset = this.selections.last::<usize>(&display_map).head();
12695
12696 if let Some(mut clipboard_selections) = clipboard_selections {
12697 let all_selections_were_entire_line =
12698 clipboard_selections.iter().all(|s| s.is_entire_line);
12699 let first_selection_indent_column =
12700 clipboard_selections.first().map(|s| s.first_line_indent);
12701 if clipboard_selections.len() != old_selections.len() {
12702 clipboard_selections.drain(..);
12703 }
12704 let mut auto_indent_on_paste = true;
12705
12706 this.buffer.update(cx, |buffer, cx| {
12707 let snapshot = buffer.read(cx);
12708 auto_indent_on_paste = snapshot
12709 .language_settings_at(cursor_offset, cx)
12710 .auto_indent_on_paste;
12711
12712 let mut start_offset = 0;
12713 let mut edits = Vec::new();
12714 let mut original_indent_columns = Vec::new();
12715 for (ix, selection) in old_selections.iter().enumerate() {
12716 let to_insert;
12717 let entire_line;
12718 let original_indent_column;
12719 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12720 let end_offset = start_offset + clipboard_selection.len;
12721 to_insert = &clipboard_text[start_offset..end_offset];
12722 entire_line = clipboard_selection.is_entire_line;
12723 start_offset = end_offset + 1;
12724 original_indent_column = Some(clipboard_selection.first_line_indent);
12725 } else {
12726 to_insert = &*clipboard_text;
12727 entire_line = all_selections_were_entire_line;
12728 original_indent_column = first_selection_indent_column
12729 }
12730
12731 let (range, to_insert) =
12732 if selection.is_empty() && handle_entire_lines && entire_line {
12733 // If the corresponding selection was empty when this slice of the
12734 // clipboard text was written, then the entire line containing the
12735 // selection was copied. If this selection is also currently empty,
12736 // then paste the line before the current line of the buffer.
12737 let column = selection.start.to_point(&snapshot).column as usize;
12738 let line_start = selection.start - column;
12739 (line_start..line_start, Cow::Borrowed(to_insert))
12740 } else {
12741 let language = snapshot.language_at(selection.head());
12742 let range = selection.range();
12743 if let Some(language) = language
12744 && language.name() == "Markdown".into()
12745 {
12746 edit_for_markdown_paste(
12747 &snapshot,
12748 range,
12749 to_insert,
12750 url::Url::parse(to_insert).ok(),
12751 )
12752 } else {
12753 (range, Cow::Borrowed(to_insert))
12754 }
12755 };
12756
12757 edits.push((range, to_insert));
12758 original_indent_columns.push(original_indent_column);
12759 }
12760 drop(snapshot);
12761
12762 buffer.edit(
12763 edits,
12764 if auto_indent_on_paste {
12765 Some(AutoindentMode::Block {
12766 original_indent_columns,
12767 })
12768 } else {
12769 None
12770 },
12771 cx,
12772 );
12773 });
12774
12775 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12776 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12777 } else {
12778 let url = url::Url::parse(&clipboard_text).ok();
12779
12780 let auto_indent_mode = if !clipboard_text.is_empty() {
12781 Some(AutoindentMode::Block {
12782 original_indent_columns: Vec::new(),
12783 })
12784 } else {
12785 None
12786 };
12787
12788 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12789 let snapshot = buffer.snapshot(cx);
12790
12791 let anchors = old_selections
12792 .iter()
12793 .map(|s| {
12794 let anchor = snapshot.anchor_after(s.head());
12795 s.map(|_| anchor)
12796 })
12797 .collect::<Vec<_>>();
12798
12799 let mut edits = Vec::new();
12800
12801 for selection in old_selections.iter() {
12802 let language = snapshot.language_at(selection.head());
12803 let range = selection.range();
12804
12805 let (edit_range, edit_text) = if let Some(language) = language
12806 && language.name() == "Markdown".into()
12807 {
12808 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12809 } else {
12810 (range, clipboard_text.clone())
12811 };
12812
12813 edits.push((edit_range, edit_text));
12814 }
12815
12816 drop(snapshot);
12817 buffer.edit(edits, auto_indent_mode, cx);
12818
12819 anchors
12820 });
12821
12822 this.change_selections(Default::default(), window, cx, |s| {
12823 s.select_anchors(selection_anchors);
12824 });
12825 }
12826
12827 let trigger_in_words =
12828 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12829
12830 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12831 });
12832 }
12833
12834 pub fn diff_clipboard_with_selection(
12835 &mut self,
12836 _: &DiffClipboardWithSelection,
12837 window: &mut Window,
12838 cx: &mut Context<Self>,
12839 ) {
12840 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
12841
12842 if selections.is_empty() {
12843 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12844 return;
12845 };
12846
12847 let clipboard_text = match cx.read_from_clipboard() {
12848 Some(item) => match item.entries().first() {
12849 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12850 _ => None,
12851 },
12852 None => None,
12853 };
12854
12855 let Some(clipboard_text) = clipboard_text else {
12856 log::warn!("Clipboard doesn't contain text.");
12857 return;
12858 };
12859
12860 window.dispatch_action(
12861 Box::new(DiffClipboardWithSelectionData {
12862 clipboard_text,
12863 editor: cx.entity(),
12864 }),
12865 cx,
12866 );
12867 }
12868
12869 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12870 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12871 if let Some(item) = cx.read_from_clipboard() {
12872 let entries = item.entries();
12873
12874 match entries.first() {
12875 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12876 // of all the pasted entries.
12877 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12878 .do_paste(
12879 clipboard_string.text(),
12880 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12881 true,
12882 window,
12883 cx,
12884 ),
12885 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12886 }
12887 }
12888 }
12889
12890 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12891 if self.read_only(cx) {
12892 return;
12893 }
12894
12895 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12896
12897 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12898 if let Some((selections, _)) =
12899 self.selection_history.transaction(transaction_id).cloned()
12900 {
12901 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12902 s.select_anchors(selections.to_vec());
12903 });
12904 } else {
12905 log::error!(
12906 "No entry in selection_history found for undo. \
12907 This may correspond to a bug where undo does not update the selection. \
12908 If this is occurring, please add details to \
12909 https://github.com/zed-industries/zed/issues/22692"
12910 );
12911 }
12912 self.request_autoscroll(Autoscroll::fit(), cx);
12913 self.unmark_text(window, cx);
12914 self.refresh_edit_prediction(true, false, window, cx);
12915 cx.emit(EditorEvent::Edited { transaction_id });
12916 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12917 }
12918 }
12919
12920 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12921 if self.read_only(cx) {
12922 return;
12923 }
12924
12925 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12926
12927 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12928 if let Some((_, Some(selections))) =
12929 self.selection_history.transaction(transaction_id).cloned()
12930 {
12931 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12932 s.select_anchors(selections.to_vec());
12933 });
12934 } else {
12935 log::error!(
12936 "No entry in selection_history found for redo. \
12937 This may correspond to a bug where undo does not update the selection. \
12938 If this is occurring, please add details to \
12939 https://github.com/zed-industries/zed/issues/22692"
12940 );
12941 }
12942 self.request_autoscroll(Autoscroll::fit(), cx);
12943 self.unmark_text(window, cx);
12944 self.refresh_edit_prediction(true, false, window, cx);
12945 cx.emit(EditorEvent::Edited { transaction_id });
12946 }
12947 }
12948
12949 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12950 self.buffer
12951 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12952 }
12953
12954 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12955 self.buffer
12956 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12957 }
12958
12959 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12960 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12961 self.change_selections(Default::default(), window, cx, |s| {
12962 s.move_with(|map, selection| {
12963 let cursor = if selection.is_empty() {
12964 movement::left(map, selection.start)
12965 } else {
12966 selection.start
12967 };
12968 selection.collapse_to(cursor, SelectionGoal::None);
12969 });
12970 })
12971 }
12972
12973 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12974 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12975 self.change_selections(Default::default(), window, cx, |s| {
12976 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12977 })
12978 }
12979
12980 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12981 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12982 self.change_selections(Default::default(), window, cx, |s| {
12983 s.move_with(|map, selection| {
12984 let cursor = if selection.is_empty() {
12985 movement::right(map, selection.end)
12986 } else {
12987 selection.end
12988 };
12989 selection.collapse_to(cursor, SelectionGoal::None)
12990 });
12991 })
12992 }
12993
12994 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12995 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12996 self.change_selections(Default::default(), window, cx, |s| {
12997 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12998 });
12999 }
13000
13001 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13002 if self.take_rename(true, window, cx).is_some() {
13003 return;
13004 }
13005
13006 if self.mode.is_single_line() {
13007 cx.propagate();
13008 return;
13009 }
13010
13011 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13012
13013 let text_layout_details = &self.text_layout_details(window);
13014 let selection_count = self.selections.count();
13015 let first_selection = self.selections.first_anchor();
13016
13017 self.change_selections(Default::default(), window, cx, |s| {
13018 s.move_with(|map, selection| {
13019 if !selection.is_empty() {
13020 selection.goal = SelectionGoal::None;
13021 }
13022 let (cursor, goal) = movement::up(
13023 map,
13024 selection.start,
13025 selection.goal,
13026 false,
13027 text_layout_details,
13028 );
13029 selection.collapse_to(cursor, goal);
13030 });
13031 });
13032
13033 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13034 {
13035 cx.propagate();
13036 }
13037 }
13038
13039 pub fn move_up_by_lines(
13040 &mut self,
13041 action: &MoveUpByLines,
13042 window: &mut Window,
13043 cx: &mut Context<Self>,
13044 ) {
13045 if self.take_rename(true, window, cx).is_some() {
13046 return;
13047 }
13048
13049 if self.mode.is_single_line() {
13050 cx.propagate();
13051 return;
13052 }
13053
13054 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13055
13056 let text_layout_details = &self.text_layout_details(window);
13057
13058 self.change_selections(Default::default(), window, cx, |s| {
13059 s.move_with(|map, selection| {
13060 if !selection.is_empty() {
13061 selection.goal = SelectionGoal::None;
13062 }
13063 let (cursor, goal) = movement::up_by_rows(
13064 map,
13065 selection.start,
13066 action.lines,
13067 selection.goal,
13068 false,
13069 text_layout_details,
13070 );
13071 selection.collapse_to(cursor, goal);
13072 });
13073 })
13074 }
13075
13076 pub fn move_down_by_lines(
13077 &mut self,
13078 action: &MoveDownByLines,
13079 window: &mut Window,
13080 cx: &mut Context<Self>,
13081 ) {
13082 if self.take_rename(true, window, cx).is_some() {
13083 return;
13084 }
13085
13086 if self.mode.is_single_line() {
13087 cx.propagate();
13088 return;
13089 }
13090
13091 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13092
13093 let text_layout_details = &self.text_layout_details(window);
13094
13095 self.change_selections(Default::default(), window, cx, |s| {
13096 s.move_with(|map, selection| {
13097 if !selection.is_empty() {
13098 selection.goal = SelectionGoal::None;
13099 }
13100 let (cursor, goal) = movement::down_by_rows(
13101 map,
13102 selection.start,
13103 action.lines,
13104 selection.goal,
13105 false,
13106 text_layout_details,
13107 );
13108 selection.collapse_to(cursor, goal);
13109 });
13110 })
13111 }
13112
13113 pub fn select_down_by_lines(
13114 &mut self,
13115 action: &SelectDownByLines,
13116 window: &mut Window,
13117 cx: &mut Context<Self>,
13118 ) {
13119 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13120 let text_layout_details = &self.text_layout_details(window);
13121 self.change_selections(Default::default(), window, cx, |s| {
13122 s.move_heads_with(|map, head, goal| {
13123 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13124 })
13125 })
13126 }
13127
13128 pub fn select_up_by_lines(
13129 &mut self,
13130 action: &SelectUpByLines,
13131 window: &mut Window,
13132 cx: &mut Context<Self>,
13133 ) {
13134 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13135 let text_layout_details = &self.text_layout_details(window);
13136 self.change_selections(Default::default(), window, cx, |s| {
13137 s.move_heads_with(|map, head, goal| {
13138 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13139 })
13140 })
13141 }
13142
13143 pub fn select_page_up(
13144 &mut self,
13145 _: &SelectPageUp,
13146 window: &mut Window,
13147 cx: &mut Context<Self>,
13148 ) {
13149 let Some(row_count) = self.visible_row_count() else {
13150 return;
13151 };
13152
13153 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13154
13155 let text_layout_details = &self.text_layout_details(window);
13156
13157 self.change_selections(Default::default(), window, cx, |s| {
13158 s.move_heads_with(|map, head, goal| {
13159 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13160 })
13161 })
13162 }
13163
13164 pub fn move_page_up(
13165 &mut self,
13166 action: &MovePageUp,
13167 window: &mut Window,
13168 cx: &mut Context<Self>,
13169 ) {
13170 if self.take_rename(true, window, cx).is_some() {
13171 return;
13172 }
13173
13174 if self
13175 .context_menu
13176 .borrow_mut()
13177 .as_mut()
13178 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13179 .unwrap_or(false)
13180 {
13181 return;
13182 }
13183
13184 if matches!(self.mode, EditorMode::SingleLine) {
13185 cx.propagate();
13186 return;
13187 }
13188
13189 let Some(row_count) = self.visible_row_count() else {
13190 return;
13191 };
13192
13193 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13194
13195 let effects = if action.center_cursor {
13196 SelectionEffects::scroll(Autoscroll::center())
13197 } else {
13198 SelectionEffects::default()
13199 };
13200
13201 let text_layout_details = &self.text_layout_details(window);
13202
13203 self.change_selections(effects, window, cx, |s| {
13204 s.move_with(|map, selection| {
13205 if !selection.is_empty() {
13206 selection.goal = SelectionGoal::None;
13207 }
13208 let (cursor, goal) = movement::up_by_rows(
13209 map,
13210 selection.end,
13211 row_count,
13212 selection.goal,
13213 false,
13214 text_layout_details,
13215 );
13216 selection.collapse_to(cursor, goal);
13217 });
13218 });
13219 }
13220
13221 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13222 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13223 let text_layout_details = &self.text_layout_details(window);
13224 self.change_selections(Default::default(), window, cx, |s| {
13225 s.move_heads_with(|map, head, goal| {
13226 movement::up(map, head, goal, false, text_layout_details)
13227 })
13228 })
13229 }
13230
13231 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13232 self.take_rename(true, window, cx);
13233
13234 if self.mode.is_single_line() {
13235 cx.propagate();
13236 return;
13237 }
13238
13239 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13240
13241 let text_layout_details = &self.text_layout_details(window);
13242 let selection_count = self.selections.count();
13243 let first_selection = self.selections.first_anchor();
13244
13245 self.change_selections(Default::default(), window, cx, |s| {
13246 s.move_with(|map, selection| {
13247 if !selection.is_empty() {
13248 selection.goal = SelectionGoal::None;
13249 }
13250 let (cursor, goal) = movement::down(
13251 map,
13252 selection.end,
13253 selection.goal,
13254 false,
13255 text_layout_details,
13256 );
13257 selection.collapse_to(cursor, goal);
13258 });
13259 });
13260
13261 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13262 {
13263 cx.propagate();
13264 }
13265 }
13266
13267 pub fn select_page_down(
13268 &mut self,
13269 _: &SelectPageDown,
13270 window: &mut Window,
13271 cx: &mut Context<Self>,
13272 ) {
13273 let Some(row_count) = self.visible_row_count() else {
13274 return;
13275 };
13276
13277 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13278
13279 let text_layout_details = &self.text_layout_details(window);
13280
13281 self.change_selections(Default::default(), window, cx, |s| {
13282 s.move_heads_with(|map, head, goal| {
13283 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13284 })
13285 })
13286 }
13287
13288 pub fn move_page_down(
13289 &mut self,
13290 action: &MovePageDown,
13291 window: &mut Window,
13292 cx: &mut Context<Self>,
13293 ) {
13294 if self.take_rename(true, window, cx).is_some() {
13295 return;
13296 }
13297
13298 if self
13299 .context_menu
13300 .borrow_mut()
13301 .as_mut()
13302 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13303 .unwrap_or(false)
13304 {
13305 return;
13306 }
13307
13308 if matches!(self.mode, EditorMode::SingleLine) {
13309 cx.propagate();
13310 return;
13311 }
13312
13313 let Some(row_count) = self.visible_row_count() else {
13314 return;
13315 };
13316
13317 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13318
13319 let effects = if action.center_cursor {
13320 SelectionEffects::scroll(Autoscroll::center())
13321 } else {
13322 SelectionEffects::default()
13323 };
13324
13325 let text_layout_details = &self.text_layout_details(window);
13326 self.change_selections(effects, window, cx, |s| {
13327 s.move_with(|map, selection| {
13328 if !selection.is_empty() {
13329 selection.goal = SelectionGoal::None;
13330 }
13331 let (cursor, goal) = movement::down_by_rows(
13332 map,
13333 selection.end,
13334 row_count,
13335 selection.goal,
13336 false,
13337 text_layout_details,
13338 );
13339 selection.collapse_to(cursor, goal);
13340 });
13341 });
13342 }
13343
13344 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13345 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13346 let text_layout_details = &self.text_layout_details(window);
13347 self.change_selections(Default::default(), window, cx, |s| {
13348 s.move_heads_with(|map, head, goal| {
13349 movement::down(map, head, goal, false, text_layout_details)
13350 })
13351 });
13352 }
13353
13354 pub fn context_menu_first(
13355 &mut self,
13356 _: &ContextMenuFirst,
13357 window: &mut Window,
13358 cx: &mut Context<Self>,
13359 ) {
13360 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13361 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13362 }
13363 }
13364
13365 pub fn context_menu_prev(
13366 &mut self,
13367 _: &ContextMenuPrevious,
13368 window: &mut Window,
13369 cx: &mut Context<Self>,
13370 ) {
13371 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13372 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13373 }
13374 }
13375
13376 pub fn context_menu_next(
13377 &mut self,
13378 _: &ContextMenuNext,
13379 window: &mut Window,
13380 cx: &mut Context<Self>,
13381 ) {
13382 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13383 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13384 }
13385 }
13386
13387 pub fn context_menu_last(
13388 &mut self,
13389 _: &ContextMenuLast,
13390 window: &mut Window,
13391 cx: &mut Context<Self>,
13392 ) {
13393 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13394 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13395 }
13396 }
13397
13398 pub fn signature_help_prev(
13399 &mut self,
13400 _: &SignatureHelpPrevious,
13401 _: &mut Window,
13402 cx: &mut Context<Self>,
13403 ) {
13404 if let Some(popover) = self.signature_help_state.popover_mut() {
13405 if popover.current_signature == 0 {
13406 popover.current_signature = popover.signatures.len() - 1;
13407 } else {
13408 popover.current_signature -= 1;
13409 }
13410 cx.notify();
13411 }
13412 }
13413
13414 pub fn signature_help_next(
13415 &mut self,
13416 _: &SignatureHelpNext,
13417 _: &mut Window,
13418 cx: &mut Context<Self>,
13419 ) {
13420 if let Some(popover) = self.signature_help_state.popover_mut() {
13421 if popover.current_signature + 1 == popover.signatures.len() {
13422 popover.current_signature = 0;
13423 } else {
13424 popover.current_signature += 1;
13425 }
13426 cx.notify();
13427 }
13428 }
13429
13430 pub fn move_to_previous_word_start(
13431 &mut self,
13432 _: &MoveToPreviousWordStart,
13433 window: &mut Window,
13434 cx: &mut Context<Self>,
13435 ) {
13436 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13437 self.change_selections(Default::default(), window, cx, |s| {
13438 s.move_cursors_with(|map, head, _| {
13439 (
13440 movement::previous_word_start(map, head),
13441 SelectionGoal::None,
13442 )
13443 });
13444 })
13445 }
13446
13447 pub fn move_to_previous_subword_start(
13448 &mut self,
13449 _: &MoveToPreviousSubwordStart,
13450 window: &mut Window,
13451 cx: &mut Context<Self>,
13452 ) {
13453 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13454 self.change_selections(Default::default(), window, cx, |s| {
13455 s.move_cursors_with(|map, head, _| {
13456 (
13457 movement::previous_subword_start(map, head),
13458 SelectionGoal::None,
13459 )
13460 });
13461 })
13462 }
13463
13464 pub fn select_to_previous_word_start(
13465 &mut self,
13466 _: &SelectToPreviousWordStart,
13467 window: &mut Window,
13468 cx: &mut Context<Self>,
13469 ) {
13470 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13471 self.change_selections(Default::default(), window, cx, |s| {
13472 s.move_heads_with(|map, head, _| {
13473 (
13474 movement::previous_word_start(map, head),
13475 SelectionGoal::None,
13476 )
13477 });
13478 })
13479 }
13480
13481 pub fn select_to_previous_subword_start(
13482 &mut self,
13483 _: &SelectToPreviousSubwordStart,
13484 window: &mut Window,
13485 cx: &mut Context<Self>,
13486 ) {
13487 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13488 self.change_selections(Default::default(), window, cx, |s| {
13489 s.move_heads_with(|map, head, _| {
13490 (
13491 movement::previous_subword_start(map, head),
13492 SelectionGoal::None,
13493 )
13494 });
13495 })
13496 }
13497
13498 pub fn delete_to_previous_word_start(
13499 &mut self,
13500 action: &DeleteToPreviousWordStart,
13501 window: &mut Window,
13502 cx: &mut Context<Self>,
13503 ) {
13504 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13505 self.transact(window, cx, |this, window, cx| {
13506 this.select_autoclose_pair(window, cx);
13507 this.change_selections(Default::default(), window, cx, |s| {
13508 s.move_with(|map, selection| {
13509 if selection.is_empty() {
13510 let mut cursor = if action.ignore_newlines {
13511 movement::previous_word_start(map, selection.head())
13512 } else {
13513 movement::previous_word_start_or_newline(map, selection.head())
13514 };
13515 cursor = movement::adjust_greedy_deletion(
13516 map,
13517 selection.head(),
13518 cursor,
13519 action.ignore_brackets,
13520 );
13521 selection.set_head(cursor, SelectionGoal::None);
13522 }
13523 });
13524 });
13525 this.insert("", window, cx);
13526 });
13527 }
13528
13529 pub fn delete_to_previous_subword_start(
13530 &mut self,
13531 _: &DeleteToPreviousSubwordStart,
13532 window: &mut Window,
13533 cx: &mut Context<Self>,
13534 ) {
13535 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13536 self.transact(window, cx, |this, window, cx| {
13537 this.select_autoclose_pair(window, cx);
13538 this.change_selections(Default::default(), window, cx, |s| {
13539 s.move_with(|map, selection| {
13540 if selection.is_empty() {
13541 let mut cursor = movement::previous_subword_start(map, selection.head());
13542 cursor =
13543 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13544 selection.set_head(cursor, SelectionGoal::None);
13545 }
13546 });
13547 });
13548 this.insert("", window, cx);
13549 });
13550 }
13551
13552 pub fn move_to_next_word_end(
13553 &mut self,
13554 _: &MoveToNextWordEnd,
13555 window: &mut Window,
13556 cx: &mut Context<Self>,
13557 ) {
13558 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13559 self.change_selections(Default::default(), window, cx, |s| {
13560 s.move_cursors_with(|map, head, _| {
13561 (movement::next_word_end(map, head), SelectionGoal::None)
13562 });
13563 })
13564 }
13565
13566 pub fn move_to_next_subword_end(
13567 &mut self,
13568 _: &MoveToNextSubwordEnd,
13569 window: &mut Window,
13570 cx: &mut Context<Self>,
13571 ) {
13572 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13573 self.change_selections(Default::default(), window, cx, |s| {
13574 s.move_cursors_with(|map, head, _| {
13575 (movement::next_subword_end(map, head), SelectionGoal::None)
13576 });
13577 })
13578 }
13579
13580 pub fn select_to_next_word_end(
13581 &mut self,
13582 _: &SelectToNextWordEnd,
13583 window: &mut Window,
13584 cx: &mut Context<Self>,
13585 ) {
13586 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13587 self.change_selections(Default::default(), window, cx, |s| {
13588 s.move_heads_with(|map, head, _| {
13589 (movement::next_word_end(map, head), SelectionGoal::None)
13590 });
13591 })
13592 }
13593
13594 pub fn select_to_next_subword_end(
13595 &mut self,
13596 _: &SelectToNextSubwordEnd,
13597 window: &mut Window,
13598 cx: &mut Context<Self>,
13599 ) {
13600 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13601 self.change_selections(Default::default(), window, cx, |s| {
13602 s.move_heads_with(|map, head, _| {
13603 (movement::next_subword_end(map, head), SelectionGoal::None)
13604 });
13605 })
13606 }
13607
13608 pub fn delete_to_next_word_end(
13609 &mut self,
13610 action: &DeleteToNextWordEnd,
13611 window: &mut Window,
13612 cx: &mut Context<Self>,
13613 ) {
13614 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13615 self.transact(window, cx, |this, window, cx| {
13616 this.change_selections(Default::default(), window, cx, |s| {
13617 s.move_with(|map, selection| {
13618 if selection.is_empty() {
13619 let mut cursor = if action.ignore_newlines {
13620 movement::next_word_end(map, selection.head())
13621 } else {
13622 movement::next_word_end_or_newline(map, selection.head())
13623 };
13624 cursor = movement::adjust_greedy_deletion(
13625 map,
13626 selection.head(),
13627 cursor,
13628 action.ignore_brackets,
13629 );
13630 selection.set_head(cursor, SelectionGoal::None);
13631 }
13632 });
13633 });
13634 this.insert("", window, cx);
13635 });
13636 }
13637
13638 pub fn delete_to_next_subword_end(
13639 &mut self,
13640 _: &DeleteToNextSubwordEnd,
13641 window: &mut Window,
13642 cx: &mut Context<Self>,
13643 ) {
13644 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13645 self.transact(window, cx, |this, window, cx| {
13646 this.change_selections(Default::default(), window, cx, |s| {
13647 s.move_with(|map, selection| {
13648 if selection.is_empty() {
13649 let mut cursor = movement::next_subword_end(map, selection.head());
13650 cursor =
13651 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13652 selection.set_head(cursor, SelectionGoal::None);
13653 }
13654 });
13655 });
13656 this.insert("", window, cx);
13657 });
13658 }
13659
13660 pub fn move_to_beginning_of_line(
13661 &mut self,
13662 action: &MoveToBeginningOfLine,
13663 window: &mut Window,
13664 cx: &mut Context<Self>,
13665 ) {
13666 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13667 self.change_selections(Default::default(), window, cx, |s| {
13668 s.move_cursors_with(|map, head, _| {
13669 (
13670 movement::indented_line_beginning(
13671 map,
13672 head,
13673 action.stop_at_soft_wraps,
13674 action.stop_at_indent,
13675 ),
13676 SelectionGoal::None,
13677 )
13678 });
13679 })
13680 }
13681
13682 pub fn select_to_beginning_of_line(
13683 &mut self,
13684 action: &SelectToBeginningOfLine,
13685 window: &mut Window,
13686 cx: &mut Context<Self>,
13687 ) {
13688 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13689 self.change_selections(Default::default(), window, cx, |s| {
13690 s.move_heads_with(|map, head, _| {
13691 (
13692 movement::indented_line_beginning(
13693 map,
13694 head,
13695 action.stop_at_soft_wraps,
13696 action.stop_at_indent,
13697 ),
13698 SelectionGoal::None,
13699 )
13700 });
13701 });
13702 }
13703
13704 pub fn delete_to_beginning_of_line(
13705 &mut self,
13706 action: &DeleteToBeginningOfLine,
13707 window: &mut Window,
13708 cx: &mut Context<Self>,
13709 ) {
13710 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13711 self.transact(window, cx, |this, window, cx| {
13712 this.change_selections(Default::default(), window, cx, |s| {
13713 s.move_with(|_, selection| {
13714 selection.reversed = true;
13715 });
13716 });
13717
13718 this.select_to_beginning_of_line(
13719 &SelectToBeginningOfLine {
13720 stop_at_soft_wraps: false,
13721 stop_at_indent: action.stop_at_indent,
13722 },
13723 window,
13724 cx,
13725 );
13726 this.backspace(&Backspace, window, cx);
13727 });
13728 }
13729
13730 pub fn move_to_end_of_line(
13731 &mut self,
13732 action: &MoveToEndOfLine,
13733 window: &mut Window,
13734 cx: &mut Context<Self>,
13735 ) {
13736 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13737 self.change_selections(Default::default(), window, cx, |s| {
13738 s.move_cursors_with(|map, head, _| {
13739 (
13740 movement::line_end(map, head, action.stop_at_soft_wraps),
13741 SelectionGoal::None,
13742 )
13743 });
13744 })
13745 }
13746
13747 pub fn select_to_end_of_line(
13748 &mut self,
13749 action: &SelectToEndOfLine,
13750 window: &mut Window,
13751 cx: &mut Context<Self>,
13752 ) {
13753 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13754 self.change_selections(Default::default(), window, cx, |s| {
13755 s.move_heads_with(|map, head, _| {
13756 (
13757 movement::line_end(map, head, action.stop_at_soft_wraps),
13758 SelectionGoal::None,
13759 )
13760 });
13761 })
13762 }
13763
13764 pub fn delete_to_end_of_line(
13765 &mut self,
13766 _: &DeleteToEndOfLine,
13767 window: &mut Window,
13768 cx: &mut Context<Self>,
13769 ) {
13770 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13771 self.transact(window, cx, |this, window, cx| {
13772 this.select_to_end_of_line(
13773 &SelectToEndOfLine {
13774 stop_at_soft_wraps: false,
13775 },
13776 window,
13777 cx,
13778 );
13779 this.delete(&Delete, window, cx);
13780 });
13781 }
13782
13783 pub fn cut_to_end_of_line(
13784 &mut self,
13785 action: &CutToEndOfLine,
13786 window: &mut Window,
13787 cx: &mut Context<Self>,
13788 ) {
13789 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13790 self.transact(window, cx, |this, window, cx| {
13791 this.select_to_end_of_line(
13792 &SelectToEndOfLine {
13793 stop_at_soft_wraps: false,
13794 },
13795 window,
13796 cx,
13797 );
13798 if !action.stop_at_newlines {
13799 this.change_selections(Default::default(), window, cx, |s| {
13800 s.move_with(|_, sel| {
13801 if sel.is_empty() {
13802 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13803 }
13804 });
13805 });
13806 }
13807 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13808 let item = this.cut_common(false, window, cx);
13809 cx.write_to_clipboard(item);
13810 });
13811 }
13812
13813 pub fn move_to_start_of_paragraph(
13814 &mut self,
13815 _: &MoveToStartOfParagraph,
13816 window: &mut Window,
13817 cx: &mut Context<Self>,
13818 ) {
13819 if matches!(self.mode, EditorMode::SingleLine) {
13820 cx.propagate();
13821 return;
13822 }
13823 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13824 self.change_selections(Default::default(), window, cx, |s| {
13825 s.move_with(|map, selection| {
13826 selection.collapse_to(
13827 movement::start_of_paragraph(map, selection.head(), 1),
13828 SelectionGoal::None,
13829 )
13830 });
13831 })
13832 }
13833
13834 pub fn move_to_end_of_paragraph(
13835 &mut self,
13836 _: &MoveToEndOfParagraph,
13837 window: &mut Window,
13838 cx: &mut Context<Self>,
13839 ) {
13840 if matches!(self.mode, EditorMode::SingleLine) {
13841 cx.propagate();
13842 return;
13843 }
13844 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13845 self.change_selections(Default::default(), window, cx, |s| {
13846 s.move_with(|map, selection| {
13847 selection.collapse_to(
13848 movement::end_of_paragraph(map, selection.head(), 1),
13849 SelectionGoal::None,
13850 )
13851 });
13852 })
13853 }
13854
13855 pub fn select_to_start_of_paragraph(
13856 &mut self,
13857 _: &SelectToStartOfParagraph,
13858 window: &mut Window,
13859 cx: &mut Context<Self>,
13860 ) {
13861 if matches!(self.mode, EditorMode::SingleLine) {
13862 cx.propagate();
13863 return;
13864 }
13865 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13866 self.change_selections(Default::default(), window, cx, |s| {
13867 s.move_heads_with(|map, head, _| {
13868 (
13869 movement::start_of_paragraph(map, head, 1),
13870 SelectionGoal::None,
13871 )
13872 });
13873 })
13874 }
13875
13876 pub fn select_to_end_of_paragraph(
13877 &mut self,
13878 _: &SelectToEndOfParagraph,
13879 window: &mut Window,
13880 cx: &mut Context<Self>,
13881 ) {
13882 if matches!(self.mode, EditorMode::SingleLine) {
13883 cx.propagate();
13884 return;
13885 }
13886 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13887 self.change_selections(Default::default(), window, cx, |s| {
13888 s.move_heads_with(|map, head, _| {
13889 (
13890 movement::end_of_paragraph(map, head, 1),
13891 SelectionGoal::None,
13892 )
13893 });
13894 })
13895 }
13896
13897 pub fn move_to_start_of_excerpt(
13898 &mut self,
13899 _: &MoveToStartOfExcerpt,
13900 window: &mut Window,
13901 cx: &mut Context<Self>,
13902 ) {
13903 if matches!(self.mode, EditorMode::SingleLine) {
13904 cx.propagate();
13905 return;
13906 }
13907 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13908 self.change_selections(Default::default(), window, cx, |s| {
13909 s.move_with(|map, selection| {
13910 selection.collapse_to(
13911 movement::start_of_excerpt(
13912 map,
13913 selection.head(),
13914 workspace::searchable::Direction::Prev,
13915 ),
13916 SelectionGoal::None,
13917 )
13918 });
13919 })
13920 }
13921
13922 pub fn move_to_start_of_next_excerpt(
13923 &mut self,
13924 _: &MoveToStartOfNextExcerpt,
13925 window: &mut Window,
13926 cx: &mut Context<Self>,
13927 ) {
13928 if matches!(self.mode, EditorMode::SingleLine) {
13929 cx.propagate();
13930 return;
13931 }
13932
13933 self.change_selections(Default::default(), window, cx, |s| {
13934 s.move_with(|map, selection| {
13935 selection.collapse_to(
13936 movement::start_of_excerpt(
13937 map,
13938 selection.head(),
13939 workspace::searchable::Direction::Next,
13940 ),
13941 SelectionGoal::None,
13942 )
13943 });
13944 })
13945 }
13946
13947 pub fn move_to_end_of_excerpt(
13948 &mut self,
13949 _: &MoveToEndOfExcerpt,
13950 window: &mut Window,
13951 cx: &mut Context<Self>,
13952 ) {
13953 if matches!(self.mode, EditorMode::SingleLine) {
13954 cx.propagate();
13955 return;
13956 }
13957 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13958 self.change_selections(Default::default(), window, cx, |s| {
13959 s.move_with(|map, selection| {
13960 selection.collapse_to(
13961 movement::end_of_excerpt(
13962 map,
13963 selection.head(),
13964 workspace::searchable::Direction::Next,
13965 ),
13966 SelectionGoal::None,
13967 )
13968 });
13969 })
13970 }
13971
13972 pub fn move_to_end_of_previous_excerpt(
13973 &mut self,
13974 _: &MoveToEndOfPreviousExcerpt,
13975 window: &mut Window,
13976 cx: &mut Context<Self>,
13977 ) {
13978 if matches!(self.mode, EditorMode::SingleLine) {
13979 cx.propagate();
13980 return;
13981 }
13982 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13983 self.change_selections(Default::default(), window, cx, |s| {
13984 s.move_with(|map, selection| {
13985 selection.collapse_to(
13986 movement::end_of_excerpt(
13987 map,
13988 selection.head(),
13989 workspace::searchable::Direction::Prev,
13990 ),
13991 SelectionGoal::None,
13992 )
13993 });
13994 })
13995 }
13996
13997 pub fn select_to_start_of_excerpt(
13998 &mut self,
13999 _: &SelectToStartOfExcerpt,
14000 window: &mut Window,
14001 cx: &mut Context<Self>,
14002 ) {
14003 if matches!(self.mode, EditorMode::SingleLine) {
14004 cx.propagate();
14005 return;
14006 }
14007 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14008 self.change_selections(Default::default(), window, cx, |s| {
14009 s.move_heads_with(|map, head, _| {
14010 (
14011 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14012 SelectionGoal::None,
14013 )
14014 });
14015 })
14016 }
14017
14018 pub fn select_to_start_of_next_excerpt(
14019 &mut self,
14020 _: &SelectToStartOfNextExcerpt,
14021 window: &mut Window,
14022 cx: &mut Context<Self>,
14023 ) {
14024 if matches!(self.mode, EditorMode::SingleLine) {
14025 cx.propagate();
14026 return;
14027 }
14028 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14029 self.change_selections(Default::default(), window, cx, |s| {
14030 s.move_heads_with(|map, head, _| {
14031 (
14032 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14033 SelectionGoal::None,
14034 )
14035 });
14036 })
14037 }
14038
14039 pub fn select_to_end_of_excerpt(
14040 &mut self,
14041 _: &SelectToEndOfExcerpt,
14042 window: &mut Window,
14043 cx: &mut Context<Self>,
14044 ) {
14045 if matches!(self.mode, EditorMode::SingleLine) {
14046 cx.propagate();
14047 return;
14048 }
14049 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14050 self.change_selections(Default::default(), window, cx, |s| {
14051 s.move_heads_with(|map, head, _| {
14052 (
14053 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14054 SelectionGoal::None,
14055 )
14056 });
14057 })
14058 }
14059
14060 pub fn select_to_end_of_previous_excerpt(
14061 &mut self,
14062 _: &SelectToEndOfPreviousExcerpt,
14063 window: &mut Window,
14064 cx: &mut Context<Self>,
14065 ) {
14066 if matches!(self.mode, EditorMode::SingleLine) {
14067 cx.propagate();
14068 return;
14069 }
14070 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14071 self.change_selections(Default::default(), window, cx, |s| {
14072 s.move_heads_with(|map, head, _| {
14073 (
14074 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14075 SelectionGoal::None,
14076 )
14077 });
14078 })
14079 }
14080
14081 pub fn move_to_beginning(
14082 &mut self,
14083 _: &MoveToBeginning,
14084 window: &mut Window,
14085 cx: &mut Context<Self>,
14086 ) {
14087 if matches!(self.mode, EditorMode::SingleLine) {
14088 cx.propagate();
14089 return;
14090 }
14091 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14092 self.change_selections(Default::default(), window, cx, |s| {
14093 s.select_ranges(vec![0..0]);
14094 });
14095 }
14096
14097 pub fn select_to_beginning(
14098 &mut self,
14099 _: &SelectToBeginning,
14100 window: &mut Window,
14101 cx: &mut Context<Self>,
14102 ) {
14103 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14104 selection.set_head(Point::zero(), SelectionGoal::None);
14105 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14106 self.change_selections(Default::default(), window, cx, |s| {
14107 s.select(vec![selection]);
14108 });
14109 }
14110
14111 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14112 if matches!(self.mode, EditorMode::SingleLine) {
14113 cx.propagate();
14114 return;
14115 }
14116 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14117 let cursor = self.buffer.read(cx).read(cx).len();
14118 self.change_selections(Default::default(), window, cx, |s| {
14119 s.select_ranges(vec![cursor..cursor])
14120 });
14121 }
14122
14123 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14124 self.nav_history = nav_history;
14125 }
14126
14127 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14128 self.nav_history.as_ref()
14129 }
14130
14131 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14132 self.push_to_nav_history(
14133 self.selections.newest_anchor().head(),
14134 None,
14135 false,
14136 true,
14137 cx,
14138 );
14139 }
14140
14141 fn push_to_nav_history(
14142 &mut self,
14143 cursor_anchor: Anchor,
14144 new_position: Option<Point>,
14145 is_deactivate: bool,
14146 always: bool,
14147 cx: &mut Context<Self>,
14148 ) {
14149 if let Some(nav_history) = self.nav_history.as_mut() {
14150 let buffer = self.buffer.read(cx).read(cx);
14151 let cursor_position = cursor_anchor.to_point(&buffer);
14152 let scroll_state = self.scroll_manager.anchor();
14153 let scroll_top_row = scroll_state.top_row(&buffer);
14154 drop(buffer);
14155
14156 if let Some(new_position) = new_position {
14157 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14158 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14159 return;
14160 }
14161 }
14162
14163 nav_history.push(
14164 Some(NavigationData {
14165 cursor_anchor,
14166 cursor_position,
14167 scroll_anchor: scroll_state,
14168 scroll_top_row,
14169 }),
14170 cx,
14171 );
14172 cx.emit(EditorEvent::PushedToNavHistory {
14173 anchor: cursor_anchor,
14174 is_deactivate,
14175 })
14176 }
14177 }
14178
14179 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14180 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14181 let buffer = self.buffer.read(cx).snapshot(cx);
14182 let mut selection = self.selections.first::<usize>(&self.display_snapshot(cx));
14183 selection.set_head(buffer.len(), SelectionGoal::None);
14184 self.change_selections(Default::default(), window, cx, |s| {
14185 s.select(vec![selection]);
14186 });
14187 }
14188
14189 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14190 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14191 let end = self.buffer.read(cx).read(cx).len();
14192 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14193 s.select_ranges(vec![0..end]);
14194 });
14195 }
14196
14197 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14198 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14199 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14200 let mut selections = self.selections.all::<Point>(&display_map);
14201 let max_point = display_map.buffer_snapshot().max_point();
14202 for selection in &mut selections {
14203 let rows = selection.spanned_rows(true, &display_map);
14204 selection.start = Point::new(rows.start.0, 0);
14205 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14206 selection.reversed = false;
14207 }
14208 self.change_selections(Default::default(), window, cx, |s| {
14209 s.select(selections);
14210 });
14211 }
14212
14213 pub fn split_selection_into_lines(
14214 &mut self,
14215 action: &SplitSelectionIntoLines,
14216 window: &mut Window,
14217 cx: &mut Context<Self>,
14218 ) {
14219 let selections = self
14220 .selections
14221 .all::<Point>(&self.display_snapshot(cx))
14222 .into_iter()
14223 .map(|selection| selection.start..selection.end)
14224 .collect::<Vec<_>>();
14225 self.unfold_ranges(&selections, true, true, cx);
14226
14227 let mut new_selection_ranges = Vec::new();
14228 {
14229 let buffer = self.buffer.read(cx).read(cx);
14230 for selection in selections {
14231 for row in selection.start.row..selection.end.row {
14232 let line_start = Point::new(row, 0);
14233 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14234
14235 if action.keep_selections {
14236 // Keep the selection range for each line
14237 let selection_start = if row == selection.start.row {
14238 selection.start
14239 } else {
14240 line_start
14241 };
14242 new_selection_ranges.push(selection_start..line_end);
14243 } else {
14244 // Collapse to cursor at end of line
14245 new_selection_ranges.push(line_end..line_end);
14246 }
14247 }
14248
14249 let is_multiline_selection = selection.start.row != selection.end.row;
14250 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14251 // so this action feels more ergonomic when paired with other selection operations
14252 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14253 if !should_skip_last {
14254 if action.keep_selections {
14255 if is_multiline_selection {
14256 let line_start = Point::new(selection.end.row, 0);
14257 new_selection_ranges.push(line_start..selection.end);
14258 } else {
14259 new_selection_ranges.push(selection.start..selection.end);
14260 }
14261 } else {
14262 new_selection_ranges.push(selection.end..selection.end);
14263 }
14264 }
14265 }
14266 }
14267 self.change_selections(Default::default(), window, cx, |s| {
14268 s.select_ranges(new_selection_ranges);
14269 });
14270 }
14271
14272 pub fn add_selection_above(
14273 &mut self,
14274 action: &AddSelectionAbove,
14275 window: &mut Window,
14276 cx: &mut Context<Self>,
14277 ) {
14278 self.add_selection(true, action.skip_soft_wrap, window, cx);
14279 }
14280
14281 pub fn add_selection_below(
14282 &mut self,
14283 action: &AddSelectionBelow,
14284 window: &mut Window,
14285 cx: &mut Context<Self>,
14286 ) {
14287 self.add_selection(false, action.skip_soft_wrap, window, cx);
14288 }
14289
14290 fn add_selection(
14291 &mut self,
14292 above: bool,
14293 skip_soft_wrap: bool,
14294 window: &mut Window,
14295 cx: &mut Context<Self>,
14296 ) {
14297 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14298
14299 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14300 let all_selections = self.selections.all::<Point>(&display_map);
14301 let text_layout_details = self.text_layout_details(window);
14302
14303 let (mut columnar_selections, new_selections_to_columnarize) = {
14304 if let Some(state) = self.add_selections_state.as_ref() {
14305 let columnar_selection_ids: HashSet<_> = state
14306 .groups
14307 .iter()
14308 .flat_map(|group| group.stack.iter())
14309 .copied()
14310 .collect();
14311
14312 all_selections
14313 .into_iter()
14314 .partition(|s| columnar_selection_ids.contains(&s.id))
14315 } else {
14316 (Vec::new(), all_selections)
14317 }
14318 };
14319
14320 let mut state = self
14321 .add_selections_state
14322 .take()
14323 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14324
14325 for selection in new_selections_to_columnarize {
14326 let range = selection.display_range(&display_map).sorted();
14327 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14328 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14329 let positions = start_x.min(end_x)..start_x.max(end_x);
14330 let mut stack = Vec::new();
14331 for row in range.start.row().0..=range.end.row().0 {
14332 if let Some(selection) = self.selections.build_columnar_selection(
14333 &display_map,
14334 DisplayRow(row),
14335 &positions,
14336 selection.reversed,
14337 &text_layout_details,
14338 ) {
14339 stack.push(selection.id);
14340 columnar_selections.push(selection);
14341 }
14342 }
14343 if !stack.is_empty() {
14344 if above {
14345 stack.reverse();
14346 }
14347 state.groups.push(AddSelectionsGroup { above, stack });
14348 }
14349 }
14350
14351 let mut final_selections = Vec::new();
14352 let end_row = if above {
14353 DisplayRow(0)
14354 } else {
14355 display_map.max_point().row()
14356 };
14357
14358 let mut last_added_item_per_group = HashMap::default();
14359 for group in state.groups.iter_mut() {
14360 if let Some(last_id) = group.stack.last() {
14361 last_added_item_per_group.insert(*last_id, group);
14362 }
14363 }
14364
14365 for selection in columnar_selections {
14366 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14367 if above == group.above {
14368 let range = selection.display_range(&display_map).sorted();
14369 debug_assert_eq!(range.start.row(), range.end.row());
14370 let mut row = range.start.row();
14371 let positions =
14372 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14373 Pixels::from(start)..Pixels::from(end)
14374 } else {
14375 let start_x =
14376 display_map.x_for_display_point(range.start, &text_layout_details);
14377 let end_x =
14378 display_map.x_for_display_point(range.end, &text_layout_details);
14379 start_x.min(end_x)..start_x.max(end_x)
14380 };
14381
14382 let mut maybe_new_selection = None;
14383 let direction = if above { -1 } else { 1 };
14384
14385 while row != end_row {
14386 if skip_soft_wrap {
14387 row = display_map
14388 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14389 .row();
14390 } else if above {
14391 row.0 -= 1;
14392 } else {
14393 row.0 += 1;
14394 }
14395
14396 if let Some(new_selection) = self.selections.build_columnar_selection(
14397 &display_map,
14398 row,
14399 &positions,
14400 selection.reversed,
14401 &text_layout_details,
14402 ) {
14403 maybe_new_selection = Some(new_selection);
14404 break;
14405 }
14406 }
14407
14408 if let Some(new_selection) = maybe_new_selection {
14409 group.stack.push(new_selection.id);
14410 if above {
14411 final_selections.push(new_selection);
14412 final_selections.push(selection);
14413 } else {
14414 final_selections.push(selection);
14415 final_selections.push(new_selection);
14416 }
14417 } else {
14418 final_selections.push(selection);
14419 }
14420 } else {
14421 group.stack.pop();
14422 }
14423 } else {
14424 final_selections.push(selection);
14425 }
14426 }
14427
14428 self.change_selections(Default::default(), window, cx, |s| {
14429 s.select(final_selections);
14430 });
14431
14432 let final_selection_ids: HashSet<_> = self
14433 .selections
14434 .all::<Point>(&display_map)
14435 .iter()
14436 .map(|s| s.id)
14437 .collect();
14438 state.groups.retain_mut(|group| {
14439 // selections might get merged above so we remove invalid items from stacks
14440 group.stack.retain(|id| final_selection_ids.contains(id));
14441
14442 // single selection in stack can be treated as initial state
14443 group.stack.len() > 1
14444 });
14445
14446 if !state.groups.is_empty() {
14447 self.add_selections_state = Some(state);
14448 }
14449 }
14450
14451 fn select_match_ranges(
14452 &mut self,
14453 range: Range<usize>,
14454 reversed: bool,
14455 replace_newest: bool,
14456 auto_scroll: Option<Autoscroll>,
14457 window: &mut Window,
14458 cx: &mut Context<Editor>,
14459 ) {
14460 self.unfold_ranges(
14461 std::slice::from_ref(&range),
14462 false,
14463 auto_scroll.is_some(),
14464 cx,
14465 );
14466 let effects = if let Some(scroll) = auto_scroll {
14467 SelectionEffects::scroll(scroll)
14468 } else {
14469 SelectionEffects::no_scroll()
14470 };
14471 self.change_selections(effects, window, cx, |s| {
14472 if replace_newest {
14473 s.delete(s.newest_anchor().id);
14474 }
14475 if reversed {
14476 s.insert_range(range.end..range.start);
14477 } else {
14478 s.insert_range(range);
14479 }
14480 });
14481 }
14482
14483 pub fn select_next_match_internal(
14484 &mut self,
14485 display_map: &DisplaySnapshot,
14486 replace_newest: bool,
14487 autoscroll: Option<Autoscroll>,
14488 window: &mut Window,
14489 cx: &mut Context<Self>,
14490 ) -> Result<()> {
14491 let buffer = display_map.buffer_snapshot();
14492 let mut selections = self.selections.all::<usize>(&display_map);
14493 if let Some(mut select_next_state) = self.select_next_state.take() {
14494 let query = &select_next_state.query;
14495 if !select_next_state.done {
14496 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14497 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14498 let mut next_selected_range = None;
14499
14500 // Collect and sort selection ranges for efficient overlap checking
14501 let mut selection_ranges: Vec<_> = selections.iter().map(|s| s.range()).collect();
14502 selection_ranges.sort_by_key(|r| r.start);
14503
14504 let bytes_after_last_selection =
14505 buffer.bytes_in_range(last_selection.end..buffer.len());
14506 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14507 let query_matches = query
14508 .stream_find_iter(bytes_after_last_selection)
14509 .map(|result| (last_selection.end, result))
14510 .chain(
14511 query
14512 .stream_find_iter(bytes_before_first_selection)
14513 .map(|result| (0, result)),
14514 );
14515
14516 for (start_offset, query_match) in query_matches {
14517 let query_match = query_match.unwrap(); // can only fail due to I/O
14518 let offset_range =
14519 start_offset + query_match.start()..start_offset + query_match.end();
14520
14521 if !select_next_state.wordwise
14522 || (!buffer.is_inside_word(offset_range.start, None)
14523 && !buffer.is_inside_word(offset_range.end, None))
14524 {
14525 // Use binary search to check for overlap (O(log n))
14526 let overlaps = selection_ranges
14527 .binary_search_by(|range| {
14528 if range.end <= offset_range.start {
14529 std::cmp::Ordering::Less
14530 } else if range.start >= offset_range.end {
14531 std::cmp::Ordering::Greater
14532 } else {
14533 std::cmp::Ordering::Equal
14534 }
14535 })
14536 .is_ok();
14537
14538 if !overlaps {
14539 next_selected_range = Some(offset_range);
14540 break;
14541 }
14542 }
14543 }
14544
14545 if let Some(next_selected_range) = next_selected_range {
14546 self.select_match_ranges(
14547 next_selected_range,
14548 last_selection.reversed,
14549 replace_newest,
14550 autoscroll,
14551 window,
14552 cx,
14553 );
14554 } else {
14555 select_next_state.done = true;
14556 }
14557 }
14558
14559 self.select_next_state = Some(select_next_state);
14560 } else {
14561 let mut only_carets = true;
14562 let mut same_text_selected = true;
14563 let mut selected_text = None;
14564
14565 let mut selections_iter = selections.iter().peekable();
14566 while let Some(selection) = selections_iter.next() {
14567 if selection.start != selection.end {
14568 only_carets = false;
14569 }
14570
14571 if same_text_selected {
14572 if selected_text.is_none() {
14573 selected_text =
14574 Some(buffer.text_for_range(selection.range()).collect::<String>());
14575 }
14576
14577 if let Some(next_selection) = selections_iter.peek() {
14578 if next_selection.range().len() == selection.range().len() {
14579 let next_selected_text = buffer
14580 .text_for_range(next_selection.range())
14581 .collect::<String>();
14582 if Some(next_selected_text) != selected_text {
14583 same_text_selected = false;
14584 selected_text = None;
14585 }
14586 } else {
14587 same_text_selected = false;
14588 selected_text = None;
14589 }
14590 }
14591 }
14592 }
14593
14594 if only_carets {
14595 for selection in &mut selections {
14596 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14597 selection.start = word_range.start;
14598 selection.end = word_range.end;
14599 selection.goal = SelectionGoal::None;
14600 selection.reversed = false;
14601 self.select_match_ranges(
14602 selection.start..selection.end,
14603 selection.reversed,
14604 replace_newest,
14605 autoscroll,
14606 window,
14607 cx,
14608 );
14609 }
14610
14611 if selections.len() == 1 {
14612 let selection = selections
14613 .last()
14614 .expect("ensured that there's only one selection");
14615 let query = buffer
14616 .text_for_range(selection.start..selection.end)
14617 .collect::<String>();
14618 let is_empty = query.is_empty();
14619 let select_state = SelectNextState {
14620 query: AhoCorasick::new(&[query])?,
14621 wordwise: true,
14622 done: is_empty,
14623 };
14624 self.select_next_state = Some(select_state);
14625 } else {
14626 self.select_next_state = None;
14627 }
14628 } else if let Some(selected_text) = selected_text {
14629 self.select_next_state = Some(SelectNextState {
14630 query: AhoCorasick::new(&[selected_text])?,
14631 wordwise: false,
14632 done: false,
14633 });
14634 self.select_next_match_internal(
14635 display_map,
14636 replace_newest,
14637 autoscroll,
14638 window,
14639 cx,
14640 )?;
14641 }
14642 }
14643 Ok(())
14644 }
14645
14646 pub fn select_all_matches(
14647 &mut self,
14648 _action: &SelectAllMatches,
14649 window: &mut Window,
14650 cx: &mut Context<Self>,
14651 ) -> Result<()> {
14652 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14653
14654 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14655
14656 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14657 let Some(select_next_state) = self.select_next_state.as_mut() else {
14658 return Ok(());
14659 };
14660 if select_next_state.done {
14661 return Ok(());
14662 }
14663
14664 let mut new_selections = Vec::new();
14665
14666 let reversed = self.selections.oldest::<usize>(&display_map).reversed;
14667 let buffer = display_map.buffer_snapshot();
14668 let query_matches = select_next_state
14669 .query
14670 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14671
14672 for query_match in query_matches.into_iter() {
14673 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14674 let offset_range = if reversed {
14675 query_match.end()..query_match.start()
14676 } else {
14677 query_match.start()..query_match.end()
14678 };
14679
14680 if !select_next_state.wordwise
14681 || (!buffer.is_inside_word(offset_range.start, None)
14682 && !buffer.is_inside_word(offset_range.end, None))
14683 {
14684 new_selections.push(offset_range.start..offset_range.end);
14685 }
14686 }
14687
14688 select_next_state.done = true;
14689
14690 if new_selections.is_empty() {
14691 log::error!("bug: new_selections is empty in select_all_matches");
14692 return Ok(());
14693 }
14694
14695 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14696 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14697 selections.select_ranges(new_selections)
14698 });
14699
14700 Ok(())
14701 }
14702
14703 pub fn select_next(
14704 &mut self,
14705 action: &SelectNext,
14706 window: &mut Window,
14707 cx: &mut Context<Self>,
14708 ) -> Result<()> {
14709 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14710 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14711 self.select_next_match_internal(
14712 &display_map,
14713 action.replace_newest,
14714 Some(Autoscroll::newest()),
14715 window,
14716 cx,
14717 )?;
14718 Ok(())
14719 }
14720
14721 pub fn select_previous(
14722 &mut self,
14723 action: &SelectPrevious,
14724 window: &mut Window,
14725 cx: &mut Context<Self>,
14726 ) -> Result<()> {
14727 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14728 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14729 let buffer = display_map.buffer_snapshot();
14730 let mut selections = self.selections.all::<usize>(&display_map);
14731 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14732 let query = &select_prev_state.query;
14733 if !select_prev_state.done {
14734 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14735 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14736 let mut next_selected_range = None;
14737 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14738 let bytes_before_last_selection =
14739 buffer.reversed_bytes_in_range(0..last_selection.start);
14740 let bytes_after_first_selection =
14741 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14742 let query_matches = query
14743 .stream_find_iter(bytes_before_last_selection)
14744 .map(|result| (last_selection.start, result))
14745 .chain(
14746 query
14747 .stream_find_iter(bytes_after_first_selection)
14748 .map(|result| (buffer.len(), result)),
14749 );
14750 for (end_offset, query_match) in query_matches {
14751 let query_match = query_match.unwrap(); // can only fail due to I/O
14752 let offset_range =
14753 end_offset - query_match.end()..end_offset - query_match.start();
14754
14755 if !select_prev_state.wordwise
14756 || (!buffer.is_inside_word(offset_range.start, None)
14757 && !buffer.is_inside_word(offset_range.end, None))
14758 {
14759 next_selected_range = Some(offset_range);
14760 break;
14761 }
14762 }
14763
14764 if let Some(next_selected_range) = next_selected_range {
14765 self.select_match_ranges(
14766 next_selected_range,
14767 last_selection.reversed,
14768 action.replace_newest,
14769 Some(Autoscroll::newest()),
14770 window,
14771 cx,
14772 );
14773 } else {
14774 select_prev_state.done = true;
14775 }
14776 }
14777
14778 self.select_prev_state = Some(select_prev_state);
14779 } else {
14780 let mut only_carets = true;
14781 let mut same_text_selected = true;
14782 let mut selected_text = None;
14783
14784 let mut selections_iter = selections.iter().peekable();
14785 while let Some(selection) = selections_iter.next() {
14786 if selection.start != selection.end {
14787 only_carets = false;
14788 }
14789
14790 if same_text_selected {
14791 if selected_text.is_none() {
14792 selected_text =
14793 Some(buffer.text_for_range(selection.range()).collect::<String>());
14794 }
14795
14796 if let Some(next_selection) = selections_iter.peek() {
14797 if next_selection.range().len() == selection.range().len() {
14798 let next_selected_text = buffer
14799 .text_for_range(next_selection.range())
14800 .collect::<String>();
14801 if Some(next_selected_text) != selected_text {
14802 same_text_selected = false;
14803 selected_text = None;
14804 }
14805 } else {
14806 same_text_selected = false;
14807 selected_text = None;
14808 }
14809 }
14810 }
14811 }
14812
14813 if only_carets {
14814 for selection in &mut selections {
14815 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14816 selection.start = word_range.start;
14817 selection.end = word_range.end;
14818 selection.goal = SelectionGoal::None;
14819 selection.reversed = false;
14820 self.select_match_ranges(
14821 selection.start..selection.end,
14822 selection.reversed,
14823 action.replace_newest,
14824 Some(Autoscroll::newest()),
14825 window,
14826 cx,
14827 );
14828 }
14829 if selections.len() == 1 {
14830 let selection = selections
14831 .last()
14832 .expect("ensured that there's only one selection");
14833 let query = buffer
14834 .text_for_range(selection.start..selection.end)
14835 .collect::<String>();
14836 let is_empty = query.is_empty();
14837 let select_state = SelectNextState {
14838 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14839 wordwise: true,
14840 done: is_empty,
14841 };
14842 self.select_prev_state = Some(select_state);
14843 } else {
14844 self.select_prev_state = None;
14845 }
14846 } else if let Some(selected_text) = selected_text {
14847 self.select_prev_state = Some(SelectNextState {
14848 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14849 wordwise: false,
14850 done: false,
14851 });
14852 self.select_previous(action, window, cx)?;
14853 }
14854 }
14855 Ok(())
14856 }
14857
14858 pub fn find_next_match(
14859 &mut self,
14860 _: &FindNextMatch,
14861 window: &mut Window,
14862 cx: &mut Context<Self>,
14863 ) -> Result<()> {
14864 let selections = self.selections.disjoint_anchors_arc();
14865 match selections.first() {
14866 Some(first) if selections.len() >= 2 => {
14867 self.change_selections(Default::default(), window, cx, |s| {
14868 s.select_ranges([first.range()]);
14869 });
14870 }
14871 _ => self.select_next(
14872 &SelectNext {
14873 replace_newest: true,
14874 },
14875 window,
14876 cx,
14877 )?,
14878 }
14879 Ok(())
14880 }
14881
14882 pub fn find_previous_match(
14883 &mut self,
14884 _: &FindPreviousMatch,
14885 window: &mut Window,
14886 cx: &mut Context<Self>,
14887 ) -> Result<()> {
14888 let selections = self.selections.disjoint_anchors_arc();
14889 match selections.last() {
14890 Some(last) if selections.len() >= 2 => {
14891 self.change_selections(Default::default(), window, cx, |s| {
14892 s.select_ranges([last.range()]);
14893 });
14894 }
14895 _ => self.select_previous(
14896 &SelectPrevious {
14897 replace_newest: true,
14898 },
14899 window,
14900 cx,
14901 )?,
14902 }
14903 Ok(())
14904 }
14905
14906 pub fn toggle_comments(
14907 &mut self,
14908 action: &ToggleComments,
14909 window: &mut Window,
14910 cx: &mut Context<Self>,
14911 ) {
14912 if self.read_only(cx) {
14913 return;
14914 }
14915 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14916 let text_layout_details = &self.text_layout_details(window);
14917 self.transact(window, cx, |this, window, cx| {
14918 let mut selections = this
14919 .selections
14920 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
14921 let mut edits = Vec::new();
14922 let mut selection_edit_ranges = Vec::new();
14923 let mut last_toggled_row = None;
14924 let snapshot = this.buffer.read(cx).read(cx);
14925 let empty_str: Arc<str> = Arc::default();
14926 let mut suffixes_inserted = Vec::new();
14927 let ignore_indent = action.ignore_indent;
14928
14929 fn comment_prefix_range(
14930 snapshot: &MultiBufferSnapshot,
14931 row: MultiBufferRow,
14932 comment_prefix: &str,
14933 comment_prefix_whitespace: &str,
14934 ignore_indent: bool,
14935 ) -> Range<Point> {
14936 let indent_size = if ignore_indent {
14937 0
14938 } else {
14939 snapshot.indent_size_for_line(row).len
14940 };
14941
14942 let start = Point::new(row.0, indent_size);
14943
14944 let mut line_bytes = snapshot
14945 .bytes_in_range(start..snapshot.max_point())
14946 .flatten()
14947 .copied();
14948
14949 // If this line currently begins with the line comment prefix, then record
14950 // the range containing the prefix.
14951 if line_bytes
14952 .by_ref()
14953 .take(comment_prefix.len())
14954 .eq(comment_prefix.bytes())
14955 {
14956 // Include any whitespace that matches the comment prefix.
14957 let matching_whitespace_len = line_bytes
14958 .zip(comment_prefix_whitespace.bytes())
14959 .take_while(|(a, b)| a == b)
14960 .count() as u32;
14961 let end = Point::new(
14962 start.row,
14963 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14964 );
14965 start..end
14966 } else {
14967 start..start
14968 }
14969 }
14970
14971 fn comment_suffix_range(
14972 snapshot: &MultiBufferSnapshot,
14973 row: MultiBufferRow,
14974 comment_suffix: &str,
14975 comment_suffix_has_leading_space: bool,
14976 ) -> Range<Point> {
14977 let end = Point::new(row.0, snapshot.line_len(row));
14978 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14979
14980 let mut line_end_bytes = snapshot
14981 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14982 .flatten()
14983 .copied();
14984
14985 let leading_space_len = if suffix_start_column > 0
14986 && line_end_bytes.next() == Some(b' ')
14987 && comment_suffix_has_leading_space
14988 {
14989 1
14990 } else {
14991 0
14992 };
14993
14994 // If this line currently begins with the line comment prefix, then record
14995 // the range containing the prefix.
14996 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14997 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14998 start..end
14999 } else {
15000 end..end
15001 }
15002 }
15003
15004 // TODO: Handle selections that cross excerpts
15005 for selection in &mut selections {
15006 let start_column = snapshot
15007 .indent_size_for_line(MultiBufferRow(selection.start.row))
15008 .len;
15009 let language = if let Some(language) =
15010 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15011 {
15012 language
15013 } else {
15014 continue;
15015 };
15016
15017 selection_edit_ranges.clear();
15018
15019 // If multiple selections contain a given row, avoid processing that
15020 // row more than once.
15021 let mut start_row = MultiBufferRow(selection.start.row);
15022 if last_toggled_row == Some(start_row) {
15023 start_row = start_row.next_row();
15024 }
15025 let end_row =
15026 if selection.end.row > selection.start.row && selection.end.column == 0 {
15027 MultiBufferRow(selection.end.row - 1)
15028 } else {
15029 MultiBufferRow(selection.end.row)
15030 };
15031 last_toggled_row = Some(end_row);
15032
15033 if start_row > end_row {
15034 continue;
15035 }
15036
15037 // If the language has line comments, toggle those.
15038 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15039
15040 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15041 if ignore_indent {
15042 full_comment_prefixes = full_comment_prefixes
15043 .into_iter()
15044 .map(|s| Arc::from(s.trim_end()))
15045 .collect();
15046 }
15047
15048 if !full_comment_prefixes.is_empty() {
15049 let first_prefix = full_comment_prefixes
15050 .first()
15051 .expect("prefixes is non-empty");
15052 let prefix_trimmed_lengths = full_comment_prefixes
15053 .iter()
15054 .map(|p| p.trim_end_matches(' ').len())
15055 .collect::<SmallVec<[usize; 4]>>();
15056
15057 let mut all_selection_lines_are_comments = true;
15058
15059 for row in start_row.0..=end_row.0 {
15060 let row = MultiBufferRow(row);
15061 if start_row < end_row && snapshot.is_line_blank(row) {
15062 continue;
15063 }
15064
15065 let prefix_range = full_comment_prefixes
15066 .iter()
15067 .zip(prefix_trimmed_lengths.iter().copied())
15068 .map(|(prefix, trimmed_prefix_len)| {
15069 comment_prefix_range(
15070 snapshot.deref(),
15071 row,
15072 &prefix[..trimmed_prefix_len],
15073 &prefix[trimmed_prefix_len..],
15074 ignore_indent,
15075 )
15076 })
15077 .max_by_key(|range| range.end.column - range.start.column)
15078 .expect("prefixes is non-empty");
15079
15080 if prefix_range.is_empty() {
15081 all_selection_lines_are_comments = false;
15082 }
15083
15084 selection_edit_ranges.push(prefix_range);
15085 }
15086
15087 if all_selection_lines_are_comments {
15088 edits.extend(
15089 selection_edit_ranges
15090 .iter()
15091 .cloned()
15092 .map(|range| (range, empty_str.clone())),
15093 );
15094 } else {
15095 let min_column = selection_edit_ranges
15096 .iter()
15097 .map(|range| range.start.column)
15098 .min()
15099 .unwrap_or(0);
15100 edits.extend(selection_edit_ranges.iter().map(|range| {
15101 let position = Point::new(range.start.row, min_column);
15102 (position..position, first_prefix.clone())
15103 }));
15104 }
15105 } else if let Some(BlockCommentConfig {
15106 start: full_comment_prefix,
15107 end: comment_suffix,
15108 ..
15109 }) = language.block_comment()
15110 {
15111 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15112 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15113 let prefix_range = comment_prefix_range(
15114 snapshot.deref(),
15115 start_row,
15116 comment_prefix,
15117 comment_prefix_whitespace,
15118 ignore_indent,
15119 );
15120 let suffix_range = comment_suffix_range(
15121 snapshot.deref(),
15122 end_row,
15123 comment_suffix.trim_start_matches(' '),
15124 comment_suffix.starts_with(' '),
15125 );
15126
15127 if prefix_range.is_empty() || suffix_range.is_empty() {
15128 edits.push((
15129 prefix_range.start..prefix_range.start,
15130 full_comment_prefix.clone(),
15131 ));
15132 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15133 suffixes_inserted.push((end_row, comment_suffix.len()));
15134 } else {
15135 edits.push((prefix_range, empty_str.clone()));
15136 edits.push((suffix_range, empty_str.clone()));
15137 }
15138 } else {
15139 continue;
15140 }
15141 }
15142
15143 drop(snapshot);
15144 this.buffer.update(cx, |buffer, cx| {
15145 buffer.edit(edits, None, cx);
15146 });
15147
15148 // Adjust selections so that they end before any comment suffixes that
15149 // were inserted.
15150 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15151 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15152 let snapshot = this.buffer.read(cx).read(cx);
15153 for selection in &mut selections {
15154 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15155 match row.cmp(&MultiBufferRow(selection.end.row)) {
15156 Ordering::Less => {
15157 suffixes_inserted.next();
15158 continue;
15159 }
15160 Ordering::Greater => break,
15161 Ordering::Equal => {
15162 if selection.end.column == snapshot.line_len(row) {
15163 if selection.is_empty() {
15164 selection.start.column -= suffix_len as u32;
15165 }
15166 selection.end.column -= suffix_len as u32;
15167 }
15168 break;
15169 }
15170 }
15171 }
15172 }
15173
15174 drop(snapshot);
15175 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15176
15177 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15178 let selections_on_single_row = selections.windows(2).all(|selections| {
15179 selections[0].start.row == selections[1].start.row
15180 && selections[0].end.row == selections[1].end.row
15181 && selections[0].start.row == selections[0].end.row
15182 });
15183 let selections_selecting = selections
15184 .iter()
15185 .any(|selection| selection.start != selection.end);
15186 let advance_downwards = action.advance_downwards
15187 && selections_on_single_row
15188 && !selections_selecting
15189 && !matches!(this.mode, EditorMode::SingleLine);
15190
15191 if advance_downwards {
15192 let snapshot = this.buffer.read(cx).snapshot(cx);
15193
15194 this.change_selections(Default::default(), window, cx, |s| {
15195 s.move_cursors_with(|display_snapshot, display_point, _| {
15196 let mut point = display_point.to_point(display_snapshot);
15197 point.row += 1;
15198 point = snapshot.clip_point(point, Bias::Left);
15199 let display_point = point.to_display_point(display_snapshot);
15200 let goal = SelectionGoal::HorizontalPosition(
15201 display_snapshot
15202 .x_for_display_point(display_point, text_layout_details)
15203 .into(),
15204 );
15205 (display_point, goal)
15206 })
15207 });
15208 }
15209 });
15210 }
15211
15212 pub fn select_enclosing_symbol(
15213 &mut self,
15214 _: &SelectEnclosingSymbol,
15215 window: &mut Window,
15216 cx: &mut Context<Self>,
15217 ) {
15218 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15219
15220 let buffer = self.buffer.read(cx).snapshot(cx);
15221 let old_selections = self
15222 .selections
15223 .all::<usize>(&self.display_snapshot(cx))
15224 .into_boxed_slice();
15225
15226 fn update_selection(
15227 selection: &Selection<usize>,
15228 buffer_snap: &MultiBufferSnapshot,
15229 ) -> Option<Selection<usize>> {
15230 let cursor = selection.head();
15231 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15232 for symbol in symbols.iter().rev() {
15233 let start = symbol.range.start.to_offset(buffer_snap);
15234 let end = symbol.range.end.to_offset(buffer_snap);
15235 let new_range = start..end;
15236 if start < selection.start || end > selection.end {
15237 return Some(Selection {
15238 id: selection.id,
15239 start: new_range.start,
15240 end: new_range.end,
15241 goal: SelectionGoal::None,
15242 reversed: selection.reversed,
15243 });
15244 }
15245 }
15246 None
15247 }
15248
15249 let mut selected_larger_symbol = false;
15250 let new_selections = old_selections
15251 .iter()
15252 .map(|selection| match update_selection(selection, &buffer) {
15253 Some(new_selection) => {
15254 if new_selection.range() != selection.range() {
15255 selected_larger_symbol = true;
15256 }
15257 new_selection
15258 }
15259 None => selection.clone(),
15260 })
15261 .collect::<Vec<_>>();
15262
15263 if selected_larger_symbol {
15264 self.change_selections(Default::default(), window, cx, |s| {
15265 s.select(new_selections);
15266 });
15267 }
15268 }
15269
15270 pub fn select_larger_syntax_node(
15271 &mut self,
15272 _: &SelectLargerSyntaxNode,
15273 window: &mut Window,
15274 cx: &mut Context<Self>,
15275 ) {
15276 let Some(visible_row_count) = self.visible_row_count() else {
15277 return;
15278 };
15279 let old_selections: Box<[_]> = self
15280 .selections
15281 .all::<usize>(&self.display_snapshot(cx))
15282 .into();
15283 if old_selections.is_empty() {
15284 return;
15285 }
15286
15287 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15288
15289 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15290 let buffer = self.buffer.read(cx).snapshot(cx);
15291
15292 let mut selected_larger_node = false;
15293 let mut new_selections = old_selections
15294 .iter()
15295 .map(|selection| {
15296 let old_range = selection.start..selection.end;
15297
15298 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15299 // manually select word at selection
15300 if ["string_content", "inline"].contains(&node.kind()) {
15301 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15302 // ignore if word is already selected
15303 if !word_range.is_empty() && old_range != word_range {
15304 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15305 // only select word if start and end point belongs to same word
15306 if word_range == last_word_range {
15307 selected_larger_node = true;
15308 return Selection {
15309 id: selection.id,
15310 start: word_range.start,
15311 end: word_range.end,
15312 goal: SelectionGoal::None,
15313 reversed: selection.reversed,
15314 };
15315 }
15316 }
15317 }
15318 }
15319
15320 let mut new_range = old_range.clone();
15321 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15322 new_range = range;
15323 if !node.is_named() {
15324 continue;
15325 }
15326 if !display_map.intersects_fold(new_range.start)
15327 && !display_map.intersects_fold(new_range.end)
15328 {
15329 break;
15330 }
15331 }
15332
15333 selected_larger_node |= new_range != old_range;
15334 Selection {
15335 id: selection.id,
15336 start: new_range.start,
15337 end: new_range.end,
15338 goal: SelectionGoal::None,
15339 reversed: selection.reversed,
15340 }
15341 })
15342 .collect::<Vec<_>>();
15343
15344 if !selected_larger_node {
15345 return; // don't put this call in the history
15346 }
15347
15348 // scroll based on transformation done to the last selection created by the user
15349 let (last_old, last_new) = old_selections
15350 .last()
15351 .zip(new_selections.last().cloned())
15352 .expect("old_selections isn't empty");
15353
15354 // revert selection
15355 let is_selection_reversed = {
15356 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15357 new_selections.last_mut().expect("checked above").reversed =
15358 should_newest_selection_be_reversed;
15359 should_newest_selection_be_reversed
15360 };
15361
15362 if selected_larger_node {
15363 self.select_syntax_node_history.disable_clearing = true;
15364 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15365 s.select(new_selections.clone());
15366 });
15367 self.select_syntax_node_history.disable_clearing = false;
15368 }
15369
15370 let start_row = last_new.start.to_display_point(&display_map).row().0;
15371 let end_row = last_new.end.to_display_point(&display_map).row().0;
15372 let selection_height = end_row - start_row + 1;
15373 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15374
15375 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15376 let scroll_behavior = if fits_on_the_screen {
15377 self.request_autoscroll(Autoscroll::fit(), cx);
15378 SelectSyntaxNodeScrollBehavior::FitSelection
15379 } else if is_selection_reversed {
15380 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15381 SelectSyntaxNodeScrollBehavior::CursorTop
15382 } else {
15383 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15384 SelectSyntaxNodeScrollBehavior::CursorBottom
15385 };
15386
15387 self.select_syntax_node_history.push((
15388 old_selections,
15389 scroll_behavior,
15390 is_selection_reversed,
15391 ));
15392 }
15393
15394 pub fn select_smaller_syntax_node(
15395 &mut self,
15396 _: &SelectSmallerSyntaxNode,
15397 window: &mut Window,
15398 cx: &mut Context<Self>,
15399 ) {
15400 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15401
15402 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15403 self.select_syntax_node_history.pop()
15404 {
15405 if let Some(selection) = selections.last_mut() {
15406 selection.reversed = is_selection_reversed;
15407 }
15408
15409 self.select_syntax_node_history.disable_clearing = true;
15410 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15411 s.select(selections.to_vec());
15412 });
15413 self.select_syntax_node_history.disable_clearing = false;
15414
15415 match scroll_behavior {
15416 SelectSyntaxNodeScrollBehavior::CursorTop => {
15417 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15418 }
15419 SelectSyntaxNodeScrollBehavior::FitSelection => {
15420 self.request_autoscroll(Autoscroll::fit(), cx);
15421 }
15422 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15423 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15424 }
15425 }
15426 }
15427 }
15428
15429 pub fn unwrap_syntax_node(
15430 &mut self,
15431 _: &UnwrapSyntaxNode,
15432 window: &mut Window,
15433 cx: &mut Context<Self>,
15434 ) {
15435 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15436
15437 let buffer = self.buffer.read(cx).snapshot(cx);
15438 let selections = self
15439 .selections
15440 .all::<usize>(&self.display_snapshot(cx))
15441 .into_iter()
15442 // subtracting the offset requires sorting
15443 .sorted_by_key(|i| i.start);
15444
15445 let full_edits = selections
15446 .into_iter()
15447 .filter_map(|selection| {
15448 let child = if selection.is_empty()
15449 && let Some((_, ancestor_range)) =
15450 buffer.syntax_ancestor(selection.start..selection.end)
15451 {
15452 ancestor_range
15453 } else {
15454 selection.range()
15455 };
15456
15457 let mut parent = child.clone();
15458 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15459 parent = ancestor_range;
15460 if parent.start < child.start || parent.end > child.end {
15461 break;
15462 }
15463 }
15464
15465 if parent == child {
15466 return None;
15467 }
15468 let text = buffer.text_for_range(child).collect::<String>();
15469 Some((selection.id, parent, text))
15470 })
15471 .collect::<Vec<_>>();
15472 if full_edits.is_empty() {
15473 return;
15474 }
15475
15476 self.transact(window, cx, |this, window, cx| {
15477 this.buffer.update(cx, |buffer, cx| {
15478 buffer.edit(
15479 full_edits
15480 .iter()
15481 .map(|(_, p, t)| (p.clone(), t.clone()))
15482 .collect::<Vec<_>>(),
15483 None,
15484 cx,
15485 );
15486 });
15487 this.change_selections(Default::default(), window, cx, |s| {
15488 let mut offset = 0;
15489 let mut selections = vec![];
15490 for (id, parent, text) in full_edits {
15491 let start = parent.start - offset;
15492 offset += parent.len() - text.len();
15493 selections.push(Selection {
15494 id,
15495 start,
15496 end: start + text.len(),
15497 reversed: false,
15498 goal: Default::default(),
15499 });
15500 }
15501 s.select(selections);
15502 });
15503 });
15504 }
15505
15506 pub fn select_next_syntax_node(
15507 &mut self,
15508 _: &SelectNextSyntaxNode,
15509 window: &mut Window,
15510 cx: &mut Context<Self>,
15511 ) {
15512 let old_selections: Box<[_]> = self
15513 .selections
15514 .all::<usize>(&self.display_snapshot(cx))
15515 .into();
15516 if old_selections.is_empty() {
15517 return;
15518 }
15519
15520 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15521
15522 let buffer = self.buffer.read(cx).snapshot(cx);
15523 let mut selected_sibling = false;
15524
15525 let new_selections = old_selections
15526 .iter()
15527 .map(|selection| {
15528 let old_range = selection.start..selection.end;
15529
15530 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15531 let new_range = node.byte_range();
15532 selected_sibling = true;
15533 Selection {
15534 id: selection.id,
15535 start: new_range.start,
15536 end: new_range.end,
15537 goal: SelectionGoal::None,
15538 reversed: selection.reversed,
15539 }
15540 } else {
15541 selection.clone()
15542 }
15543 })
15544 .collect::<Vec<_>>();
15545
15546 if selected_sibling {
15547 self.change_selections(
15548 SelectionEffects::scroll(Autoscroll::fit()),
15549 window,
15550 cx,
15551 |s| {
15552 s.select(new_selections);
15553 },
15554 );
15555 }
15556 }
15557
15558 pub fn select_prev_syntax_node(
15559 &mut self,
15560 _: &SelectPreviousSyntaxNode,
15561 window: &mut Window,
15562 cx: &mut Context<Self>,
15563 ) {
15564 let old_selections: Box<[_]> = self
15565 .selections
15566 .all::<usize>(&self.display_snapshot(cx))
15567 .into();
15568 if old_selections.is_empty() {
15569 return;
15570 }
15571
15572 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15573
15574 let buffer = self.buffer.read(cx).snapshot(cx);
15575 let mut selected_sibling = false;
15576
15577 let new_selections = old_selections
15578 .iter()
15579 .map(|selection| {
15580 let old_range = selection.start..selection.end;
15581
15582 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15583 let new_range = node.byte_range();
15584 selected_sibling = true;
15585 Selection {
15586 id: selection.id,
15587 start: new_range.start,
15588 end: new_range.end,
15589 goal: SelectionGoal::None,
15590 reversed: selection.reversed,
15591 }
15592 } else {
15593 selection.clone()
15594 }
15595 })
15596 .collect::<Vec<_>>();
15597
15598 if selected_sibling {
15599 self.change_selections(
15600 SelectionEffects::scroll(Autoscroll::fit()),
15601 window,
15602 cx,
15603 |s| {
15604 s.select(new_selections);
15605 },
15606 );
15607 }
15608 }
15609
15610 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15611 if !EditorSettings::get_global(cx).gutter.runnables {
15612 self.clear_tasks();
15613 return Task::ready(());
15614 }
15615 let project = self.project().map(Entity::downgrade);
15616 let task_sources = self.lsp_task_sources(cx);
15617 let multi_buffer = self.buffer.downgrade();
15618 cx.spawn_in(window, async move |editor, cx| {
15619 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15620 let Some(project) = project.and_then(|p| p.upgrade()) else {
15621 return;
15622 };
15623 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15624 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15625 }) else {
15626 return;
15627 };
15628
15629 let hide_runnables = project
15630 .update(cx, |project, _| project.is_via_collab())
15631 .unwrap_or(true);
15632 if hide_runnables {
15633 return;
15634 }
15635 let new_rows =
15636 cx.background_spawn({
15637 let snapshot = display_snapshot.clone();
15638 async move {
15639 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15640 }
15641 })
15642 .await;
15643 let Ok(lsp_tasks) =
15644 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15645 else {
15646 return;
15647 };
15648 let lsp_tasks = lsp_tasks.await;
15649
15650 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15651 lsp_tasks
15652 .into_iter()
15653 .flat_map(|(kind, tasks)| {
15654 tasks.into_iter().filter_map(move |(location, task)| {
15655 Some((kind.clone(), location?, task))
15656 })
15657 })
15658 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15659 let buffer = location.target.buffer;
15660 let buffer_snapshot = buffer.read(cx).snapshot();
15661 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15662 |(excerpt_id, snapshot, _)| {
15663 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15664 display_snapshot
15665 .buffer_snapshot()
15666 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15667 } else {
15668 None
15669 }
15670 },
15671 );
15672 if let Some(offset) = offset {
15673 let task_buffer_range =
15674 location.target.range.to_point(&buffer_snapshot);
15675 let context_buffer_range =
15676 task_buffer_range.to_offset(&buffer_snapshot);
15677 let context_range = BufferOffset(context_buffer_range.start)
15678 ..BufferOffset(context_buffer_range.end);
15679
15680 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15681 .or_insert_with(|| RunnableTasks {
15682 templates: Vec::new(),
15683 offset,
15684 column: task_buffer_range.start.column,
15685 extra_variables: HashMap::default(),
15686 context_range,
15687 })
15688 .templates
15689 .push((kind, task.original_task().clone()));
15690 }
15691
15692 acc
15693 })
15694 }) else {
15695 return;
15696 };
15697
15698 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15699 buffer.language_settings(cx).tasks.prefer_lsp
15700 }) else {
15701 return;
15702 };
15703
15704 let rows = Self::runnable_rows(
15705 project,
15706 display_snapshot,
15707 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15708 new_rows,
15709 cx.clone(),
15710 )
15711 .await;
15712 editor
15713 .update(cx, |editor, _| {
15714 editor.clear_tasks();
15715 for (key, mut value) in rows {
15716 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15717 value.templates.extend(lsp_tasks.templates);
15718 }
15719
15720 editor.insert_tasks(key, value);
15721 }
15722 for (key, value) in lsp_tasks_by_rows {
15723 editor.insert_tasks(key, value);
15724 }
15725 })
15726 .ok();
15727 })
15728 }
15729 fn fetch_runnable_ranges(
15730 snapshot: &DisplaySnapshot,
15731 range: Range<Anchor>,
15732 ) -> Vec<language::RunnableRange> {
15733 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15734 }
15735
15736 fn runnable_rows(
15737 project: Entity<Project>,
15738 snapshot: DisplaySnapshot,
15739 prefer_lsp: bool,
15740 runnable_ranges: Vec<RunnableRange>,
15741 cx: AsyncWindowContext,
15742 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15743 cx.spawn(async move |cx| {
15744 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15745 for mut runnable in runnable_ranges {
15746 let Some(tasks) = cx
15747 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15748 .ok()
15749 else {
15750 continue;
15751 };
15752 let mut tasks = tasks.await;
15753
15754 if prefer_lsp {
15755 tasks.retain(|(task_kind, _)| {
15756 !matches!(task_kind, TaskSourceKind::Language { .. })
15757 });
15758 }
15759 if tasks.is_empty() {
15760 continue;
15761 }
15762
15763 let point = runnable
15764 .run_range
15765 .start
15766 .to_point(&snapshot.buffer_snapshot());
15767 let Some(row) = snapshot
15768 .buffer_snapshot()
15769 .buffer_line_for_row(MultiBufferRow(point.row))
15770 .map(|(_, range)| range.start.row)
15771 else {
15772 continue;
15773 };
15774
15775 let context_range =
15776 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15777 runnable_rows.push((
15778 (runnable.buffer_id, row),
15779 RunnableTasks {
15780 templates: tasks,
15781 offset: snapshot
15782 .buffer_snapshot()
15783 .anchor_before(runnable.run_range.start),
15784 context_range,
15785 column: point.column,
15786 extra_variables: runnable.extra_captures,
15787 },
15788 ));
15789 }
15790 runnable_rows
15791 })
15792 }
15793
15794 fn templates_with_tags(
15795 project: &Entity<Project>,
15796 runnable: &mut Runnable,
15797 cx: &mut App,
15798 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15799 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15800 let (worktree_id, file) = project
15801 .buffer_for_id(runnable.buffer, cx)
15802 .and_then(|buffer| buffer.read(cx).file())
15803 .map(|file| (file.worktree_id(cx), file.clone()))
15804 .unzip();
15805
15806 (
15807 project.task_store().read(cx).task_inventory().cloned(),
15808 worktree_id,
15809 file,
15810 )
15811 });
15812
15813 let tags = mem::take(&mut runnable.tags);
15814 let language = runnable.language.clone();
15815 cx.spawn(async move |cx| {
15816 let mut templates_with_tags = Vec::new();
15817 if let Some(inventory) = inventory {
15818 for RunnableTag(tag) in tags {
15819 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15820 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15821 }) else {
15822 return templates_with_tags;
15823 };
15824 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15825 move |(_, template)| {
15826 template.tags.iter().any(|source_tag| source_tag == &tag)
15827 },
15828 ));
15829 }
15830 }
15831 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15832
15833 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15834 // Strongest source wins; if we have worktree tag binding, prefer that to
15835 // global and language bindings;
15836 // if we have a global binding, prefer that to language binding.
15837 let first_mismatch = templates_with_tags
15838 .iter()
15839 .position(|(tag_source, _)| tag_source != leading_tag_source);
15840 if let Some(index) = first_mismatch {
15841 templates_with_tags.truncate(index);
15842 }
15843 }
15844
15845 templates_with_tags
15846 })
15847 }
15848
15849 pub fn move_to_enclosing_bracket(
15850 &mut self,
15851 _: &MoveToEnclosingBracket,
15852 window: &mut Window,
15853 cx: &mut Context<Self>,
15854 ) {
15855 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15856 self.change_selections(Default::default(), window, cx, |s| {
15857 s.move_offsets_with(|snapshot, selection| {
15858 let Some(enclosing_bracket_ranges) =
15859 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15860 else {
15861 return;
15862 };
15863
15864 let mut best_length = usize::MAX;
15865 let mut best_inside = false;
15866 let mut best_in_bracket_range = false;
15867 let mut best_destination = None;
15868 for (open, close) in enclosing_bracket_ranges {
15869 let close = close.to_inclusive();
15870 let length = close.end() - open.start;
15871 let inside = selection.start >= open.end && selection.end <= *close.start();
15872 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15873 || close.contains(&selection.head());
15874
15875 // If best is next to a bracket and current isn't, skip
15876 if !in_bracket_range && best_in_bracket_range {
15877 continue;
15878 }
15879
15880 // Prefer smaller lengths unless best is inside and current isn't
15881 if length > best_length && (best_inside || !inside) {
15882 continue;
15883 }
15884
15885 best_length = length;
15886 best_inside = inside;
15887 best_in_bracket_range = in_bracket_range;
15888 best_destination = Some(
15889 if close.contains(&selection.start) && close.contains(&selection.end) {
15890 if inside { open.end } else { open.start }
15891 } else if inside {
15892 *close.start()
15893 } else {
15894 *close.end()
15895 },
15896 );
15897 }
15898
15899 if let Some(destination) = best_destination {
15900 selection.collapse_to(destination, SelectionGoal::None);
15901 }
15902 })
15903 });
15904 }
15905
15906 pub fn undo_selection(
15907 &mut self,
15908 _: &UndoSelection,
15909 window: &mut Window,
15910 cx: &mut Context<Self>,
15911 ) {
15912 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15913 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15914 self.selection_history.mode = SelectionHistoryMode::Undoing;
15915 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15916 this.end_selection(window, cx);
15917 this.change_selections(
15918 SelectionEffects::scroll(Autoscroll::newest()),
15919 window,
15920 cx,
15921 |s| s.select_anchors(entry.selections.to_vec()),
15922 );
15923 });
15924 self.selection_history.mode = SelectionHistoryMode::Normal;
15925
15926 self.select_next_state = entry.select_next_state;
15927 self.select_prev_state = entry.select_prev_state;
15928 self.add_selections_state = entry.add_selections_state;
15929 }
15930 }
15931
15932 pub fn redo_selection(
15933 &mut self,
15934 _: &RedoSelection,
15935 window: &mut Window,
15936 cx: &mut Context<Self>,
15937 ) {
15938 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15939 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15940 self.selection_history.mode = SelectionHistoryMode::Redoing;
15941 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15942 this.end_selection(window, cx);
15943 this.change_selections(
15944 SelectionEffects::scroll(Autoscroll::newest()),
15945 window,
15946 cx,
15947 |s| s.select_anchors(entry.selections.to_vec()),
15948 );
15949 });
15950 self.selection_history.mode = SelectionHistoryMode::Normal;
15951
15952 self.select_next_state = entry.select_next_state;
15953 self.select_prev_state = entry.select_prev_state;
15954 self.add_selections_state = entry.add_selections_state;
15955 }
15956 }
15957
15958 pub fn expand_excerpts(
15959 &mut self,
15960 action: &ExpandExcerpts,
15961 _: &mut Window,
15962 cx: &mut Context<Self>,
15963 ) {
15964 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15965 }
15966
15967 pub fn expand_excerpts_down(
15968 &mut self,
15969 action: &ExpandExcerptsDown,
15970 _: &mut Window,
15971 cx: &mut Context<Self>,
15972 ) {
15973 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15974 }
15975
15976 pub fn expand_excerpts_up(
15977 &mut self,
15978 action: &ExpandExcerptsUp,
15979 _: &mut Window,
15980 cx: &mut Context<Self>,
15981 ) {
15982 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15983 }
15984
15985 pub fn expand_excerpts_for_direction(
15986 &mut self,
15987 lines: u32,
15988 direction: ExpandExcerptDirection,
15989
15990 cx: &mut Context<Self>,
15991 ) {
15992 let selections = self.selections.disjoint_anchors_arc();
15993
15994 let lines = if lines == 0 {
15995 EditorSettings::get_global(cx).expand_excerpt_lines
15996 } else {
15997 lines
15998 };
15999
16000 self.buffer.update(cx, |buffer, cx| {
16001 let snapshot = buffer.snapshot(cx);
16002 let mut excerpt_ids = selections
16003 .iter()
16004 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16005 .collect::<Vec<_>>();
16006 excerpt_ids.sort();
16007 excerpt_ids.dedup();
16008 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16009 })
16010 }
16011
16012 pub fn expand_excerpt(
16013 &mut self,
16014 excerpt: ExcerptId,
16015 direction: ExpandExcerptDirection,
16016 window: &mut Window,
16017 cx: &mut Context<Self>,
16018 ) {
16019 let current_scroll_position = self.scroll_position(cx);
16020 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16021 let mut should_scroll_up = false;
16022
16023 if direction == ExpandExcerptDirection::Down {
16024 let multi_buffer = self.buffer.read(cx);
16025 let snapshot = multi_buffer.snapshot(cx);
16026 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16027 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16028 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16029 {
16030 let buffer_snapshot = buffer.read(cx).snapshot();
16031 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16032 let last_row = buffer_snapshot.max_point().row;
16033 let lines_below = last_row.saturating_sub(excerpt_end_row);
16034 should_scroll_up = lines_below >= lines_to_expand;
16035 }
16036 }
16037
16038 self.buffer.update(cx, |buffer, cx| {
16039 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16040 });
16041
16042 if should_scroll_up {
16043 let new_scroll_position =
16044 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as ScrollOffset);
16045 self.set_scroll_position(new_scroll_position, window, cx);
16046 }
16047 }
16048
16049 pub fn go_to_singleton_buffer_point(
16050 &mut self,
16051 point: Point,
16052 window: &mut Window,
16053 cx: &mut Context<Self>,
16054 ) {
16055 self.go_to_singleton_buffer_range(point..point, window, cx);
16056 }
16057
16058 pub fn go_to_singleton_buffer_range(
16059 &mut self,
16060 range: Range<Point>,
16061 window: &mut Window,
16062 cx: &mut Context<Self>,
16063 ) {
16064 let multibuffer = self.buffer().read(cx);
16065 let Some(buffer) = multibuffer.as_singleton() else {
16066 return;
16067 };
16068 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16069 return;
16070 };
16071 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16072 return;
16073 };
16074 self.change_selections(
16075 SelectionEffects::default().nav_history(true),
16076 window,
16077 cx,
16078 |s| s.select_anchor_ranges([start..end]),
16079 );
16080 }
16081
16082 pub fn go_to_diagnostic(
16083 &mut self,
16084 action: &GoToDiagnostic,
16085 window: &mut Window,
16086 cx: &mut Context<Self>,
16087 ) {
16088 if !self.diagnostics_enabled() {
16089 return;
16090 }
16091 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16092 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16093 }
16094
16095 pub fn go_to_prev_diagnostic(
16096 &mut self,
16097 action: &GoToPreviousDiagnostic,
16098 window: &mut Window,
16099 cx: &mut Context<Self>,
16100 ) {
16101 if !self.diagnostics_enabled() {
16102 return;
16103 }
16104 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16105 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16106 }
16107
16108 pub fn go_to_diagnostic_impl(
16109 &mut self,
16110 direction: Direction,
16111 severity: GoToDiagnosticSeverityFilter,
16112 window: &mut Window,
16113 cx: &mut Context<Self>,
16114 ) {
16115 let buffer = self.buffer.read(cx).snapshot(cx);
16116 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16117
16118 let mut active_group_id = None;
16119 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16120 && active_group.active_range.start.to_offset(&buffer) == selection.start
16121 {
16122 active_group_id = Some(active_group.group_id);
16123 }
16124
16125 fn filtered<'a>(
16126 snapshot: EditorSnapshot,
16127 severity: GoToDiagnosticSeverityFilter,
16128 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
16129 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
16130 diagnostics
16131 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16132 .filter(|entry| entry.range.start != entry.range.end)
16133 .filter(|entry| !entry.diagnostic.is_unnecessary)
16134 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
16135 }
16136
16137 let snapshot = self.snapshot(window, cx);
16138 let before = filtered(
16139 snapshot.clone(),
16140 severity,
16141 buffer
16142 .diagnostics_in_range(0..selection.start)
16143 .filter(|entry| entry.range.start <= selection.start),
16144 );
16145 let after = filtered(
16146 snapshot,
16147 severity,
16148 buffer
16149 .diagnostics_in_range(selection.start..buffer.len())
16150 .filter(|entry| entry.range.start >= selection.start),
16151 );
16152
16153 let mut found: Option<DiagnosticEntryRef<usize>> = None;
16154 if direction == Direction::Prev {
16155 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16156 {
16157 for diagnostic in prev_diagnostics.into_iter().rev() {
16158 if diagnostic.range.start != selection.start
16159 || active_group_id
16160 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16161 {
16162 found = Some(diagnostic);
16163 break 'outer;
16164 }
16165 }
16166 }
16167 } else {
16168 for diagnostic in after.chain(before) {
16169 if diagnostic.range.start != selection.start
16170 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16171 {
16172 found = Some(diagnostic);
16173 break;
16174 }
16175 }
16176 }
16177 let Some(next_diagnostic) = found else {
16178 return;
16179 };
16180
16181 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16182 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16183 return;
16184 };
16185 self.change_selections(Default::default(), window, cx, |s| {
16186 s.select_ranges(vec![
16187 next_diagnostic.range.start..next_diagnostic.range.start,
16188 ])
16189 });
16190 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16191 self.refresh_edit_prediction(false, true, window, cx);
16192 }
16193
16194 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16195 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16196 let snapshot = self.snapshot(window, cx);
16197 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16198 self.go_to_hunk_before_or_after_position(
16199 &snapshot,
16200 selection.head(),
16201 Direction::Next,
16202 window,
16203 cx,
16204 );
16205 }
16206
16207 pub fn go_to_hunk_before_or_after_position(
16208 &mut self,
16209 snapshot: &EditorSnapshot,
16210 position: Point,
16211 direction: Direction,
16212 window: &mut Window,
16213 cx: &mut Context<Editor>,
16214 ) {
16215 let row = if direction == Direction::Next {
16216 self.hunk_after_position(snapshot, position)
16217 .map(|hunk| hunk.row_range.start)
16218 } else {
16219 self.hunk_before_position(snapshot, position)
16220 };
16221
16222 if let Some(row) = row {
16223 let destination = Point::new(row.0, 0);
16224 let autoscroll = Autoscroll::center();
16225
16226 self.unfold_ranges(&[destination..destination], false, false, cx);
16227 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16228 s.select_ranges([destination..destination]);
16229 });
16230 }
16231 }
16232
16233 fn hunk_after_position(
16234 &mut self,
16235 snapshot: &EditorSnapshot,
16236 position: Point,
16237 ) -> Option<MultiBufferDiffHunk> {
16238 snapshot
16239 .buffer_snapshot()
16240 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16241 .find(|hunk| hunk.row_range.start.0 > position.row)
16242 .or_else(|| {
16243 snapshot
16244 .buffer_snapshot()
16245 .diff_hunks_in_range(Point::zero()..position)
16246 .find(|hunk| hunk.row_range.end.0 < position.row)
16247 })
16248 }
16249
16250 fn go_to_prev_hunk(
16251 &mut self,
16252 _: &GoToPreviousHunk,
16253 window: &mut Window,
16254 cx: &mut Context<Self>,
16255 ) {
16256 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16257 let snapshot = self.snapshot(window, cx);
16258 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16259 self.go_to_hunk_before_or_after_position(
16260 &snapshot,
16261 selection.head(),
16262 Direction::Prev,
16263 window,
16264 cx,
16265 );
16266 }
16267
16268 fn hunk_before_position(
16269 &mut self,
16270 snapshot: &EditorSnapshot,
16271 position: Point,
16272 ) -> Option<MultiBufferRow> {
16273 snapshot
16274 .buffer_snapshot()
16275 .diff_hunk_before(position)
16276 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16277 }
16278
16279 fn go_to_next_change(
16280 &mut self,
16281 _: &GoToNextChange,
16282 window: &mut Window,
16283 cx: &mut Context<Self>,
16284 ) {
16285 if let Some(selections) = self
16286 .change_list
16287 .next_change(1, Direction::Next)
16288 .map(|s| s.to_vec())
16289 {
16290 self.change_selections(Default::default(), window, cx, |s| {
16291 let map = s.display_map();
16292 s.select_display_ranges(selections.iter().map(|a| {
16293 let point = a.to_display_point(&map);
16294 point..point
16295 }))
16296 })
16297 }
16298 }
16299
16300 fn go_to_previous_change(
16301 &mut self,
16302 _: &GoToPreviousChange,
16303 window: &mut Window,
16304 cx: &mut Context<Self>,
16305 ) {
16306 if let Some(selections) = self
16307 .change_list
16308 .next_change(1, Direction::Prev)
16309 .map(|s| s.to_vec())
16310 {
16311 self.change_selections(Default::default(), window, cx, |s| {
16312 let map = s.display_map();
16313 s.select_display_ranges(selections.iter().map(|a| {
16314 let point = a.to_display_point(&map);
16315 point..point
16316 }))
16317 })
16318 }
16319 }
16320
16321 pub fn go_to_next_document_highlight(
16322 &mut self,
16323 _: &GoToNextDocumentHighlight,
16324 window: &mut Window,
16325 cx: &mut Context<Self>,
16326 ) {
16327 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16328 }
16329
16330 pub fn go_to_prev_document_highlight(
16331 &mut self,
16332 _: &GoToPreviousDocumentHighlight,
16333 window: &mut Window,
16334 cx: &mut Context<Self>,
16335 ) {
16336 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16337 }
16338
16339 pub fn go_to_document_highlight_before_or_after_position(
16340 &mut self,
16341 direction: Direction,
16342 window: &mut Window,
16343 cx: &mut Context<Editor>,
16344 ) {
16345 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16346 let snapshot = self.snapshot(window, cx);
16347 let buffer = &snapshot.buffer_snapshot();
16348 let position = self
16349 .selections
16350 .newest::<Point>(&snapshot.display_snapshot)
16351 .head();
16352 let anchor_position = buffer.anchor_after(position);
16353
16354 // Get all document highlights (both read and write)
16355 let mut all_highlights = Vec::new();
16356
16357 if let Some((_, read_highlights)) = self
16358 .background_highlights
16359 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16360 {
16361 all_highlights.extend(read_highlights.iter());
16362 }
16363
16364 if let Some((_, write_highlights)) = self
16365 .background_highlights
16366 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16367 {
16368 all_highlights.extend(write_highlights.iter());
16369 }
16370
16371 if all_highlights.is_empty() {
16372 return;
16373 }
16374
16375 // Sort highlights by position
16376 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16377
16378 let target_highlight = match direction {
16379 Direction::Next => {
16380 // Find the first highlight after the current position
16381 all_highlights
16382 .iter()
16383 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16384 }
16385 Direction::Prev => {
16386 // Find the last highlight before the current position
16387 all_highlights
16388 .iter()
16389 .rev()
16390 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16391 }
16392 };
16393
16394 if let Some(highlight) = target_highlight {
16395 let destination = highlight.start.to_point(buffer);
16396 let autoscroll = Autoscroll::center();
16397
16398 self.unfold_ranges(&[destination..destination], false, false, cx);
16399 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16400 s.select_ranges([destination..destination]);
16401 });
16402 }
16403 }
16404
16405 fn go_to_line<T: 'static>(
16406 &mut self,
16407 position: Anchor,
16408 highlight_color: Option<Hsla>,
16409 window: &mut Window,
16410 cx: &mut Context<Self>,
16411 ) {
16412 let snapshot = self.snapshot(window, cx).display_snapshot;
16413 let position = position.to_point(&snapshot.buffer_snapshot());
16414 let start = snapshot
16415 .buffer_snapshot()
16416 .clip_point(Point::new(position.row, 0), Bias::Left);
16417 let end = start + Point::new(1, 0);
16418 let start = snapshot.buffer_snapshot().anchor_before(start);
16419 let end = snapshot.buffer_snapshot().anchor_before(end);
16420
16421 self.highlight_rows::<T>(
16422 start..end,
16423 highlight_color
16424 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16425 Default::default(),
16426 cx,
16427 );
16428
16429 if self.buffer.read(cx).is_singleton() {
16430 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16431 }
16432 }
16433
16434 pub fn go_to_definition(
16435 &mut self,
16436 _: &GoToDefinition,
16437 window: &mut Window,
16438 cx: &mut Context<Self>,
16439 ) -> Task<Result<Navigated>> {
16440 let definition =
16441 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16442 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16443 cx.spawn_in(window, async move |editor, cx| {
16444 if definition.await? == Navigated::Yes {
16445 return Ok(Navigated::Yes);
16446 }
16447 match fallback_strategy {
16448 GoToDefinitionFallback::None => Ok(Navigated::No),
16449 GoToDefinitionFallback::FindAllReferences => {
16450 match editor.update_in(cx, |editor, window, cx| {
16451 editor.find_all_references(&FindAllReferences, window, cx)
16452 })? {
16453 Some(references) => references.await,
16454 None => Ok(Navigated::No),
16455 }
16456 }
16457 }
16458 })
16459 }
16460
16461 pub fn go_to_declaration(
16462 &mut self,
16463 _: &GoToDeclaration,
16464 window: &mut Window,
16465 cx: &mut Context<Self>,
16466 ) -> Task<Result<Navigated>> {
16467 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16468 }
16469
16470 pub fn go_to_declaration_split(
16471 &mut self,
16472 _: &GoToDeclaration,
16473 window: &mut Window,
16474 cx: &mut Context<Self>,
16475 ) -> Task<Result<Navigated>> {
16476 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16477 }
16478
16479 pub fn go_to_implementation(
16480 &mut self,
16481 _: &GoToImplementation,
16482 window: &mut Window,
16483 cx: &mut Context<Self>,
16484 ) -> Task<Result<Navigated>> {
16485 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16486 }
16487
16488 pub fn go_to_implementation_split(
16489 &mut self,
16490 _: &GoToImplementationSplit,
16491 window: &mut Window,
16492 cx: &mut Context<Self>,
16493 ) -> Task<Result<Navigated>> {
16494 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16495 }
16496
16497 pub fn go_to_type_definition(
16498 &mut self,
16499 _: &GoToTypeDefinition,
16500 window: &mut Window,
16501 cx: &mut Context<Self>,
16502 ) -> Task<Result<Navigated>> {
16503 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16504 }
16505
16506 pub fn go_to_definition_split(
16507 &mut self,
16508 _: &GoToDefinitionSplit,
16509 window: &mut Window,
16510 cx: &mut Context<Self>,
16511 ) -> Task<Result<Navigated>> {
16512 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16513 }
16514
16515 pub fn go_to_type_definition_split(
16516 &mut self,
16517 _: &GoToTypeDefinitionSplit,
16518 window: &mut Window,
16519 cx: &mut Context<Self>,
16520 ) -> Task<Result<Navigated>> {
16521 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16522 }
16523
16524 fn go_to_definition_of_kind(
16525 &mut self,
16526 kind: GotoDefinitionKind,
16527 split: bool,
16528 window: &mut Window,
16529 cx: &mut Context<Self>,
16530 ) -> Task<Result<Navigated>> {
16531 let Some(provider) = self.semantics_provider.clone() else {
16532 return Task::ready(Ok(Navigated::No));
16533 };
16534 let head = self
16535 .selections
16536 .newest::<usize>(&self.display_snapshot(cx))
16537 .head();
16538 let buffer = self.buffer.read(cx);
16539 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16540 return Task::ready(Ok(Navigated::No));
16541 };
16542 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16543 return Task::ready(Ok(Navigated::No));
16544 };
16545
16546 cx.spawn_in(window, async move |editor, cx| {
16547 let Some(definitions) = definitions.await? else {
16548 return Ok(Navigated::No);
16549 };
16550 let navigated = editor
16551 .update_in(cx, |editor, window, cx| {
16552 editor.navigate_to_hover_links(
16553 Some(kind),
16554 definitions
16555 .into_iter()
16556 .filter(|location| {
16557 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16558 })
16559 .map(HoverLink::Text)
16560 .collect::<Vec<_>>(),
16561 split,
16562 window,
16563 cx,
16564 )
16565 })?
16566 .await?;
16567 anyhow::Ok(navigated)
16568 })
16569 }
16570
16571 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16572 let selection = self.selections.newest_anchor();
16573 let head = selection.head();
16574 let tail = selection.tail();
16575
16576 let Some((buffer, start_position)) =
16577 self.buffer.read(cx).text_anchor_for_position(head, cx)
16578 else {
16579 return;
16580 };
16581
16582 let end_position = if head != tail {
16583 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16584 return;
16585 };
16586 Some(pos)
16587 } else {
16588 None
16589 };
16590
16591 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16592 let url = if let Some(end_pos) = end_position {
16593 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16594 } else {
16595 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16596 };
16597
16598 if let Some(url) = url {
16599 cx.update(|window, cx| {
16600 if parse_zed_link(&url, cx).is_some() {
16601 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16602 } else {
16603 cx.open_url(&url);
16604 }
16605 })?;
16606 }
16607
16608 anyhow::Ok(())
16609 });
16610
16611 url_finder.detach();
16612 }
16613
16614 pub fn open_selected_filename(
16615 &mut self,
16616 _: &OpenSelectedFilename,
16617 window: &mut Window,
16618 cx: &mut Context<Self>,
16619 ) {
16620 let Some(workspace) = self.workspace() else {
16621 return;
16622 };
16623
16624 let position = self.selections.newest_anchor().head();
16625
16626 let Some((buffer, buffer_position)) =
16627 self.buffer.read(cx).text_anchor_for_position(position, cx)
16628 else {
16629 return;
16630 };
16631
16632 let project = self.project.clone();
16633
16634 cx.spawn_in(window, async move |_, cx| {
16635 let result = find_file(&buffer, project, buffer_position, cx).await;
16636
16637 if let Some((_, path)) = result {
16638 workspace
16639 .update_in(cx, |workspace, window, cx| {
16640 workspace.open_resolved_path(path, window, cx)
16641 })?
16642 .await?;
16643 }
16644 anyhow::Ok(())
16645 })
16646 .detach();
16647 }
16648
16649 pub(crate) fn navigate_to_hover_links(
16650 &mut self,
16651 kind: Option<GotoDefinitionKind>,
16652 definitions: Vec<HoverLink>,
16653 split: bool,
16654 window: &mut Window,
16655 cx: &mut Context<Editor>,
16656 ) -> Task<Result<Navigated>> {
16657 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16658 let mut first_url_or_file = None;
16659 let definitions: Vec<_> = definitions
16660 .into_iter()
16661 .filter_map(|def| match def {
16662 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16663 HoverLink::InlayHint(lsp_location, server_id) => {
16664 let computation =
16665 self.compute_target_location(lsp_location, server_id, window, cx);
16666 Some(cx.background_spawn(computation))
16667 }
16668 HoverLink::Url(url) => {
16669 first_url_or_file = Some(Either::Left(url));
16670 None
16671 }
16672 HoverLink::File(path) => {
16673 first_url_or_file = Some(Either::Right(path));
16674 None
16675 }
16676 })
16677 .collect();
16678
16679 let workspace = self.workspace();
16680
16681 cx.spawn_in(window, async move |editor, cx| {
16682 let locations: Vec<Location> = future::join_all(definitions)
16683 .await
16684 .into_iter()
16685 .filter_map(|location| location.transpose())
16686 .collect::<Result<_>>()
16687 .context("location tasks")?;
16688 let mut locations = cx.update(|_, cx| {
16689 locations
16690 .into_iter()
16691 .map(|location| {
16692 let buffer = location.buffer.read(cx);
16693 (location.buffer, location.range.to_point(buffer))
16694 })
16695 .into_group_map()
16696 })?;
16697 let mut num_locations = 0;
16698 for ranges in locations.values_mut() {
16699 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16700 ranges.dedup();
16701 num_locations += ranges.len();
16702 }
16703
16704 if num_locations > 1 {
16705 let Some(workspace) = workspace else {
16706 return Ok(Navigated::No);
16707 };
16708
16709 let tab_kind = match kind {
16710 Some(GotoDefinitionKind::Implementation) => "Implementations",
16711 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16712 Some(GotoDefinitionKind::Declaration) => "Declarations",
16713 Some(GotoDefinitionKind::Type) => "Types",
16714 };
16715 let title = editor
16716 .update_in(cx, |_, _, cx| {
16717 let target = locations
16718 .iter()
16719 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16720 .map(|(buffer, location)| {
16721 buffer
16722 .read(cx)
16723 .text_for_range(location.clone())
16724 .collect::<String>()
16725 })
16726 .filter(|text| !text.contains('\n'))
16727 .unique()
16728 .take(3)
16729 .join(", ");
16730 if target.is_empty() {
16731 tab_kind.to_owned()
16732 } else {
16733 format!("{tab_kind} for {target}")
16734 }
16735 })
16736 .context("buffer title")?;
16737
16738 let opened = workspace
16739 .update_in(cx, |workspace, window, cx| {
16740 Self::open_locations_in_multibuffer(
16741 workspace,
16742 locations,
16743 title,
16744 split,
16745 MultibufferSelectionMode::First,
16746 window,
16747 cx,
16748 )
16749 })
16750 .is_ok();
16751
16752 anyhow::Ok(Navigated::from_bool(opened))
16753 } else if num_locations == 0 {
16754 // If there is one url or file, open it directly
16755 match first_url_or_file {
16756 Some(Either::Left(url)) => {
16757 cx.update(|_, cx| cx.open_url(&url))?;
16758 Ok(Navigated::Yes)
16759 }
16760 Some(Either::Right(path)) => {
16761 let Some(workspace) = workspace else {
16762 return Ok(Navigated::No);
16763 };
16764
16765 workspace
16766 .update_in(cx, |workspace, window, cx| {
16767 workspace.open_resolved_path(path, window, cx)
16768 })?
16769 .await?;
16770 Ok(Navigated::Yes)
16771 }
16772 None => Ok(Navigated::No),
16773 }
16774 } else {
16775 let Some(workspace) = workspace else {
16776 return Ok(Navigated::No);
16777 };
16778
16779 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16780 let target_range = target_ranges.first().unwrap().clone();
16781
16782 editor.update_in(cx, |editor, window, cx| {
16783 let range = target_range.to_point(target_buffer.read(cx));
16784 let range = editor.range_for_match(&range);
16785 let range = collapse_multiline_range(range);
16786
16787 if !split
16788 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16789 {
16790 editor.go_to_singleton_buffer_range(range, window, cx);
16791 } else {
16792 let pane = workspace.read(cx).active_pane().clone();
16793 window.defer(cx, move |window, cx| {
16794 let target_editor: Entity<Self> =
16795 workspace.update(cx, |workspace, cx| {
16796 let pane = if split {
16797 workspace.adjacent_pane(window, cx)
16798 } else {
16799 workspace.active_pane().clone()
16800 };
16801
16802 workspace.open_project_item(
16803 pane,
16804 target_buffer.clone(),
16805 true,
16806 true,
16807 window,
16808 cx,
16809 )
16810 });
16811 target_editor.update(cx, |target_editor, cx| {
16812 // When selecting a definition in a different buffer, disable the nav history
16813 // to avoid creating a history entry at the previous cursor location.
16814 pane.update(cx, |pane, _| pane.disable_history());
16815 target_editor.go_to_singleton_buffer_range(range, window, cx);
16816 pane.update(cx, |pane, _| pane.enable_history());
16817 });
16818 });
16819 }
16820 Navigated::Yes
16821 })
16822 }
16823 })
16824 }
16825
16826 fn compute_target_location(
16827 &self,
16828 lsp_location: lsp::Location,
16829 server_id: LanguageServerId,
16830 window: &mut Window,
16831 cx: &mut Context<Self>,
16832 ) -> Task<anyhow::Result<Option<Location>>> {
16833 let Some(project) = self.project.clone() else {
16834 return Task::ready(Ok(None));
16835 };
16836
16837 cx.spawn_in(window, async move |editor, cx| {
16838 let location_task = editor.update(cx, |_, cx| {
16839 project.update(cx, |project, cx| {
16840 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16841 })
16842 })?;
16843 let location = Some({
16844 let target_buffer_handle = location_task.await.context("open local buffer")?;
16845 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16846 let target_start = target_buffer
16847 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16848 let target_end = target_buffer
16849 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16850 target_buffer.anchor_after(target_start)
16851 ..target_buffer.anchor_before(target_end)
16852 })?;
16853 Location {
16854 buffer: target_buffer_handle,
16855 range,
16856 }
16857 });
16858 Ok(location)
16859 })
16860 }
16861
16862 pub fn find_all_references(
16863 &mut self,
16864 _: &FindAllReferences,
16865 window: &mut Window,
16866 cx: &mut Context<Self>,
16867 ) -> Option<Task<Result<Navigated>>> {
16868 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16869 let multi_buffer = self.buffer.read(cx);
16870 let head = selection.head();
16871
16872 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16873 let head_anchor = multi_buffer_snapshot.anchor_at(
16874 head,
16875 if head < selection.tail() {
16876 Bias::Right
16877 } else {
16878 Bias::Left
16879 },
16880 );
16881
16882 match self
16883 .find_all_references_task_sources
16884 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16885 {
16886 Ok(_) => {
16887 log::info!(
16888 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16889 );
16890 return None;
16891 }
16892 Err(i) => {
16893 self.find_all_references_task_sources.insert(i, head_anchor);
16894 }
16895 }
16896
16897 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16898 let workspace = self.workspace()?;
16899 let project = workspace.read(cx).project().clone();
16900 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16901 Some(cx.spawn_in(window, async move |editor, cx| {
16902 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16903 if let Ok(i) = editor
16904 .find_all_references_task_sources
16905 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16906 {
16907 editor.find_all_references_task_sources.remove(i);
16908 }
16909 });
16910
16911 let Some(locations) = references.await? else {
16912 return anyhow::Ok(Navigated::No);
16913 };
16914 let mut locations = cx.update(|_, cx| {
16915 locations
16916 .into_iter()
16917 .map(|location| {
16918 let buffer = location.buffer.read(cx);
16919 (location.buffer, location.range.to_point(buffer))
16920 })
16921 .into_group_map()
16922 })?;
16923 if locations.is_empty() {
16924 return anyhow::Ok(Navigated::No);
16925 }
16926 for ranges in locations.values_mut() {
16927 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16928 ranges.dedup();
16929 }
16930
16931 workspace.update_in(cx, |workspace, window, cx| {
16932 let target = locations
16933 .iter()
16934 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16935 .map(|(buffer, location)| {
16936 buffer
16937 .read(cx)
16938 .text_for_range(location.clone())
16939 .collect::<String>()
16940 })
16941 .filter(|text| !text.contains('\n'))
16942 .unique()
16943 .take(3)
16944 .join(", ");
16945 let title = if target.is_empty() {
16946 "References".to_owned()
16947 } else {
16948 format!("References to {target}")
16949 };
16950 Self::open_locations_in_multibuffer(
16951 workspace,
16952 locations,
16953 title,
16954 false,
16955 MultibufferSelectionMode::First,
16956 window,
16957 cx,
16958 );
16959 Navigated::Yes
16960 })
16961 }))
16962 }
16963
16964 /// Opens a multibuffer with the given project locations in it
16965 pub fn open_locations_in_multibuffer(
16966 workspace: &mut Workspace,
16967 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
16968 title: String,
16969 split: bool,
16970 multibuffer_selection_mode: MultibufferSelectionMode,
16971 window: &mut Window,
16972 cx: &mut Context<Workspace>,
16973 ) {
16974 if locations.is_empty() {
16975 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16976 return;
16977 }
16978
16979 let capability = workspace.project().read(cx).capability();
16980 let mut ranges = <Vec<Range<Anchor>>>::new();
16981
16982 // a key to find existing multibuffer editors with the same set of locations
16983 // to prevent us from opening more and more multibuffer tabs for searches and the like
16984 let mut key = (title.clone(), vec![]);
16985 let excerpt_buffer = cx.new(|cx| {
16986 let key = &mut key.1;
16987 let mut multibuffer = MultiBuffer::new(capability);
16988 for (buffer, mut ranges_for_buffer) in locations {
16989 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16990 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
16991 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16992 PathKey::for_buffer(&buffer, cx),
16993 buffer.clone(),
16994 ranges_for_buffer,
16995 multibuffer_context_lines(cx),
16996 cx,
16997 );
16998 ranges.extend(new_ranges)
16999 }
17000
17001 multibuffer.with_title(title)
17002 });
17003 let existing = workspace.active_pane().update(cx, |pane, cx| {
17004 pane.items()
17005 .filter_map(|item| item.downcast::<Editor>())
17006 .find(|editor| {
17007 editor
17008 .read(cx)
17009 .lookup_key
17010 .as_ref()
17011 .and_then(|it| {
17012 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17013 })
17014 .is_some_and(|it| *it == key)
17015 })
17016 });
17017 let editor = existing.unwrap_or_else(|| {
17018 cx.new(|cx| {
17019 let mut editor = Editor::for_multibuffer(
17020 excerpt_buffer,
17021 Some(workspace.project().clone()),
17022 window,
17023 cx,
17024 );
17025 editor.lookup_key = Some(Box::new(key));
17026 editor
17027 })
17028 });
17029 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17030 MultibufferSelectionMode::First => {
17031 if let Some(first_range) = ranges.first() {
17032 editor.change_selections(
17033 SelectionEffects::no_scroll(),
17034 window,
17035 cx,
17036 |selections| {
17037 selections.clear_disjoint();
17038 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17039 },
17040 );
17041 }
17042 editor.highlight_background::<Self>(
17043 &ranges,
17044 |theme| theme.colors().editor_highlighted_line_background,
17045 cx,
17046 );
17047 }
17048 MultibufferSelectionMode::All => {
17049 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17050 selections.clear_disjoint();
17051 selections.select_anchor_ranges(ranges);
17052 });
17053 }
17054 });
17055
17056 let item = Box::new(editor);
17057 let item_id = item.item_id();
17058
17059 if split {
17060 let pane = workspace.adjacent_pane(window, cx);
17061 workspace.add_item(pane, item, None, true, true, window, cx);
17062 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17063 let (preview_item_id, preview_item_idx) =
17064 workspace.active_pane().read_with(cx, |pane, _| {
17065 (pane.preview_item_id(), pane.preview_item_idx())
17066 });
17067
17068 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17069
17070 if let Some(preview_item_id) = preview_item_id {
17071 workspace.active_pane().update(cx, |pane, cx| {
17072 pane.remove_item(preview_item_id, false, false, window, cx);
17073 });
17074 }
17075 } else {
17076 workspace.add_item_to_active_pane(item, None, true, window, cx);
17077 }
17078 workspace.active_pane().update(cx, |pane, cx| {
17079 pane.set_preview_item_id(Some(item_id), cx);
17080 });
17081 }
17082
17083 pub fn rename(
17084 &mut self,
17085 _: &Rename,
17086 window: &mut Window,
17087 cx: &mut Context<Self>,
17088 ) -> Option<Task<Result<()>>> {
17089 use language::ToOffset as _;
17090
17091 let provider = self.semantics_provider.clone()?;
17092 let selection = self.selections.newest_anchor().clone();
17093 let (cursor_buffer, cursor_buffer_position) = self
17094 .buffer
17095 .read(cx)
17096 .text_anchor_for_position(selection.head(), cx)?;
17097 let (tail_buffer, cursor_buffer_position_end) = self
17098 .buffer
17099 .read(cx)
17100 .text_anchor_for_position(selection.tail(), cx)?;
17101 if tail_buffer != cursor_buffer {
17102 return None;
17103 }
17104
17105 let snapshot = cursor_buffer.read(cx).snapshot();
17106 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17107 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17108 let prepare_rename = provider
17109 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17110 .unwrap_or_else(|| Task::ready(Ok(None)));
17111 drop(snapshot);
17112
17113 Some(cx.spawn_in(window, async move |this, cx| {
17114 let rename_range = if let Some(range) = prepare_rename.await? {
17115 Some(range)
17116 } else {
17117 this.update(cx, |this, cx| {
17118 let buffer = this.buffer.read(cx).snapshot(cx);
17119 let mut buffer_highlights = this
17120 .document_highlights_for_position(selection.head(), &buffer)
17121 .filter(|highlight| {
17122 highlight.start.excerpt_id == selection.head().excerpt_id
17123 && highlight.end.excerpt_id == selection.head().excerpt_id
17124 });
17125 buffer_highlights
17126 .next()
17127 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17128 })?
17129 };
17130 if let Some(rename_range) = rename_range {
17131 this.update_in(cx, |this, window, cx| {
17132 let snapshot = cursor_buffer.read(cx).snapshot();
17133 let rename_buffer_range = rename_range.to_offset(&snapshot);
17134 let cursor_offset_in_rename_range =
17135 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17136 let cursor_offset_in_rename_range_end =
17137 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17138
17139 this.take_rename(false, window, cx);
17140 let buffer = this.buffer.read(cx).read(cx);
17141 let cursor_offset = selection.head().to_offset(&buffer);
17142 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17143 let rename_end = rename_start + rename_buffer_range.len();
17144 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17145 let mut old_highlight_id = None;
17146 let old_name: Arc<str> = buffer
17147 .chunks(rename_start..rename_end, true)
17148 .map(|chunk| {
17149 if old_highlight_id.is_none() {
17150 old_highlight_id = chunk.syntax_highlight_id;
17151 }
17152 chunk.text
17153 })
17154 .collect::<String>()
17155 .into();
17156
17157 drop(buffer);
17158
17159 // Position the selection in the rename editor so that it matches the current selection.
17160 this.show_local_selections = false;
17161 let rename_editor = cx.new(|cx| {
17162 let mut editor = Editor::single_line(window, cx);
17163 editor.buffer.update(cx, |buffer, cx| {
17164 buffer.edit([(0..0, old_name.clone())], None, cx)
17165 });
17166 let rename_selection_range = match cursor_offset_in_rename_range
17167 .cmp(&cursor_offset_in_rename_range_end)
17168 {
17169 Ordering::Equal => {
17170 editor.select_all(&SelectAll, window, cx);
17171 return editor;
17172 }
17173 Ordering::Less => {
17174 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17175 }
17176 Ordering::Greater => {
17177 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17178 }
17179 };
17180 if rename_selection_range.end > old_name.len() {
17181 editor.select_all(&SelectAll, window, cx);
17182 } else {
17183 editor.change_selections(Default::default(), window, cx, |s| {
17184 s.select_ranges([rename_selection_range]);
17185 });
17186 }
17187 editor
17188 });
17189 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17190 if e == &EditorEvent::Focused {
17191 cx.emit(EditorEvent::FocusedIn)
17192 }
17193 })
17194 .detach();
17195
17196 let write_highlights =
17197 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17198 let read_highlights =
17199 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17200 let ranges = write_highlights
17201 .iter()
17202 .flat_map(|(_, ranges)| ranges.iter())
17203 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17204 .cloned()
17205 .collect();
17206
17207 this.highlight_text::<Rename>(
17208 ranges,
17209 HighlightStyle {
17210 fade_out: Some(0.6),
17211 ..Default::default()
17212 },
17213 cx,
17214 );
17215 let rename_focus_handle = rename_editor.focus_handle(cx);
17216 window.focus(&rename_focus_handle);
17217 let block_id = this.insert_blocks(
17218 [BlockProperties {
17219 style: BlockStyle::Flex,
17220 placement: BlockPlacement::Below(range.start),
17221 height: Some(1),
17222 render: Arc::new({
17223 let rename_editor = rename_editor.clone();
17224 move |cx: &mut BlockContext| {
17225 let mut text_style = cx.editor_style.text.clone();
17226 if let Some(highlight_style) = old_highlight_id
17227 .and_then(|h| h.style(&cx.editor_style.syntax))
17228 {
17229 text_style = text_style.highlight(highlight_style);
17230 }
17231 div()
17232 .block_mouse_except_scroll()
17233 .pl(cx.anchor_x)
17234 .child(EditorElement::new(
17235 &rename_editor,
17236 EditorStyle {
17237 background: cx.theme().system().transparent,
17238 local_player: cx.editor_style.local_player,
17239 text: text_style,
17240 scrollbar_width: cx.editor_style.scrollbar_width,
17241 syntax: cx.editor_style.syntax.clone(),
17242 status: cx.editor_style.status.clone(),
17243 inlay_hints_style: HighlightStyle {
17244 font_weight: Some(FontWeight::BOLD),
17245 ..make_inlay_hints_style(cx.app)
17246 },
17247 edit_prediction_styles: make_suggestion_styles(
17248 cx.app,
17249 ),
17250 ..EditorStyle::default()
17251 },
17252 ))
17253 .into_any_element()
17254 }
17255 }),
17256 priority: 0,
17257 }],
17258 Some(Autoscroll::fit()),
17259 cx,
17260 )[0];
17261 this.pending_rename = Some(RenameState {
17262 range,
17263 old_name,
17264 editor: rename_editor,
17265 block_id,
17266 });
17267 })?;
17268 }
17269
17270 Ok(())
17271 }))
17272 }
17273
17274 pub fn confirm_rename(
17275 &mut self,
17276 _: &ConfirmRename,
17277 window: &mut Window,
17278 cx: &mut Context<Self>,
17279 ) -> Option<Task<Result<()>>> {
17280 let rename = self.take_rename(false, window, cx)?;
17281 let workspace = self.workspace()?.downgrade();
17282 let (buffer, start) = self
17283 .buffer
17284 .read(cx)
17285 .text_anchor_for_position(rename.range.start, cx)?;
17286 let (end_buffer, _) = self
17287 .buffer
17288 .read(cx)
17289 .text_anchor_for_position(rename.range.end, cx)?;
17290 if buffer != end_buffer {
17291 return None;
17292 }
17293
17294 let old_name = rename.old_name;
17295 let new_name = rename.editor.read(cx).text(cx);
17296
17297 let rename = self.semantics_provider.as_ref()?.perform_rename(
17298 &buffer,
17299 start,
17300 new_name.clone(),
17301 cx,
17302 )?;
17303
17304 Some(cx.spawn_in(window, async move |editor, cx| {
17305 let project_transaction = rename.await?;
17306 Self::open_project_transaction(
17307 &editor,
17308 workspace,
17309 project_transaction,
17310 format!("Rename: {} → {}", old_name, new_name),
17311 cx,
17312 )
17313 .await?;
17314
17315 editor.update(cx, |editor, cx| {
17316 editor.refresh_document_highlights(cx);
17317 })?;
17318 Ok(())
17319 }))
17320 }
17321
17322 fn take_rename(
17323 &mut self,
17324 moving_cursor: bool,
17325 window: &mut Window,
17326 cx: &mut Context<Self>,
17327 ) -> Option<RenameState> {
17328 let rename = self.pending_rename.take()?;
17329 if rename.editor.focus_handle(cx).is_focused(window) {
17330 window.focus(&self.focus_handle);
17331 }
17332
17333 self.remove_blocks(
17334 [rename.block_id].into_iter().collect(),
17335 Some(Autoscroll::fit()),
17336 cx,
17337 );
17338 self.clear_highlights::<Rename>(cx);
17339 self.show_local_selections = true;
17340
17341 if moving_cursor {
17342 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17343 editor
17344 .selections
17345 .newest::<usize>(&editor.display_snapshot(cx))
17346 .head()
17347 });
17348
17349 // Update the selection to match the position of the selection inside
17350 // the rename editor.
17351 let snapshot = self.buffer.read(cx).read(cx);
17352 let rename_range = rename.range.to_offset(&snapshot);
17353 let cursor_in_editor = snapshot
17354 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17355 .min(rename_range.end);
17356 drop(snapshot);
17357
17358 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17359 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17360 });
17361 } else {
17362 self.refresh_document_highlights(cx);
17363 }
17364
17365 Some(rename)
17366 }
17367
17368 pub fn pending_rename(&self) -> Option<&RenameState> {
17369 self.pending_rename.as_ref()
17370 }
17371
17372 fn format(
17373 &mut self,
17374 _: &Format,
17375 window: &mut Window,
17376 cx: &mut Context<Self>,
17377 ) -> Option<Task<Result<()>>> {
17378 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17379
17380 let project = match &self.project {
17381 Some(project) => project.clone(),
17382 None => return None,
17383 };
17384
17385 Some(self.perform_format(
17386 project,
17387 FormatTrigger::Manual,
17388 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17389 window,
17390 cx,
17391 ))
17392 }
17393
17394 fn format_selections(
17395 &mut self,
17396 _: &FormatSelections,
17397 window: &mut Window,
17398 cx: &mut Context<Self>,
17399 ) -> Option<Task<Result<()>>> {
17400 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17401
17402 let project = match &self.project {
17403 Some(project) => project.clone(),
17404 None => return None,
17405 };
17406
17407 let ranges = self
17408 .selections
17409 .all_adjusted(&self.display_snapshot(cx))
17410 .into_iter()
17411 .map(|selection| selection.range())
17412 .collect_vec();
17413
17414 Some(self.perform_format(
17415 project,
17416 FormatTrigger::Manual,
17417 FormatTarget::Ranges(ranges),
17418 window,
17419 cx,
17420 ))
17421 }
17422
17423 fn perform_format(
17424 &mut self,
17425 project: Entity<Project>,
17426 trigger: FormatTrigger,
17427 target: FormatTarget,
17428 window: &mut Window,
17429 cx: &mut Context<Self>,
17430 ) -> Task<Result<()>> {
17431 let buffer = self.buffer.clone();
17432 let (buffers, target) = match target {
17433 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17434 FormatTarget::Ranges(selection_ranges) => {
17435 let multi_buffer = buffer.read(cx);
17436 let snapshot = multi_buffer.read(cx);
17437 let mut buffers = HashSet::default();
17438 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17439 BTreeMap::new();
17440 for selection_range in selection_ranges {
17441 for (buffer, buffer_range, _) in
17442 snapshot.range_to_buffer_ranges(selection_range)
17443 {
17444 let buffer_id = buffer.remote_id();
17445 let start = buffer.anchor_before(buffer_range.start);
17446 let end = buffer.anchor_after(buffer_range.end);
17447 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17448 buffer_id_to_ranges
17449 .entry(buffer_id)
17450 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17451 .or_insert_with(|| vec![start..end]);
17452 }
17453 }
17454 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17455 }
17456 };
17457
17458 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17459 let selections_prev = transaction_id_prev
17460 .and_then(|transaction_id_prev| {
17461 // default to selections as they were after the last edit, if we have them,
17462 // instead of how they are now.
17463 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17464 // will take you back to where you made the last edit, instead of staying where you scrolled
17465 self.selection_history
17466 .transaction(transaction_id_prev)
17467 .map(|t| t.0.clone())
17468 })
17469 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17470
17471 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17472 let format = project.update(cx, |project, cx| {
17473 project.format(buffers, target, true, trigger, cx)
17474 });
17475
17476 cx.spawn_in(window, async move |editor, cx| {
17477 let transaction = futures::select_biased! {
17478 transaction = format.log_err().fuse() => transaction,
17479 () = timeout => {
17480 log::warn!("timed out waiting for formatting");
17481 None
17482 }
17483 };
17484
17485 buffer
17486 .update(cx, |buffer, cx| {
17487 if let Some(transaction) = transaction
17488 && !buffer.is_singleton()
17489 {
17490 buffer.push_transaction(&transaction.0, cx);
17491 }
17492 cx.notify();
17493 })
17494 .ok();
17495
17496 if let Some(transaction_id_now) =
17497 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17498 {
17499 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17500 if has_new_transaction {
17501 _ = editor.update(cx, |editor, _| {
17502 editor
17503 .selection_history
17504 .insert_transaction(transaction_id_now, selections_prev);
17505 });
17506 }
17507 }
17508
17509 Ok(())
17510 })
17511 }
17512
17513 fn organize_imports(
17514 &mut self,
17515 _: &OrganizeImports,
17516 window: &mut Window,
17517 cx: &mut Context<Self>,
17518 ) -> Option<Task<Result<()>>> {
17519 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17520 let project = match &self.project {
17521 Some(project) => project.clone(),
17522 None => return None,
17523 };
17524 Some(self.perform_code_action_kind(
17525 project,
17526 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17527 window,
17528 cx,
17529 ))
17530 }
17531
17532 fn perform_code_action_kind(
17533 &mut self,
17534 project: Entity<Project>,
17535 kind: CodeActionKind,
17536 window: &mut Window,
17537 cx: &mut Context<Self>,
17538 ) -> Task<Result<()>> {
17539 let buffer = self.buffer.clone();
17540 let buffers = buffer.read(cx).all_buffers();
17541 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17542 let apply_action = project.update(cx, |project, cx| {
17543 project.apply_code_action_kind(buffers, kind, true, cx)
17544 });
17545 cx.spawn_in(window, async move |_, cx| {
17546 let transaction = futures::select_biased! {
17547 () = timeout => {
17548 log::warn!("timed out waiting for executing code action");
17549 None
17550 }
17551 transaction = apply_action.log_err().fuse() => transaction,
17552 };
17553 buffer
17554 .update(cx, |buffer, cx| {
17555 // check if we need this
17556 if let Some(transaction) = transaction
17557 && !buffer.is_singleton()
17558 {
17559 buffer.push_transaction(&transaction.0, cx);
17560 }
17561 cx.notify();
17562 })
17563 .ok();
17564 Ok(())
17565 })
17566 }
17567
17568 pub fn restart_language_server(
17569 &mut self,
17570 _: &RestartLanguageServer,
17571 _: &mut Window,
17572 cx: &mut Context<Self>,
17573 ) {
17574 if let Some(project) = self.project.clone() {
17575 self.buffer.update(cx, |multi_buffer, cx| {
17576 project.update(cx, |project, cx| {
17577 project.restart_language_servers_for_buffers(
17578 multi_buffer.all_buffers().into_iter().collect(),
17579 HashSet::default(),
17580 cx,
17581 );
17582 });
17583 })
17584 }
17585 }
17586
17587 pub fn stop_language_server(
17588 &mut self,
17589 _: &StopLanguageServer,
17590 _: &mut Window,
17591 cx: &mut Context<Self>,
17592 ) {
17593 if let Some(project) = self.project.clone() {
17594 self.buffer.update(cx, |multi_buffer, cx| {
17595 project.update(cx, |project, cx| {
17596 project.stop_language_servers_for_buffers(
17597 multi_buffer.all_buffers().into_iter().collect(),
17598 HashSet::default(),
17599 cx,
17600 );
17601 cx.emit(project::Event::RefreshInlayHints);
17602 });
17603 });
17604 }
17605 }
17606
17607 fn cancel_language_server_work(
17608 workspace: &mut Workspace,
17609 _: &actions::CancelLanguageServerWork,
17610 _: &mut Window,
17611 cx: &mut Context<Workspace>,
17612 ) {
17613 let project = workspace.project();
17614 let buffers = workspace
17615 .active_item(cx)
17616 .and_then(|item| item.act_as::<Editor>(cx))
17617 .map_or(HashSet::default(), |editor| {
17618 editor.read(cx).buffer.read(cx).all_buffers()
17619 });
17620 project.update(cx, |project, cx| {
17621 project.cancel_language_server_work_for_buffers(buffers, cx);
17622 });
17623 }
17624
17625 fn show_character_palette(
17626 &mut self,
17627 _: &ShowCharacterPalette,
17628 window: &mut Window,
17629 _: &mut Context<Self>,
17630 ) {
17631 window.show_character_palette();
17632 }
17633
17634 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17635 if !self.diagnostics_enabled() {
17636 return;
17637 }
17638
17639 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17640 let buffer = self.buffer.read(cx).snapshot(cx);
17641 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17642 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17643 let is_valid = buffer
17644 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17645 .any(|entry| {
17646 entry.diagnostic.is_primary
17647 && !entry.range.is_empty()
17648 && entry.range.start == primary_range_start
17649 && entry.diagnostic.message == active_diagnostics.active_message
17650 });
17651
17652 if !is_valid {
17653 self.dismiss_diagnostics(cx);
17654 }
17655 }
17656 }
17657
17658 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17659 match &self.active_diagnostics {
17660 ActiveDiagnostic::Group(group) => Some(group),
17661 _ => None,
17662 }
17663 }
17664
17665 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17666 if !self.diagnostics_enabled() {
17667 return;
17668 }
17669 self.dismiss_diagnostics(cx);
17670 self.active_diagnostics = ActiveDiagnostic::All;
17671 }
17672
17673 fn activate_diagnostics(
17674 &mut self,
17675 buffer_id: BufferId,
17676 diagnostic: DiagnosticEntryRef<'_, usize>,
17677 window: &mut Window,
17678 cx: &mut Context<Self>,
17679 ) {
17680 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17681 return;
17682 }
17683 self.dismiss_diagnostics(cx);
17684 let snapshot = self.snapshot(window, cx);
17685 let buffer = self.buffer.read(cx).snapshot(cx);
17686 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17687 return;
17688 };
17689
17690 let diagnostic_group = buffer
17691 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17692 .collect::<Vec<_>>();
17693
17694 let blocks =
17695 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17696
17697 let blocks = self.display_map.update(cx, |display_map, cx| {
17698 display_map.insert_blocks(blocks, cx).into_iter().collect()
17699 });
17700 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17701 active_range: buffer.anchor_before(diagnostic.range.start)
17702 ..buffer.anchor_after(diagnostic.range.end),
17703 active_message: diagnostic.diagnostic.message.clone(),
17704 group_id: diagnostic.diagnostic.group_id,
17705 blocks,
17706 });
17707 cx.notify();
17708 }
17709
17710 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17711 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17712 return;
17713 };
17714
17715 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17716 if let ActiveDiagnostic::Group(group) = prev {
17717 self.display_map.update(cx, |display_map, cx| {
17718 display_map.remove_blocks(group.blocks, cx);
17719 });
17720 cx.notify();
17721 }
17722 }
17723
17724 /// Disable inline diagnostics rendering for this editor.
17725 pub fn disable_inline_diagnostics(&mut self) {
17726 self.inline_diagnostics_enabled = false;
17727 self.inline_diagnostics_update = Task::ready(());
17728 self.inline_diagnostics.clear();
17729 }
17730
17731 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17732 self.diagnostics_enabled = false;
17733 self.dismiss_diagnostics(cx);
17734 self.inline_diagnostics_update = Task::ready(());
17735 self.inline_diagnostics.clear();
17736 }
17737
17738 pub fn disable_word_completions(&mut self) {
17739 self.word_completions_enabled = false;
17740 }
17741
17742 pub fn diagnostics_enabled(&self) -> bool {
17743 self.diagnostics_enabled && self.mode.is_full()
17744 }
17745
17746 pub fn inline_diagnostics_enabled(&self) -> bool {
17747 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17748 }
17749
17750 pub fn show_inline_diagnostics(&self) -> bool {
17751 self.show_inline_diagnostics
17752 }
17753
17754 pub fn toggle_inline_diagnostics(
17755 &mut self,
17756 _: &ToggleInlineDiagnostics,
17757 window: &mut Window,
17758 cx: &mut Context<Editor>,
17759 ) {
17760 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17761 self.refresh_inline_diagnostics(false, window, cx);
17762 }
17763
17764 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17765 self.diagnostics_max_severity = severity;
17766 self.display_map.update(cx, |display_map, _| {
17767 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17768 });
17769 }
17770
17771 pub fn toggle_diagnostics(
17772 &mut self,
17773 _: &ToggleDiagnostics,
17774 window: &mut Window,
17775 cx: &mut Context<Editor>,
17776 ) {
17777 if !self.diagnostics_enabled() {
17778 return;
17779 }
17780
17781 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17782 EditorSettings::get_global(cx)
17783 .diagnostics_max_severity
17784 .filter(|severity| severity != &DiagnosticSeverity::Off)
17785 .unwrap_or(DiagnosticSeverity::Hint)
17786 } else {
17787 DiagnosticSeverity::Off
17788 };
17789 self.set_max_diagnostics_severity(new_severity, cx);
17790 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17791 self.active_diagnostics = ActiveDiagnostic::None;
17792 self.inline_diagnostics_update = Task::ready(());
17793 self.inline_diagnostics.clear();
17794 } else {
17795 self.refresh_inline_diagnostics(false, window, cx);
17796 }
17797
17798 cx.notify();
17799 }
17800
17801 pub fn toggle_minimap(
17802 &mut self,
17803 _: &ToggleMinimap,
17804 window: &mut Window,
17805 cx: &mut Context<Editor>,
17806 ) {
17807 if self.supports_minimap(cx) {
17808 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17809 }
17810 }
17811
17812 fn refresh_inline_diagnostics(
17813 &mut self,
17814 debounce: bool,
17815 window: &mut Window,
17816 cx: &mut Context<Self>,
17817 ) {
17818 let max_severity = ProjectSettings::get_global(cx)
17819 .diagnostics
17820 .inline
17821 .max_severity
17822 .unwrap_or(self.diagnostics_max_severity);
17823
17824 if !self.inline_diagnostics_enabled()
17825 || !self.show_inline_diagnostics
17826 || max_severity == DiagnosticSeverity::Off
17827 {
17828 self.inline_diagnostics_update = Task::ready(());
17829 self.inline_diagnostics.clear();
17830 return;
17831 }
17832
17833 let debounce_ms = ProjectSettings::get_global(cx)
17834 .diagnostics
17835 .inline
17836 .update_debounce_ms;
17837 let debounce = if debounce && debounce_ms > 0 {
17838 Some(Duration::from_millis(debounce_ms))
17839 } else {
17840 None
17841 };
17842 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17843 if let Some(debounce) = debounce {
17844 cx.background_executor().timer(debounce).await;
17845 }
17846 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17847 editor
17848 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17849 .ok()
17850 }) else {
17851 return;
17852 };
17853
17854 let new_inline_diagnostics = cx
17855 .background_spawn(async move {
17856 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17857 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17858 let message = diagnostic_entry
17859 .diagnostic
17860 .message
17861 .split_once('\n')
17862 .map(|(line, _)| line)
17863 .map(SharedString::new)
17864 .unwrap_or_else(|| {
17865 SharedString::new(&*diagnostic_entry.diagnostic.message)
17866 });
17867 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17868 let (Ok(i) | Err(i)) = inline_diagnostics
17869 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17870 inline_diagnostics.insert(
17871 i,
17872 (
17873 start_anchor,
17874 InlineDiagnostic {
17875 message,
17876 group_id: diagnostic_entry.diagnostic.group_id,
17877 start: diagnostic_entry.range.start.to_point(&snapshot),
17878 is_primary: diagnostic_entry.diagnostic.is_primary,
17879 severity: diagnostic_entry.diagnostic.severity,
17880 },
17881 ),
17882 );
17883 }
17884 inline_diagnostics
17885 })
17886 .await;
17887
17888 editor
17889 .update(cx, |editor, cx| {
17890 editor.inline_diagnostics = new_inline_diagnostics;
17891 cx.notify();
17892 })
17893 .ok();
17894 });
17895 }
17896
17897 fn pull_diagnostics(
17898 &mut self,
17899 buffer_id: Option<BufferId>,
17900 window: &Window,
17901 cx: &mut Context<Self>,
17902 ) -> Option<()> {
17903 if self.ignore_lsp_data() {
17904 return None;
17905 }
17906 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17907 .diagnostics
17908 .lsp_pull_diagnostics;
17909 if !pull_diagnostics_settings.enabled {
17910 return None;
17911 }
17912 let project = self.project()?.downgrade();
17913 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17914 let mut buffers = self.buffer.read(cx).all_buffers();
17915 buffers.retain(|buffer| {
17916 let buffer_id_to_retain = buffer.read(cx).remote_id();
17917 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
17918 && self.registered_buffers.contains_key(&buffer_id_to_retain)
17919 });
17920 if buffers.is_empty() {
17921 self.pull_diagnostics_task = Task::ready(());
17922 return None;
17923 }
17924
17925 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17926 cx.background_executor().timer(debounce).await;
17927
17928 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17929 buffers
17930 .into_iter()
17931 .filter_map(|buffer| {
17932 project
17933 .update(cx, |project, cx| {
17934 project.lsp_store().update(cx, |lsp_store, cx| {
17935 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17936 })
17937 })
17938 .ok()
17939 })
17940 .collect::<FuturesUnordered<_>>()
17941 }) else {
17942 return;
17943 };
17944
17945 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17946 match pull_task {
17947 Ok(()) => {
17948 if editor
17949 .update_in(cx, |editor, window, cx| {
17950 editor.update_diagnostics_state(window, cx);
17951 })
17952 .is_err()
17953 {
17954 return;
17955 }
17956 }
17957 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17958 }
17959 }
17960 });
17961
17962 Some(())
17963 }
17964
17965 pub fn set_selections_from_remote(
17966 &mut self,
17967 selections: Vec<Selection<Anchor>>,
17968 pending_selection: Option<Selection<Anchor>>,
17969 window: &mut Window,
17970 cx: &mut Context<Self>,
17971 ) {
17972 let old_cursor_position = self.selections.newest_anchor().head();
17973 self.selections.change_with(cx, |s| {
17974 s.select_anchors(selections);
17975 if let Some(pending_selection) = pending_selection {
17976 s.set_pending(pending_selection, SelectMode::Character);
17977 } else {
17978 s.clear_pending();
17979 }
17980 });
17981 self.selections_did_change(
17982 false,
17983 &old_cursor_position,
17984 SelectionEffects::default(),
17985 window,
17986 cx,
17987 );
17988 }
17989
17990 pub fn transact(
17991 &mut self,
17992 window: &mut Window,
17993 cx: &mut Context<Self>,
17994 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17995 ) -> Option<TransactionId> {
17996 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17997 this.start_transaction_at(Instant::now(), window, cx);
17998 update(this, window, cx);
17999 this.end_transaction_at(Instant::now(), cx)
18000 })
18001 }
18002
18003 pub fn start_transaction_at(
18004 &mut self,
18005 now: Instant,
18006 window: &mut Window,
18007 cx: &mut Context<Self>,
18008 ) -> Option<TransactionId> {
18009 self.end_selection(window, cx);
18010 if let Some(tx_id) = self
18011 .buffer
18012 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18013 {
18014 self.selection_history
18015 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18016 cx.emit(EditorEvent::TransactionBegun {
18017 transaction_id: tx_id,
18018 });
18019 Some(tx_id)
18020 } else {
18021 None
18022 }
18023 }
18024
18025 pub fn end_transaction_at(
18026 &mut self,
18027 now: Instant,
18028 cx: &mut Context<Self>,
18029 ) -> Option<TransactionId> {
18030 if let Some(transaction_id) = self
18031 .buffer
18032 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18033 {
18034 if let Some((_, end_selections)) =
18035 self.selection_history.transaction_mut(transaction_id)
18036 {
18037 *end_selections = Some(self.selections.disjoint_anchors_arc());
18038 } else {
18039 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18040 }
18041
18042 cx.emit(EditorEvent::Edited { transaction_id });
18043 Some(transaction_id)
18044 } else {
18045 None
18046 }
18047 }
18048
18049 pub fn modify_transaction_selection_history(
18050 &mut self,
18051 transaction_id: TransactionId,
18052 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18053 ) -> bool {
18054 self.selection_history
18055 .transaction_mut(transaction_id)
18056 .map(modify)
18057 .is_some()
18058 }
18059
18060 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18061 if self.selection_mark_mode {
18062 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18063 s.move_with(|_, sel| {
18064 sel.collapse_to(sel.head(), SelectionGoal::None);
18065 });
18066 })
18067 }
18068 self.selection_mark_mode = true;
18069 cx.notify();
18070 }
18071
18072 pub fn swap_selection_ends(
18073 &mut self,
18074 _: &actions::SwapSelectionEnds,
18075 window: &mut Window,
18076 cx: &mut Context<Self>,
18077 ) {
18078 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18079 s.move_with(|_, sel| {
18080 if sel.start != sel.end {
18081 sel.reversed = !sel.reversed
18082 }
18083 });
18084 });
18085 self.request_autoscroll(Autoscroll::newest(), cx);
18086 cx.notify();
18087 }
18088
18089 pub fn toggle_focus(
18090 workspace: &mut Workspace,
18091 _: &actions::ToggleFocus,
18092 window: &mut Window,
18093 cx: &mut Context<Workspace>,
18094 ) {
18095 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18096 return;
18097 };
18098 workspace.activate_item(&item, true, true, window, cx);
18099 }
18100
18101 pub fn toggle_fold(
18102 &mut self,
18103 _: &actions::ToggleFold,
18104 window: &mut Window,
18105 cx: &mut Context<Self>,
18106 ) {
18107 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18108 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18109 let selection = self.selections.newest::<Point>(&display_map);
18110
18111 let range = if selection.is_empty() {
18112 let point = selection.head().to_display_point(&display_map);
18113 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18114 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18115 .to_point(&display_map);
18116 start..end
18117 } else {
18118 selection.range()
18119 };
18120 if display_map.folds_in_range(range).next().is_some() {
18121 self.unfold_lines(&Default::default(), window, cx)
18122 } else {
18123 self.fold(&Default::default(), window, cx)
18124 }
18125 } else {
18126 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18127 let buffer_ids: HashSet<_> = self
18128 .selections
18129 .disjoint_anchor_ranges()
18130 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18131 .collect();
18132
18133 let should_unfold = buffer_ids
18134 .iter()
18135 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18136
18137 for buffer_id in buffer_ids {
18138 if should_unfold {
18139 self.unfold_buffer(buffer_id, cx);
18140 } else {
18141 self.fold_buffer(buffer_id, cx);
18142 }
18143 }
18144 }
18145 }
18146
18147 pub fn toggle_fold_recursive(
18148 &mut self,
18149 _: &actions::ToggleFoldRecursive,
18150 window: &mut Window,
18151 cx: &mut Context<Self>,
18152 ) {
18153 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18154
18155 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18156 let range = if selection.is_empty() {
18157 let point = selection.head().to_display_point(&display_map);
18158 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18159 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18160 .to_point(&display_map);
18161 start..end
18162 } else {
18163 selection.range()
18164 };
18165 if display_map.folds_in_range(range).next().is_some() {
18166 self.unfold_recursive(&Default::default(), window, cx)
18167 } else {
18168 self.fold_recursive(&Default::default(), window, cx)
18169 }
18170 }
18171
18172 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18173 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18174 let mut to_fold = Vec::new();
18175 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18176 let selections = self.selections.all_adjusted(&display_map);
18177
18178 for selection in selections {
18179 let range = selection.range().sorted();
18180 let buffer_start_row = range.start.row;
18181
18182 if range.start.row != range.end.row {
18183 let mut found = false;
18184 let mut row = range.start.row;
18185 while row <= range.end.row {
18186 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18187 {
18188 found = true;
18189 row = crease.range().end.row + 1;
18190 to_fold.push(crease);
18191 } else {
18192 row += 1
18193 }
18194 }
18195 if found {
18196 continue;
18197 }
18198 }
18199
18200 for row in (0..=range.start.row).rev() {
18201 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18202 && crease.range().end.row >= buffer_start_row
18203 {
18204 to_fold.push(crease);
18205 if row <= range.start.row {
18206 break;
18207 }
18208 }
18209 }
18210 }
18211
18212 self.fold_creases(to_fold, true, window, cx);
18213 } else {
18214 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18215 let buffer_ids = self
18216 .selections
18217 .disjoint_anchor_ranges()
18218 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18219 .collect::<HashSet<_>>();
18220 for buffer_id in buffer_ids {
18221 self.fold_buffer(buffer_id, cx);
18222 }
18223 }
18224 }
18225
18226 pub fn toggle_fold_all(
18227 &mut self,
18228 _: &actions::ToggleFoldAll,
18229 window: &mut Window,
18230 cx: &mut Context<Self>,
18231 ) {
18232 if self.buffer.read(cx).is_singleton() {
18233 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18234 let has_folds = display_map
18235 .folds_in_range(0..display_map.buffer_snapshot().len())
18236 .next()
18237 .is_some();
18238
18239 if has_folds {
18240 self.unfold_all(&actions::UnfoldAll, window, cx);
18241 } else {
18242 self.fold_all(&actions::FoldAll, window, cx);
18243 }
18244 } else {
18245 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18246 let should_unfold = buffer_ids
18247 .iter()
18248 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18249
18250 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18251 editor
18252 .update_in(cx, |editor, _, cx| {
18253 for buffer_id in buffer_ids {
18254 if should_unfold {
18255 editor.unfold_buffer(buffer_id, cx);
18256 } else {
18257 editor.fold_buffer(buffer_id, cx);
18258 }
18259 }
18260 })
18261 .ok();
18262 });
18263 }
18264 }
18265
18266 fn fold_at_level(
18267 &mut self,
18268 fold_at: &FoldAtLevel,
18269 window: &mut Window,
18270 cx: &mut Context<Self>,
18271 ) {
18272 if !self.buffer.read(cx).is_singleton() {
18273 return;
18274 }
18275
18276 let fold_at_level = fold_at.0;
18277 let snapshot = self.buffer.read(cx).snapshot(cx);
18278 let mut to_fold = Vec::new();
18279 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18280
18281 let row_ranges_to_keep: Vec<Range<u32>> = self
18282 .selections
18283 .all::<Point>(&self.display_snapshot(cx))
18284 .into_iter()
18285 .map(|sel| sel.start.row..sel.end.row)
18286 .collect();
18287
18288 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18289 while start_row < end_row {
18290 match self
18291 .snapshot(window, cx)
18292 .crease_for_buffer_row(MultiBufferRow(start_row))
18293 {
18294 Some(crease) => {
18295 let nested_start_row = crease.range().start.row + 1;
18296 let nested_end_row = crease.range().end.row;
18297
18298 if current_level < fold_at_level {
18299 stack.push((nested_start_row, nested_end_row, current_level + 1));
18300 } else if current_level == fold_at_level {
18301 // Fold iff there is no selection completely contained within the fold region
18302 if !row_ranges_to_keep.iter().any(|selection| {
18303 selection.end >= nested_start_row
18304 && selection.start <= nested_end_row
18305 }) {
18306 to_fold.push(crease);
18307 }
18308 }
18309
18310 start_row = nested_end_row + 1;
18311 }
18312 None => start_row += 1,
18313 }
18314 }
18315 }
18316
18317 self.fold_creases(to_fold, true, window, cx);
18318 }
18319
18320 pub fn fold_at_level_1(
18321 &mut self,
18322 _: &actions::FoldAtLevel1,
18323 window: &mut Window,
18324 cx: &mut Context<Self>,
18325 ) {
18326 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18327 }
18328
18329 pub fn fold_at_level_2(
18330 &mut self,
18331 _: &actions::FoldAtLevel2,
18332 window: &mut Window,
18333 cx: &mut Context<Self>,
18334 ) {
18335 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18336 }
18337
18338 pub fn fold_at_level_3(
18339 &mut self,
18340 _: &actions::FoldAtLevel3,
18341 window: &mut Window,
18342 cx: &mut Context<Self>,
18343 ) {
18344 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18345 }
18346
18347 pub fn fold_at_level_4(
18348 &mut self,
18349 _: &actions::FoldAtLevel4,
18350 window: &mut Window,
18351 cx: &mut Context<Self>,
18352 ) {
18353 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18354 }
18355
18356 pub fn fold_at_level_5(
18357 &mut self,
18358 _: &actions::FoldAtLevel5,
18359 window: &mut Window,
18360 cx: &mut Context<Self>,
18361 ) {
18362 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18363 }
18364
18365 pub fn fold_at_level_6(
18366 &mut self,
18367 _: &actions::FoldAtLevel6,
18368 window: &mut Window,
18369 cx: &mut Context<Self>,
18370 ) {
18371 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18372 }
18373
18374 pub fn fold_at_level_7(
18375 &mut self,
18376 _: &actions::FoldAtLevel7,
18377 window: &mut Window,
18378 cx: &mut Context<Self>,
18379 ) {
18380 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18381 }
18382
18383 pub fn fold_at_level_8(
18384 &mut self,
18385 _: &actions::FoldAtLevel8,
18386 window: &mut Window,
18387 cx: &mut Context<Self>,
18388 ) {
18389 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18390 }
18391
18392 pub fn fold_at_level_9(
18393 &mut self,
18394 _: &actions::FoldAtLevel9,
18395 window: &mut Window,
18396 cx: &mut Context<Self>,
18397 ) {
18398 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18399 }
18400
18401 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18402 if self.buffer.read(cx).is_singleton() {
18403 let mut fold_ranges = Vec::new();
18404 let snapshot = self.buffer.read(cx).snapshot(cx);
18405
18406 for row in 0..snapshot.max_row().0 {
18407 if let Some(foldable_range) = self
18408 .snapshot(window, cx)
18409 .crease_for_buffer_row(MultiBufferRow(row))
18410 {
18411 fold_ranges.push(foldable_range);
18412 }
18413 }
18414
18415 self.fold_creases(fold_ranges, true, window, cx);
18416 } else {
18417 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18418 editor
18419 .update_in(cx, |editor, _, cx| {
18420 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18421 editor.fold_buffer(buffer_id, cx);
18422 }
18423 })
18424 .ok();
18425 });
18426 }
18427 }
18428
18429 pub fn fold_function_bodies(
18430 &mut self,
18431 _: &actions::FoldFunctionBodies,
18432 window: &mut Window,
18433 cx: &mut Context<Self>,
18434 ) {
18435 let snapshot = self.buffer.read(cx).snapshot(cx);
18436
18437 let ranges = snapshot
18438 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18439 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18440 .collect::<Vec<_>>();
18441
18442 let creases = ranges
18443 .into_iter()
18444 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18445 .collect();
18446
18447 self.fold_creases(creases, true, window, cx);
18448 }
18449
18450 pub fn fold_recursive(
18451 &mut self,
18452 _: &actions::FoldRecursive,
18453 window: &mut Window,
18454 cx: &mut Context<Self>,
18455 ) {
18456 let mut to_fold = Vec::new();
18457 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18458 let selections = self.selections.all_adjusted(&display_map);
18459
18460 for selection in selections {
18461 let range = selection.range().sorted();
18462 let buffer_start_row = range.start.row;
18463
18464 if range.start.row != range.end.row {
18465 let mut found = false;
18466 for row in range.start.row..=range.end.row {
18467 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18468 found = true;
18469 to_fold.push(crease);
18470 }
18471 }
18472 if found {
18473 continue;
18474 }
18475 }
18476
18477 for row in (0..=range.start.row).rev() {
18478 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18479 if crease.range().end.row >= buffer_start_row {
18480 to_fold.push(crease);
18481 } else {
18482 break;
18483 }
18484 }
18485 }
18486 }
18487
18488 self.fold_creases(to_fold, true, window, cx);
18489 }
18490
18491 pub fn fold_at(
18492 &mut self,
18493 buffer_row: MultiBufferRow,
18494 window: &mut Window,
18495 cx: &mut Context<Self>,
18496 ) {
18497 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18498
18499 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18500 let autoscroll = self
18501 .selections
18502 .all::<Point>(&display_map)
18503 .iter()
18504 .any(|selection| crease.range().overlaps(&selection.range()));
18505
18506 self.fold_creases(vec![crease], autoscroll, window, cx);
18507 }
18508 }
18509
18510 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18511 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18512 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18513 let buffer = display_map.buffer_snapshot();
18514 let selections = self.selections.all::<Point>(&display_map);
18515 let ranges = selections
18516 .iter()
18517 .map(|s| {
18518 let range = s.display_range(&display_map).sorted();
18519 let mut start = range.start.to_point(&display_map);
18520 let mut end = range.end.to_point(&display_map);
18521 start.column = 0;
18522 end.column = buffer.line_len(MultiBufferRow(end.row));
18523 start..end
18524 })
18525 .collect::<Vec<_>>();
18526
18527 self.unfold_ranges(&ranges, true, true, cx);
18528 } else {
18529 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18530 let buffer_ids = self
18531 .selections
18532 .disjoint_anchor_ranges()
18533 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18534 .collect::<HashSet<_>>();
18535 for buffer_id in buffer_ids {
18536 self.unfold_buffer(buffer_id, cx);
18537 }
18538 }
18539 }
18540
18541 pub fn unfold_recursive(
18542 &mut self,
18543 _: &UnfoldRecursive,
18544 _window: &mut Window,
18545 cx: &mut Context<Self>,
18546 ) {
18547 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18548 let selections = self.selections.all::<Point>(&display_map);
18549 let ranges = selections
18550 .iter()
18551 .map(|s| {
18552 let mut range = s.display_range(&display_map).sorted();
18553 *range.start.column_mut() = 0;
18554 *range.end.column_mut() = display_map.line_len(range.end.row());
18555 let start = range.start.to_point(&display_map);
18556 let end = range.end.to_point(&display_map);
18557 start..end
18558 })
18559 .collect::<Vec<_>>();
18560
18561 self.unfold_ranges(&ranges, true, true, cx);
18562 }
18563
18564 pub fn unfold_at(
18565 &mut self,
18566 buffer_row: MultiBufferRow,
18567 _window: &mut Window,
18568 cx: &mut Context<Self>,
18569 ) {
18570 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18571
18572 let intersection_range = Point::new(buffer_row.0, 0)
18573 ..Point::new(
18574 buffer_row.0,
18575 display_map.buffer_snapshot().line_len(buffer_row),
18576 );
18577
18578 let autoscroll = self
18579 .selections
18580 .all::<Point>(&display_map)
18581 .iter()
18582 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18583
18584 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18585 }
18586
18587 pub fn unfold_all(
18588 &mut self,
18589 _: &actions::UnfoldAll,
18590 _window: &mut Window,
18591 cx: &mut Context<Self>,
18592 ) {
18593 if self.buffer.read(cx).is_singleton() {
18594 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18595 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18596 } else {
18597 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18598 editor
18599 .update(cx, |editor, cx| {
18600 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18601 editor.unfold_buffer(buffer_id, cx);
18602 }
18603 })
18604 .ok();
18605 });
18606 }
18607 }
18608
18609 pub fn fold_selected_ranges(
18610 &mut self,
18611 _: &FoldSelectedRanges,
18612 window: &mut Window,
18613 cx: &mut Context<Self>,
18614 ) {
18615 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18616 let selections = self.selections.all_adjusted(&display_map);
18617 let ranges = selections
18618 .into_iter()
18619 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18620 .collect::<Vec<_>>();
18621 self.fold_creases(ranges, true, window, cx);
18622 }
18623
18624 pub fn fold_ranges<T: ToOffset + Clone>(
18625 &mut self,
18626 ranges: Vec<Range<T>>,
18627 auto_scroll: bool,
18628 window: &mut Window,
18629 cx: &mut Context<Self>,
18630 ) {
18631 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18632 let ranges = ranges
18633 .into_iter()
18634 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18635 .collect::<Vec<_>>();
18636 self.fold_creases(ranges, auto_scroll, window, cx);
18637 }
18638
18639 pub fn fold_creases<T: ToOffset + Clone>(
18640 &mut self,
18641 creases: Vec<Crease<T>>,
18642 auto_scroll: bool,
18643 _window: &mut Window,
18644 cx: &mut Context<Self>,
18645 ) {
18646 if creases.is_empty() {
18647 return;
18648 }
18649
18650 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18651
18652 if auto_scroll {
18653 self.request_autoscroll(Autoscroll::fit(), cx);
18654 }
18655
18656 cx.notify();
18657
18658 self.scrollbar_marker_state.dirty = true;
18659 self.folds_did_change(cx);
18660 }
18661
18662 /// Removes any folds whose ranges intersect any of the given ranges.
18663 pub fn unfold_ranges<T: ToOffset + Clone>(
18664 &mut self,
18665 ranges: &[Range<T>],
18666 inclusive: bool,
18667 auto_scroll: bool,
18668 cx: &mut Context<Self>,
18669 ) {
18670 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18671 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18672 });
18673 self.folds_did_change(cx);
18674 }
18675
18676 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18677 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18678 return;
18679 }
18680 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18681 self.display_map.update(cx, |display_map, cx| {
18682 display_map.fold_buffers([buffer_id], cx)
18683 });
18684 cx.emit(EditorEvent::BufferFoldToggled {
18685 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18686 folded: true,
18687 });
18688 cx.notify();
18689 }
18690
18691 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18692 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18693 return;
18694 }
18695 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18696 self.display_map.update(cx, |display_map, cx| {
18697 display_map.unfold_buffers([buffer_id], cx);
18698 });
18699 cx.emit(EditorEvent::BufferFoldToggled {
18700 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18701 folded: false,
18702 });
18703 cx.notify();
18704 }
18705
18706 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18707 self.display_map.read(cx).is_buffer_folded(buffer)
18708 }
18709
18710 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18711 self.display_map.read(cx).folded_buffers()
18712 }
18713
18714 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18715 self.display_map.update(cx, |display_map, cx| {
18716 display_map.disable_header_for_buffer(buffer_id, cx);
18717 });
18718 cx.notify();
18719 }
18720
18721 /// Removes any folds with the given ranges.
18722 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18723 &mut self,
18724 ranges: &[Range<T>],
18725 type_id: TypeId,
18726 auto_scroll: bool,
18727 cx: &mut Context<Self>,
18728 ) {
18729 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18730 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18731 });
18732 self.folds_did_change(cx);
18733 }
18734
18735 fn remove_folds_with<T: ToOffset + Clone>(
18736 &mut self,
18737 ranges: &[Range<T>],
18738 auto_scroll: bool,
18739 cx: &mut Context<Self>,
18740 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18741 ) {
18742 if ranges.is_empty() {
18743 return;
18744 }
18745
18746 let mut buffers_affected = HashSet::default();
18747 let multi_buffer = self.buffer().read(cx);
18748 for range in ranges {
18749 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18750 buffers_affected.insert(buffer.read(cx).remote_id());
18751 };
18752 }
18753
18754 self.display_map.update(cx, update);
18755
18756 if auto_scroll {
18757 self.request_autoscroll(Autoscroll::fit(), cx);
18758 }
18759
18760 cx.notify();
18761 self.scrollbar_marker_state.dirty = true;
18762 self.active_indent_guides_state.dirty = true;
18763 }
18764
18765 pub fn update_renderer_widths(
18766 &mut self,
18767 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18768 cx: &mut Context<Self>,
18769 ) -> bool {
18770 self.display_map
18771 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18772 }
18773
18774 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18775 self.display_map.read(cx).fold_placeholder.clone()
18776 }
18777
18778 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18779 self.buffer.update(cx, |buffer, cx| {
18780 buffer.set_all_diff_hunks_expanded(cx);
18781 });
18782 }
18783
18784 pub fn expand_all_diff_hunks(
18785 &mut self,
18786 _: &ExpandAllDiffHunks,
18787 _window: &mut Window,
18788 cx: &mut Context<Self>,
18789 ) {
18790 self.buffer.update(cx, |buffer, cx| {
18791 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18792 });
18793 }
18794
18795 pub fn collapse_all_diff_hunks(
18796 &mut self,
18797 _: &CollapseAllDiffHunks,
18798 _window: &mut Window,
18799 cx: &mut Context<Self>,
18800 ) {
18801 self.buffer.update(cx, |buffer, cx| {
18802 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18803 });
18804 }
18805
18806 pub fn toggle_selected_diff_hunks(
18807 &mut self,
18808 _: &ToggleSelectedDiffHunks,
18809 _window: &mut Window,
18810 cx: &mut Context<Self>,
18811 ) {
18812 let ranges: Vec<_> = self
18813 .selections
18814 .disjoint_anchors()
18815 .iter()
18816 .map(|s| s.range())
18817 .collect();
18818 self.toggle_diff_hunks_in_ranges(ranges, cx);
18819 }
18820
18821 pub fn diff_hunks_in_ranges<'a>(
18822 &'a self,
18823 ranges: &'a [Range<Anchor>],
18824 buffer: &'a MultiBufferSnapshot,
18825 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18826 ranges.iter().flat_map(move |range| {
18827 let end_excerpt_id = range.end.excerpt_id;
18828 let range = range.to_point(buffer);
18829 let mut peek_end = range.end;
18830 if range.end.row < buffer.max_row().0 {
18831 peek_end = Point::new(range.end.row + 1, 0);
18832 }
18833 buffer
18834 .diff_hunks_in_range(range.start..peek_end)
18835 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18836 })
18837 }
18838
18839 pub fn has_stageable_diff_hunks_in_ranges(
18840 &self,
18841 ranges: &[Range<Anchor>],
18842 snapshot: &MultiBufferSnapshot,
18843 ) -> bool {
18844 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18845 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18846 }
18847
18848 pub fn toggle_staged_selected_diff_hunks(
18849 &mut self,
18850 _: &::git::ToggleStaged,
18851 _: &mut Window,
18852 cx: &mut Context<Self>,
18853 ) {
18854 let snapshot = self.buffer.read(cx).snapshot(cx);
18855 let ranges: Vec<_> = self
18856 .selections
18857 .disjoint_anchors()
18858 .iter()
18859 .map(|s| s.range())
18860 .collect();
18861 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18862 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18863 }
18864
18865 pub fn set_render_diff_hunk_controls(
18866 &mut self,
18867 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18868 cx: &mut Context<Self>,
18869 ) {
18870 self.render_diff_hunk_controls = render_diff_hunk_controls;
18871 cx.notify();
18872 }
18873
18874 pub fn stage_and_next(
18875 &mut self,
18876 _: &::git::StageAndNext,
18877 window: &mut Window,
18878 cx: &mut Context<Self>,
18879 ) {
18880 self.do_stage_or_unstage_and_next(true, window, cx);
18881 }
18882
18883 pub fn unstage_and_next(
18884 &mut self,
18885 _: &::git::UnstageAndNext,
18886 window: &mut Window,
18887 cx: &mut Context<Self>,
18888 ) {
18889 self.do_stage_or_unstage_and_next(false, window, cx);
18890 }
18891
18892 pub fn stage_or_unstage_diff_hunks(
18893 &mut self,
18894 stage: bool,
18895 ranges: Vec<Range<Anchor>>,
18896 cx: &mut Context<Self>,
18897 ) {
18898 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18899 cx.spawn(async move |this, cx| {
18900 task.await?;
18901 this.update(cx, |this, cx| {
18902 let snapshot = this.buffer.read(cx).snapshot(cx);
18903 let chunk_by = this
18904 .diff_hunks_in_ranges(&ranges, &snapshot)
18905 .chunk_by(|hunk| hunk.buffer_id);
18906 for (buffer_id, hunks) in &chunk_by {
18907 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18908 }
18909 })
18910 })
18911 .detach_and_log_err(cx);
18912 }
18913
18914 fn save_buffers_for_ranges_if_needed(
18915 &mut self,
18916 ranges: &[Range<Anchor>],
18917 cx: &mut Context<Editor>,
18918 ) -> Task<Result<()>> {
18919 let multibuffer = self.buffer.read(cx);
18920 let snapshot = multibuffer.read(cx);
18921 let buffer_ids: HashSet<_> = ranges
18922 .iter()
18923 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18924 .collect();
18925 drop(snapshot);
18926
18927 let mut buffers = HashSet::default();
18928 for buffer_id in buffer_ids {
18929 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18930 let buffer = buffer_entity.read(cx);
18931 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18932 {
18933 buffers.insert(buffer_entity);
18934 }
18935 }
18936 }
18937
18938 if let Some(project) = &self.project {
18939 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18940 } else {
18941 Task::ready(Ok(()))
18942 }
18943 }
18944
18945 fn do_stage_or_unstage_and_next(
18946 &mut self,
18947 stage: bool,
18948 window: &mut Window,
18949 cx: &mut Context<Self>,
18950 ) {
18951 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18952
18953 if ranges.iter().any(|range| range.start != range.end) {
18954 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18955 return;
18956 }
18957
18958 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18959 let snapshot = self.snapshot(window, cx);
18960 let position = self
18961 .selections
18962 .newest::<Point>(&snapshot.display_snapshot)
18963 .head();
18964 let mut row = snapshot
18965 .buffer_snapshot()
18966 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
18967 .find(|hunk| hunk.row_range.start.0 > position.row)
18968 .map(|hunk| hunk.row_range.start);
18969
18970 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18971 // Outside of the project diff editor, wrap around to the beginning.
18972 if !all_diff_hunks_expanded {
18973 row = row.or_else(|| {
18974 snapshot
18975 .buffer_snapshot()
18976 .diff_hunks_in_range(Point::zero()..position)
18977 .find(|hunk| hunk.row_range.end.0 < position.row)
18978 .map(|hunk| hunk.row_range.start)
18979 });
18980 }
18981
18982 if let Some(row) = row {
18983 let destination = Point::new(row.0, 0);
18984 let autoscroll = Autoscroll::center();
18985
18986 self.unfold_ranges(&[destination..destination], false, false, cx);
18987 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18988 s.select_ranges([destination..destination]);
18989 });
18990 }
18991 }
18992
18993 fn do_stage_or_unstage(
18994 &self,
18995 stage: bool,
18996 buffer_id: BufferId,
18997 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18998 cx: &mut App,
18999 ) -> Option<()> {
19000 let project = self.project()?;
19001 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19002 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19003 let buffer_snapshot = buffer.read(cx).snapshot();
19004 let file_exists = buffer_snapshot
19005 .file()
19006 .is_some_and(|file| file.disk_state().exists());
19007 diff.update(cx, |diff, cx| {
19008 diff.stage_or_unstage_hunks(
19009 stage,
19010 &hunks
19011 .map(|hunk| buffer_diff::DiffHunk {
19012 buffer_range: hunk.buffer_range,
19013 diff_base_byte_range: hunk.diff_base_byte_range,
19014 secondary_status: hunk.secondary_status,
19015 range: Point::zero()..Point::zero(), // unused
19016 })
19017 .collect::<Vec<_>>(),
19018 &buffer_snapshot,
19019 file_exists,
19020 cx,
19021 )
19022 });
19023 None
19024 }
19025
19026 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19027 let ranges: Vec<_> = self
19028 .selections
19029 .disjoint_anchors()
19030 .iter()
19031 .map(|s| s.range())
19032 .collect();
19033 self.buffer
19034 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19035 }
19036
19037 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19038 self.buffer.update(cx, |buffer, cx| {
19039 let ranges = vec![Anchor::min()..Anchor::max()];
19040 if !buffer.all_diff_hunks_expanded()
19041 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19042 {
19043 buffer.collapse_diff_hunks(ranges, cx);
19044 true
19045 } else {
19046 false
19047 }
19048 })
19049 }
19050
19051 fn toggle_diff_hunks_in_ranges(
19052 &mut self,
19053 ranges: Vec<Range<Anchor>>,
19054 cx: &mut Context<Editor>,
19055 ) {
19056 self.buffer.update(cx, |buffer, cx| {
19057 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19058 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19059 })
19060 }
19061
19062 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19063 self.buffer.update(cx, |buffer, cx| {
19064 let snapshot = buffer.snapshot(cx);
19065 let excerpt_id = range.end.excerpt_id;
19066 let point_range = range.to_point(&snapshot);
19067 let expand = !buffer.single_hunk_is_expanded(range, cx);
19068 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19069 })
19070 }
19071
19072 pub(crate) fn apply_all_diff_hunks(
19073 &mut self,
19074 _: &ApplyAllDiffHunks,
19075 window: &mut Window,
19076 cx: &mut Context<Self>,
19077 ) {
19078 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19079
19080 let buffers = self.buffer.read(cx).all_buffers();
19081 for branch_buffer in buffers {
19082 branch_buffer.update(cx, |branch_buffer, cx| {
19083 branch_buffer.merge_into_base(Vec::new(), cx);
19084 });
19085 }
19086
19087 if let Some(project) = self.project.clone() {
19088 self.save(
19089 SaveOptions {
19090 format: true,
19091 autosave: false,
19092 },
19093 project,
19094 window,
19095 cx,
19096 )
19097 .detach_and_log_err(cx);
19098 }
19099 }
19100
19101 pub(crate) fn apply_selected_diff_hunks(
19102 &mut self,
19103 _: &ApplyDiffHunk,
19104 window: &mut Window,
19105 cx: &mut Context<Self>,
19106 ) {
19107 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19108 let snapshot = self.snapshot(window, cx);
19109 let hunks = snapshot.hunks_for_ranges(
19110 self.selections
19111 .all(&snapshot.display_snapshot)
19112 .into_iter()
19113 .map(|selection| selection.range()),
19114 );
19115 let mut ranges_by_buffer = HashMap::default();
19116 self.transact(window, cx, |editor, _window, cx| {
19117 for hunk in hunks {
19118 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19119 ranges_by_buffer
19120 .entry(buffer.clone())
19121 .or_insert_with(Vec::new)
19122 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19123 }
19124 }
19125
19126 for (buffer, ranges) in ranges_by_buffer {
19127 buffer.update(cx, |buffer, cx| {
19128 buffer.merge_into_base(ranges, cx);
19129 });
19130 }
19131 });
19132
19133 if let Some(project) = self.project.clone() {
19134 self.save(
19135 SaveOptions {
19136 format: true,
19137 autosave: false,
19138 },
19139 project,
19140 window,
19141 cx,
19142 )
19143 .detach_and_log_err(cx);
19144 }
19145 }
19146
19147 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19148 if hovered != self.gutter_hovered {
19149 self.gutter_hovered = hovered;
19150 cx.notify();
19151 }
19152 }
19153
19154 pub fn insert_blocks(
19155 &mut self,
19156 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19157 autoscroll: Option<Autoscroll>,
19158 cx: &mut Context<Self>,
19159 ) -> Vec<CustomBlockId> {
19160 let blocks = self
19161 .display_map
19162 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19163 if let Some(autoscroll) = autoscroll {
19164 self.request_autoscroll(autoscroll, cx);
19165 }
19166 cx.notify();
19167 blocks
19168 }
19169
19170 pub fn resize_blocks(
19171 &mut self,
19172 heights: HashMap<CustomBlockId, u32>,
19173 autoscroll: Option<Autoscroll>,
19174 cx: &mut Context<Self>,
19175 ) {
19176 self.display_map
19177 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19178 if let Some(autoscroll) = autoscroll {
19179 self.request_autoscroll(autoscroll, cx);
19180 }
19181 cx.notify();
19182 }
19183
19184 pub fn replace_blocks(
19185 &mut self,
19186 renderers: HashMap<CustomBlockId, RenderBlock>,
19187 autoscroll: Option<Autoscroll>,
19188 cx: &mut Context<Self>,
19189 ) {
19190 self.display_map
19191 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19192 if let Some(autoscroll) = autoscroll {
19193 self.request_autoscroll(autoscroll, cx);
19194 }
19195 cx.notify();
19196 }
19197
19198 pub fn remove_blocks(
19199 &mut self,
19200 block_ids: HashSet<CustomBlockId>,
19201 autoscroll: Option<Autoscroll>,
19202 cx: &mut Context<Self>,
19203 ) {
19204 self.display_map.update(cx, |display_map, cx| {
19205 display_map.remove_blocks(block_ids, cx)
19206 });
19207 if let Some(autoscroll) = autoscroll {
19208 self.request_autoscroll(autoscroll, cx);
19209 }
19210 cx.notify();
19211 }
19212
19213 pub fn row_for_block(
19214 &self,
19215 block_id: CustomBlockId,
19216 cx: &mut Context<Self>,
19217 ) -> Option<DisplayRow> {
19218 self.display_map
19219 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19220 }
19221
19222 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19223 self.focused_block = Some(focused_block);
19224 }
19225
19226 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19227 self.focused_block.take()
19228 }
19229
19230 pub fn insert_creases(
19231 &mut self,
19232 creases: impl IntoIterator<Item = Crease<Anchor>>,
19233 cx: &mut Context<Self>,
19234 ) -> Vec<CreaseId> {
19235 self.display_map
19236 .update(cx, |map, cx| map.insert_creases(creases, cx))
19237 }
19238
19239 pub fn remove_creases(
19240 &mut self,
19241 ids: impl IntoIterator<Item = CreaseId>,
19242 cx: &mut Context<Self>,
19243 ) -> Vec<(CreaseId, Range<Anchor>)> {
19244 self.display_map
19245 .update(cx, |map, cx| map.remove_creases(ids, cx))
19246 }
19247
19248 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19249 self.display_map
19250 .update(cx, |map, cx| map.snapshot(cx))
19251 .longest_row()
19252 }
19253
19254 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19255 self.display_map
19256 .update(cx, |map, cx| map.snapshot(cx))
19257 .max_point()
19258 }
19259
19260 pub fn text(&self, cx: &App) -> String {
19261 self.buffer.read(cx).read(cx).text()
19262 }
19263
19264 pub fn is_empty(&self, cx: &App) -> bool {
19265 self.buffer.read(cx).read(cx).is_empty()
19266 }
19267
19268 pub fn text_option(&self, cx: &App) -> Option<String> {
19269 let text = self.text(cx);
19270 let text = text.trim();
19271
19272 if text.is_empty() {
19273 return None;
19274 }
19275
19276 Some(text.to_string())
19277 }
19278
19279 pub fn set_text(
19280 &mut self,
19281 text: impl Into<Arc<str>>,
19282 window: &mut Window,
19283 cx: &mut Context<Self>,
19284 ) {
19285 self.transact(window, cx, |this, _, cx| {
19286 this.buffer
19287 .read(cx)
19288 .as_singleton()
19289 .expect("you can only call set_text on editors for singleton buffers")
19290 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19291 });
19292 }
19293
19294 pub fn display_text(&self, cx: &mut App) -> String {
19295 self.display_map
19296 .update(cx, |map, cx| map.snapshot(cx))
19297 .text()
19298 }
19299
19300 fn create_minimap(
19301 &self,
19302 minimap_settings: MinimapSettings,
19303 window: &mut Window,
19304 cx: &mut Context<Self>,
19305 ) -> Option<Entity<Self>> {
19306 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19307 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19308 }
19309
19310 fn initialize_new_minimap(
19311 &self,
19312 minimap_settings: MinimapSettings,
19313 window: &mut Window,
19314 cx: &mut Context<Self>,
19315 ) -> Entity<Self> {
19316 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19317
19318 let mut minimap = Editor::new_internal(
19319 EditorMode::Minimap {
19320 parent: cx.weak_entity(),
19321 },
19322 self.buffer.clone(),
19323 None,
19324 Some(self.display_map.clone()),
19325 window,
19326 cx,
19327 );
19328 minimap.scroll_manager.clone_state(&self.scroll_manager);
19329 minimap.set_text_style_refinement(TextStyleRefinement {
19330 font_size: Some(MINIMAP_FONT_SIZE),
19331 font_weight: Some(MINIMAP_FONT_WEIGHT),
19332 ..Default::default()
19333 });
19334 minimap.update_minimap_configuration(minimap_settings, cx);
19335 cx.new(|_| minimap)
19336 }
19337
19338 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19339 let current_line_highlight = minimap_settings
19340 .current_line_highlight
19341 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19342 self.set_current_line_highlight(Some(current_line_highlight));
19343 }
19344
19345 pub fn minimap(&self) -> Option<&Entity<Self>> {
19346 self.minimap
19347 .as_ref()
19348 .filter(|_| self.minimap_visibility.visible())
19349 }
19350
19351 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19352 let mut wrap_guides = smallvec![];
19353
19354 if self.show_wrap_guides == Some(false) {
19355 return wrap_guides;
19356 }
19357
19358 let settings = self.buffer.read(cx).language_settings(cx);
19359 if settings.show_wrap_guides {
19360 match self.soft_wrap_mode(cx) {
19361 SoftWrap::Column(soft_wrap) => {
19362 wrap_guides.push((soft_wrap as usize, true));
19363 }
19364 SoftWrap::Bounded(soft_wrap) => {
19365 wrap_guides.push((soft_wrap as usize, true));
19366 }
19367 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19368 }
19369 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19370 }
19371
19372 wrap_guides
19373 }
19374
19375 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19376 let settings = self.buffer.read(cx).language_settings(cx);
19377 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19378 match mode {
19379 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19380 SoftWrap::None
19381 }
19382 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19383 language_settings::SoftWrap::PreferredLineLength => {
19384 SoftWrap::Column(settings.preferred_line_length)
19385 }
19386 language_settings::SoftWrap::Bounded => {
19387 SoftWrap::Bounded(settings.preferred_line_length)
19388 }
19389 }
19390 }
19391
19392 pub fn set_soft_wrap_mode(
19393 &mut self,
19394 mode: language_settings::SoftWrap,
19395
19396 cx: &mut Context<Self>,
19397 ) {
19398 self.soft_wrap_mode_override = Some(mode);
19399 cx.notify();
19400 }
19401
19402 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19403 self.hard_wrap = hard_wrap;
19404 cx.notify();
19405 }
19406
19407 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19408 self.text_style_refinement = Some(style);
19409 }
19410
19411 /// called by the Element so we know what style we were most recently rendered with.
19412 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19413 // We intentionally do not inform the display map about the minimap style
19414 // so that wrapping is not recalculated and stays consistent for the editor
19415 // and its linked minimap.
19416 if !self.mode.is_minimap() {
19417 let font = style.text.font();
19418 let font_size = style.text.font_size.to_pixels(window.rem_size());
19419 let display_map = self
19420 .placeholder_display_map
19421 .as_ref()
19422 .filter(|_| self.is_empty(cx))
19423 .unwrap_or(&self.display_map);
19424
19425 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19426 }
19427 self.style = Some(style);
19428 }
19429
19430 pub fn style(&self) -> Option<&EditorStyle> {
19431 self.style.as_ref()
19432 }
19433
19434 // Called by the element. This method is not designed to be called outside of the editor
19435 // element's layout code because it does not notify when rewrapping is computed synchronously.
19436 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19437 if self.is_empty(cx) {
19438 self.placeholder_display_map
19439 .as_ref()
19440 .map_or(false, |display_map| {
19441 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19442 })
19443 } else {
19444 self.display_map
19445 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19446 }
19447 }
19448
19449 pub fn set_soft_wrap(&mut self) {
19450 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19451 }
19452
19453 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19454 if self.soft_wrap_mode_override.is_some() {
19455 self.soft_wrap_mode_override.take();
19456 } else {
19457 let soft_wrap = match self.soft_wrap_mode(cx) {
19458 SoftWrap::GitDiff => return,
19459 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19460 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19461 language_settings::SoftWrap::None
19462 }
19463 };
19464 self.soft_wrap_mode_override = Some(soft_wrap);
19465 }
19466 cx.notify();
19467 }
19468
19469 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19470 let Some(workspace) = self.workspace() else {
19471 return;
19472 };
19473 let fs = workspace.read(cx).app_state().fs.clone();
19474 let current_show = TabBarSettings::get_global(cx).show;
19475 update_settings_file(fs, cx, move |setting, _| {
19476 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19477 });
19478 }
19479
19480 pub fn toggle_indent_guides(
19481 &mut self,
19482 _: &ToggleIndentGuides,
19483 _: &mut Window,
19484 cx: &mut Context<Self>,
19485 ) {
19486 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19487 self.buffer
19488 .read(cx)
19489 .language_settings(cx)
19490 .indent_guides
19491 .enabled
19492 });
19493 self.show_indent_guides = Some(!currently_enabled);
19494 cx.notify();
19495 }
19496
19497 fn should_show_indent_guides(&self) -> Option<bool> {
19498 self.show_indent_guides
19499 }
19500
19501 pub fn toggle_line_numbers(
19502 &mut self,
19503 _: &ToggleLineNumbers,
19504 _: &mut Window,
19505 cx: &mut Context<Self>,
19506 ) {
19507 let mut editor_settings = EditorSettings::get_global(cx).clone();
19508 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19509 EditorSettings::override_global(editor_settings, cx);
19510 }
19511
19512 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19513 if let Some(show_line_numbers) = self.show_line_numbers {
19514 return show_line_numbers;
19515 }
19516 EditorSettings::get_global(cx).gutter.line_numbers
19517 }
19518
19519 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19520 self.use_relative_line_numbers
19521 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19522 }
19523
19524 pub fn toggle_relative_line_numbers(
19525 &mut self,
19526 _: &ToggleRelativeLineNumbers,
19527 _: &mut Window,
19528 cx: &mut Context<Self>,
19529 ) {
19530 let is_relative = self.should_use_relative_line_numbers(cx);
19531 self.set_relative_line_number(Some(!is_relative), cx)
19532 }
19533
19534 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19535 self.use_relative_line_numbers = is_relative;
19536 cx.notify();
19537 }
19538
19539 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19540 self.show_gutter = show_gutter;
19541 cx.notify();
19542 }
19543
19544 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19545 self.show_scrollbars = ScrollbarAxes {
19546 horizontal: show,
19547 vertical: show,
19548 };
19549 cx.notify();
19550 }
19551
19552 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19553 self.show_scrollbars.vertical = show;
19554 cx.notify();
19555 }
19556
19557 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19558 self.show_scrollbars.horizontal = show;
19559 cx.notify();
19560 }
19561
19562 pub fn set_minimap_visibility(
19563 &mut self,
19564 minimap_visibility: MinimapVisibility,
19565 window: &mut Window,
19566 cx: &mut Context<Self>,
19567 ) {
19568 if self.minimap_visibility != minimap_visibility {
19569 if minimap_visibility.visible() && self.minimap.is_none() {
19570 let minimap_settings = EditorSettings::get_global(cx).minimap;
19571 self.minimap =
19572 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19573 }
19574 self.minimap_visibility = minimap_visibility;
19575 cx.notify();
19576 }
19577 }
19578
19579 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19580 self.set_show_scrollbars(false, cx);
19581 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19582 }
19583
19584 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19585 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19586 }
19587
19588 /// Normally the text in full mode and auto height editors is padded on the
19589 /// left side by roughly half a character width for improved hit testing.
19590 ///
19591 /// Use this method to disable this for cases where this is not wanted (e.g.
19592 /// if you want to align the editor text with some other text above or below)
19593 /// or if you want to add this padding to single-line editors.
19594 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19595 self.offset_content = offset_content;
19596 cx.notify();
19597 }
19598
19599 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19600 self.show_line_numbers = Some(show_line_numbers);
19601 cx.notify();
19602 }
19603
19604 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19605 self.disable_expand_excerpt_buttons = true;
19606 cx.notify();
19607 }
19608
19609 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19610 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19611 cx.notify();
19612 }
19613
19614 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19615 self.show_code_actions = Some(show_code_actions);
19616 cx.notify();
19617 }
19618
19619 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19620 self.show_runnables = Some(show_runnables);
19621 cx.notify();
19622 }
19623
19624 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19625 self.show_breakpoints = Some(show_breakpoints);
19626 cx.notify();
19627 }
19628
19629 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19630 if self.display_map.read(cx).masked != masked {
19631 self.display_map.update(cx, |map, _| map.masked = masked);
19632 }
19633 cx.notify()
19634 }
19635
19636 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19637 self.show_wrap_guides = Some(show_wrap_guides);
19638 cx.notify();
19639 }
19640
19641 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19642 self.show_indent_guides = Some(show_indent_guides);
19643 cx.notify();
19644 }
19645
19646 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19647 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19648 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19649 && let Some(dir) = file.abs_path(cx).parent()
19650 {
19651 return Some(dir.to_owned());
19652 }
19653 }
19654
19655 None
19656 }
19657
19658 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19659 self.active_excerpt(cx)?
19660 .1
19661 .read(cx)
19662 .file()
19663 .and_then(|f| f.as_local())
19664 }
19665
19666 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19667 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19668 let buffer = buffer.read(cx);
19669 if let Some(project_path) = buffer.project_path(cx) {
19670 let project = self.project()?.read(cx);
19671 project.absolute_path(&project_path, cx)
19672 } else {
19673 buffer
19674 .file()
19675 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19676 }
19677 })
19678 }
19679
19680 pub fn reveal_in_finder(
19681 &mut self,
19682 _: &RevealInFileManager,
19683 _window: &mut Window,
19684 cx: &mut Context<Self>,
19685 ) {
19686 if let Some(target) = self.target_file(cx) {
19687 cx.reveal_path(&target.abs_path(cx));
19688 }
19689 }
19690
19691 pub fn copy_path(
19692 &mut self,
19693 _: &zed_actions::workspace::CopyPath,
19694 _window: &mut Window,
19695 cx: &mut Context<Self>,
19696 ) {
19697 if let Some(path) = self.target_file_abs_path(cx)
19698 && let Some(path) = path.to_str()
19699 {
19700 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19701 } else {
19702 cx.propagate();
19703 }
19704 }
19705
19706 pub fn copy_relative_path(
19707 &mut self,
19708 _: &zed_actions::workspace::CopyRelativePath,
19709 _window: &mut Window,
19710 cx: &mut Context<Self>,
19711 ) {
19712 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19713 let project = self.project()?.read(cx);
19714 let path = buffer.read(cx).file()?.path();
19715 let path = path.display(project.path_style(cx));
19716 Some(path)
19717 }) {
19718 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19719 } else {
19720 cx.propagate();
19721 }
19722 }
19723
19724 /// Returns the project path for the editor's buffer, if any buffer is
19725 /// opened in the editor.
19726 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19727 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19728 buffer.read(cx).project_path(cx)
19729 } else {
19730 None
19731 }
19732 }
19733
19734 // Returns true if the editor handled a go-to-line request
19735 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19736 maybe!({
19737 let breakpoint_store = self.breakpoint_store.as_ref()?;
19738
19739 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19740 else {
19741 self.clear_row_highlights::<ActiveDebugLine>();
19742 return None;
19743 };
19744
19745 let position = active_stack_frame.position;
19746 let buffer_id = position.buffer_id?;
19747 let snapshot = self
19748 .project
19749 .as_ref()?
19750 .read(cx)
19751 .buffer_for_id(buffer_id, cx)?
19752 .read(cx)
19753 .snapshot();
19754
19755 let mut handled = false;
19756 for (id, ExcerptRange { context, .. }) in
19757 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19758 {
19759 if context.start.cmp(&position, &snapshot).is_ge()
19760 || context.end.cmp(&position, &snapshot).is_lt()
19761 {
19762 continue;
19763 }
19764 let snapshot = self.buffer.read(cx).snapshot(cx);
19765 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19766
19767 handled = true;
19768 self.clear_row_highlights::<ActiveDebugLine>();
19769
19770 self.go_to_line::<ActiveDebugLine>(
19771 multibuffer_anchor,
19772 Some(cx.theme().colors().editor_debugger_active_line_background),
19773 window,
19774 cx,
19775 );
19776
19777 cx.notify();
19778 }
19779
19780 handled.then_some(())
19781 })
19782 .is_some()
19783 }
19784
19785 pub fn copy_file_name_without_extension(
19786 &mut self,
19787 _: &CopyFileNameWithoutExtension,
19788 _: &mut Window,
19789 cx: &mut Context<Self>,
19790 ) {
19791 if let Some(file) = self.target_file(cx)
19792 && let Some(file_stem) = file.path().file_stem()
19793 {
19794 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19795 }
19796 }
19797
19798 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19799 if let Some(file) = self.target_file(cx)
19800 && let Some(name) = file.path().file_name()
19801 {
19802 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19803 }
19804 }
19805
19806 pub fn toggle_git_blame(
19807 &mut self,
19808 _: &::git::Blame,
19809 window: &mut Window,
19810 cx: &mut Context<Self>,
19811 ) {
19812 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19813
19814 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19815 self.start_git_blame(true, window, cx);
19816 }
19817
19818 cx.notify();
19819 }
19820
19821 pub fn toggle_git_blame_inline(
19822 &mut self,
19823 _: &ToggleGitBlameInline,
19824 window: &mut Window,
19825 cx: &mut Context<Self>,
19826 ) {
19827 self.toggle_git_blame_inline_internal(true, window, cx);
19828 cx.notify();
19829 }
19830
19831 pub fn open_git_blame_commit(
19832 &mut self,
19833 _: &OpenGitBlameCommit,
19834 window: &mut Window,
19835 cx: &mut Context<Self>,
19836 ) {
19837 self.open_git_blame_commit_internal(window, cx);
19838 }
19839
19840 fn open_git_blame_commit_internal(
19841 &mut self,
19842 window: &mut Window,
19843 cx: &mut Context<Self>,
19844 ) -> Option<()> {
19845 let blame = self.blame.as_ref()?;
19846 let snapshot = self.snapshot(window, cx);
19847 let cursor = self
19848 .selections
19849 .newest::<Point>(&snapshot.display_snapshot)
19850 .head();
19851 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
19852 let (_, blame_entry) = blame
19853 .update(cx, |blame, cx| {
19854 blame
19855 .blame_for_rows(
19856 &[RowInfo {
19857 buffer_id: Some(buffer.remote_id()),
19858 buffer_row: Some(point.row),
19859 ..Default::default()
19860 }],
19861 cx,
19862 )
19863 .next()
19864 })
19865 .flatten()?;
19866 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19867 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19868 let workspace = self.workspace()?.downgrade();
19869 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19870 None
19871 }
19872
19873 pub fn git_blame_inline_enabled(&self) -> bool {
19874 self.git_blame_inline_enabled
19875 }
19876
19877 pub fn toggle_selection_menu(
19878 &mut self,
19879 _: &ToggleSelectionMenu,
19880 _: &mut Window,
19881 cx: &mut Context<Self>,
19882 ) {
19883 self.show_selection_menu = self
19884 .show_selection_menu
19885 .map(|show_selections_menu| !show_selections_menu)
19886 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19887
19888 cx.notify();
19889 }
19890
19891 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19892 self.show_selection_menu
19893 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19894 }
19895
19896 fn start_git_blame(
19897 &mut self,
19898 user_triggered: bool,
19899 window: &mut Window,
19900 cx: &mut Context<Self>,
19901 ) {
19902 if let Some(project) = self.project() {
19903 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19904 && buffer.read(cx).file().is_none()
19905 {
19906 return;
19907 }
19908
19909 let focused = self.focus_handle(cx).contains_focused(window, cx);
19910
19911 let project = project.clone();
19912 let blame = cx
19913 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19914 self.blame_subscription =
19915 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19916 self.blame = Some(blame);
19917 }
19918 }
19919
19920 fn toggle_git_blame_inline_internal(
19921 &mut self,
19922 user_triggered: bool,
19923 window: &mut Window,
19924 cx: &mut Context<Self>,
19925 ) {
19926 if self.git_blame_inline_enabled {
19927 self.git_blame_inline_enabled = false;
19928 self.show_git_blame_inline = false;
19929 self.show_git_blame_inline_delay_task.take();
19930 } else {
19931 self.git_blame_inline_enabled = true;
19932 self.start_git_blame_inline(user_triggered, window, cx);
19933 }
19934
19935 cx.notify();
19936 }
19937
19938 fn start_git_blame_inline(
19939 &mut self,
19940 user_triggered: bool,
19941 window: &mut Window,
19942 cx: &mut Context<Self>,
19943 ) {
19944 self.start_git_blame(user_triggered, window, cx);
19945
19946 if ProjectSettings::get_global(cx)
19947 .git
19948 .inline_blame_delay()
19949 .is_some()
19950 {
19951 self.start_inline_blame_timer(window, cx);
19952 } else {
19953 self.show_git_blame_inline = true
19954 }
19955 }
19956
19957 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19958 self.blame.as_ref()
19959 }
19960
19961 pub fn show_git_blame_gutter(&self) -> bool {
19962 self.show_git_blame_gutter
19963 }
19964
19965 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19966 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19967 }
19968
19969 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19970 self.show_git_blame_inline
19971 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19972 && !self.newest_selection_head_on_empty_line(cx)
19973 && self.has_blame_entries(cx)
19974 }
19975
19976 fn has_blame_entries(&self, cx: &App) -> bool {
19977 self.blame()
19978 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19979 }
19980
19981 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19982 let cursor_anchor = self.selections.newest_anchor().head();
19983
19984 let snapshot = self.buffer.read(cx).snapshot(cx);
19985 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19986
19987 snapshot.line_len(buffer_row) == 0
19988 }
19989
19990 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19991 let buffer_and_selection = maybe!({
19992 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19993 let selection_range = selection.range();
19994
19995 let multi_buffer = self.buffer().read(cx);
19996 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19997 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19998
19999 let (buffer, range, _) = if selection.reversed {
20000 buffer_ranges.first()
20001 } else {
20002 buffer_ranges.last()
20003 }?;
20004
20005 let selection = text::ToPoint::to_point(&range.start, buffer).row
20006 ..text::ToPoint::to_point(&range.end, buffer).row;
20007 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
20008 });
20009
20010 let Some((buffer, selection)) = buffer_and_selection else {
20011 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20012 };
20013
20014 let Some(project) = self.project() else {
20015 return Task::ready(Err(anyhow!("editor does not have project")));
20016 };
20017
20018 project.update(cx, |project, cx| {
20019 project.get_permalink_to_line(&buffer, selection, cx)
20020 })
20021 }
20022
20023 pub fn copy_permalink_to_line(
20024 &mut self,
20025 _: &CopyPermalinkToLine,
20026 window: &mut Window,
20027 cx: &mut Context<Self>,
20028 ) {
20029 let permalink_task = self.get_permalink_to_line(cx);
20030 let workspace = self.workspace();
20031
20032 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20033 Ok(permalink) => {
20034 cx.update(|_, cx| {
20035 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20036 })
20037 .ok();
20038 }
20039 Err(err) => {
20040 let message = format!("Failed to copy permalink: {err}");
20041
20042 anyhow::Result::<()>::Err(err).log_err();
20043
20044 if let Some(workspace) = workspace {
20045 workspace
20046 .update_in(cx, |workspace, _, cx| {
20047 struct CopyPermalinkToLine;
20048
20049 workspace.show_toast(
20050 Toast::new(
20051 NotificationId::unique::<CopyPermalinkToLine>(),
20052 message,
20053 ),
20054 cx,
20055 )
20056 })
20057 .ok();
20058 }
20059 }
20060 })
20061 .detach();
20062 }
20063
20064 pub fn copy_file_location(
20065 &mut self,
20066 _: &CopyFileLocation,
20067 _: &mut Window,
20068 cx: &mut Context<Self>,
20069 ) {
20070 let selection = self
20071 .selections
20072 .newest::<Point>(&self.display_snapshot(cx))
20073 .start
20074 .row
20075 + 1;
20076 if let Some(file) = self.target_file(cx) {
20077 let path = file.path().display(file.path_style(cx));
20078 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
20079 }
20080 }
20081
20082 pub fn open_permalink_to_line(
20083 &mut self,
20084 _: &OpenPermalinkToLine,
20085 window: &mut Window,
20086 cx: &mut Context<Self>,
20087 ) {
20088 let permalink_task = self.get_permalink_to_line(cx);
20089 let workspace = self.workspace();
20090
20091 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20092 Ok(permalink) => {
20093 cx.update(|_, cx| {
20094 cx.open_url(permalink.as_ref());
20095 })
20096 .ok();
20097 }
20098 Err(err) => {
20099 let message = format!("Failed to open permalink: {err}");
20100
20101 anyhow::Result::<()>::Err(err).log_err();
20102
20103 if let Some(workspace) = workspace {
20104 workspace
20105 .update(cx, |workspace, cx| {
20106 struct OpenPermalinkToLine;
20107
20108 workspace.show_toast(
20109 Toast::new(
20110 NotificationId::unique::<OpenPermalinkToLine>(),
20111 message,
20112 ),
20113 cx,
20114 )
20115 })
20116 .ok();
20117 }
20118 }
20119 })
20120 .detach();
20121 }
20122
20123 pub fn insert_uuid_v4(
20124 &mut self,
20125 _: &InsertUuidV4,
20126 window: &mut Window,
20127 cx: &mut Context<Self>,
20128 ) {
20129 self.insert_uuid(UuidVersion::V4, window, cx);
20130 }
20131
20132 pub fn insert_uuid_v7(
20133 &mut self,
20134 _: &InsertUuidV7,
20135 window: &mut Window,
20136 cx: &mut Context<Self>,
20137 ) {
20138 self.insert_uuid(UuidVersion::V7, window, cx);
20139 }
20140
20141 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20142 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20143 self.transact(window, cx, |this, window, cx| {
20144 let edits = this
20145 .selections
20146 .all::<Point>(&this.display_snapshot(cx))
20147 .into_iter()
20148 .map(|selection| {
20149 let uuid = match version {
20150 UuidVersion::V4 => uuid::Uuid::new_v4(),
20151 UuidVersion::V7 => uuid::Uuid::now_v7(),
20152 };
20153
20154 (selection.range(), uuid.to_string())
20155 });
20156 this.edit(edits, cx);
20157 this.refresh_edit_prediction(true, false, window, cx);
20158 });
20159 }
20160
20161 pub fn open_selections_in_multibuffer(
20162 &mut self,
20163 _: &OpenSelectionsInMultibuffer,
20164 window: &mut Window,
20165 cx: &mut Context<Self>,
20166 ) {
20167 let multibuffer = self.buffer.read(cx);
20168
20169 let Some(buffer) = multibuffer.as_singleton() else {
20170 return;
20171 };
20172
20173 let Some(workspace) = self.workspace() else {
20174 return;
20175 };
20176
20177 let title = multibuffer.title(cx).to_string();
20178
20179 let locations = self
20180 .selections
20181 .all_anchors(cx)
20182 .iter()
20183 .map(|selection| {
20184 (
20185 buffer.clone(),
20186 (selection.start.text_anchor..selection.end.text_anchor)
20187 .to_point(buffer.read(cx)),
20188 )
20189 })
20190 .into_group_map();
20191
20192 cx.spawn_in(window, async move |_, cx| {
20193 workspace.update_in(cx, |workspace, window, cx| {
20194 Self::open_locations_in_multibuffer(
20195 workspace,
20196 locations,
20197 format!("Selections for '{title}'"),
20198 false,
20199 MultibufferSelectionMode::All,
20200 window,
20201 cx,
20202 );
20203 })
20204 })
20205 .detach();
20206 }
20207
20208 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20209 /// last highlight added will be used.
20210 ///
20211 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20212 pub fn highlight_rows<T: 'static>(
20213 &mut self,
20214 range: Range<Anchor>,
20215 color: Hsla,
20216 options: RowHighlightOptions,
20217 cx: &mut Context<Self>,
20218 ) {
20219 let snapshot = self.buffer().read(cx).snapshot(cx);
20220 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20221 let ix = row_highlights.binary_search_by(|highlight| {
20222 Ordering::Equal
20223 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20224 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20225 });
20226
20227 if let Err(mut ix) = ix {
20228 let index = post_inc(&mut self.highlight_order);
20229
20230 // If this range intersects with the preceding highlight, then merge it with
20231 // the preceding highlight. Otherwise insert a new highlight.
20232 let mut merged = false;
20233 if ix > 0 {
20234 let prev_highlight = &mut row_highlights[ix - 1];
20235 if prev_highlight
20236 .range
20237 .end
20238 .cmp(&range.start, &snapshot)
20239 .is_ge()
20240 {
20241 ix -= 1;
20242 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20243 prev_highlight.range.end = range.end;
20244 }
20245 merged = true;
20246 prev_highlight.index = index;
20247 prev_highlight.color = color;
20248 prev_highlight.options = options;
20249 }
20250 }
20251
20252 if !merged {
20253 row_highlights.insert(
20254 ix,
20255 RowHighlight {
20256 range,
20257 index,
20258 color,
20259 options,
20260 type_id: TypeId::of::<T>(),
20261 },
20262 );
20263 }
20264
20265 // If any of the following highlights intersect with this one, merge them.
20266 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20267 let highlight = &row_highlights[ix];
20268 if next_highlight
20269 .range
20270 .start
20271 .cmp(&highlight.range.end, &snapshot)
20272 .is_le()
20273 {
20274 if next_highlight
20275 .range
20276 .end
20277 .cmp(&highlight.range.end, &snapshot)
20278 .is_gt()
20279 {
20280 row_highlights[ix].range.end = next_highlight.range.end;
20281 }
20282 row_highlights.remove(ix + 1);
20283 } else {
20284 break;
20285 }
20286 }
20287 }
20288 }
20289
20290 /// Remove any highlighted row ranges of the given type that intersect the
20291 /// given ranges.
20292 pub fn remove_highlighted_rows<T: 'static>(
20293 &mut self,
20294 ranges_to_remove: Vec<Range<Anchor>>,
20295 cx: &mut Context<Self>,
20296 ) {
20297 let snapshot = self.buffer().read(cx).snapshot(cx);
20298 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20299 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20300 row_highlights.retain(|highlight| {
20301 while let Some(range_to_remove) = ranges_to_remove.peek() {
20302 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20303 Ordering::Less | Ordering::Equal => {
20304 ranges_to_remove.next();
20305 }
20306 Ordering::Greater => {
20307 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20308 Ordering::Less | Ordering::Equal => {
20309 return false;
20310 }
20311 Ordering::Greater => break,
20312 }
20313 }
20314 }
20315 }
20316
20317 true
20318 })
20319 }
20320
20321 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20322 pub fn clear_row_highlights<T: 'static>(&mut self) {
20323 self.highlighted_rows.remove(&TypeId::of::<T>());
20324 }
20325
20326 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20327 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20328 self.highlighted_rows
20329 .get(&TypeId::of::<T>())
20330 .map_or(&[] as &[_], |vec| vec.as_slice())
20331 .iter()
20332 .map(|highlight| (highlight.range.clone(), highlight.color))
20333 }
20334
20335 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20336 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20337 /// Allows to ignore certain kinds of highlights.
20338 pub fn highlighted_display_rows(
20339 &self,
20340 window: &mut Window,
20341 cx: &mut App,
20342 ) -> BTreeMap<DisplayRow, LineHighlight> {
20343 let snapshot = self.snapshot(window, cx);
20344 let mut used_highlight_orders = HashMap::default();
20345 self.highlighted_rows
20346 .iter()
20347 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20348 .fold(
20349 BTreeMap::<DisplayRow, LineHighlight>::new(),
20350 |mut unique_rows, highlight| {
20351 let start = highlight.range.start.to_display_point(&snapshot);
20352 let end = highlight.range.end.to_display_point(&snapshot);
20353 let start_row = start.row().0;
20354 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20355 && end.column() == 0
20356 {
20357 end.row().0.saturating_sub(1)
20358 } else {
20359 end.row().0
20360 };
20361 for row in start_row..=end_row {
20362 let used_index =
20363 used_highlight_orders.entry(row).or_insert(highlight.index);
20364 if highlight.index >= *used_index {
20365 *used_index = highlight.index;
20366 unique_rows.insert(
20367 DisplayRow(row),
20368 LineHighlight {
20369 include_gutter: highlight.options.include_gutter,
20370 border: None,
20371 background: highlight.color.into(),
20372 type_id: Some(highlight.type_id),
20373 },
20374 );
20375 }
20376 }
20377 unique_rows
20378 },
20379 )
20380 }
20381
20382 pub fn highlighted_display_row_for_autoscroll(
20383 &self,
20384 snapshot: &DisplaySnapshot,
20385 ) -> Option<DisplayRow> {
20386 self.highlighted_rows
20387 .values()
20388 .flat_map(|highlighted_rows| highlighted_rows.iter())
20389 .filter_map(|highlight| {
20390 if highlight.options.autoscroll {
20391 Some(highlight.range.start.to_display_point(snapshot).row())
20392 } else {
20393 None
20394 }
20395 })
20396 .min()
20397 }
20398
20399 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20400 self.highlight_background::<SearchWithinRange>(
20401 ranges,
20402 |colors| colors.colors().editor_document_highlight_read_background,
20403 cx,
20404 )
20405 }
20406
20407 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20408 self.breadcrumb_header = Some(new_header);
20409 }
20410
20411 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20412 self.clear_background_highlights::<SearchWithinRange>(cx);
20413 }
20414
20415 pub fn highlight_background<T: 'static>(
20416 &mut self,
20417 ranges: &[Range<Anchor>],
20418 color_fetcher: fn(&Theme) -> Hsla,
20419 cx: &mut Context<Self>,
20420 ) {
20421 self.background_highlights.insert(
20422 HighlightKey::Type(TypeId::of::<T>()),
20423 (color_fetcher, Arc::from(ranges)),
20424 );
20425 self.scrollbar_marker_state.dirty = true;
20426 cx.notify();
20427 }
20428
20429 pub fn highlight_background_key<T: 'static>(
20430 &mut self,
20431 key: usize,
20432 ranges: &[Range<Anchor>],
20433 color_fetcher: fn(&Theme) -> Hsla,
20434 cx: &mut Context<Self>,
20435 ) {
20436 self.background_highlights.insert(
20437 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20438 (color_fetcher, Arc::from(ranges)),
20439 );
20440 self.scrollbar_marker_state.dirty = true;
20441 cx.notify();
20442 }
20443
20444 pub fn clear_background_highlights<T: 'static>(
20445 &mut self,
20446 cx: &mut Context<Self>,
20447 ) -> Option<BackgroundHighlight> {
20448 let text_highlights = self
20449 .background_highlights
20450 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20451 if !text_highlights.1.is_empty() {
20452 self.scrollbar_marker_state.dirty = true;
20453 cx.notify();
20454 }
20455 Some(text_highlights)
20456 }
20457
20458 pub fn highlight_gutter<T: 'static>(
20459 &mut self,
20460 ranges: impl Into<Vec<Range<Anchor>>>,
20461 color_fetcher: fn(&App) -> Hsla,
20462 cx: &mut Context<Self>,
20463 ) {
20464 self.gutter_highlights
20465 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20466 cx.notify();
20467 }
20468
20469 pub fn clear_gutter_highlights<T: 'static>(
20470 &mut self,
20471 cx: &mut Context<Self>,
20472 ) -> Option<GutterHighlight> {
20473 cx.notify();
20474 self.gutter_highlights.remove(&TypeId::of::<T>())
20475 }
20476
20477 pub fn insert_gutter_highlight<T: 'static>(
20478 &mut self,
20479 range: Range<Anchor>,
20480 color_fetcher: fn(&App) -> Hsla,
20481 cx: &mut Context<Self>,
20482 ) {
20483 let snapshot = self.buffer().read(cx).snapshot(cx);
20484 let mut highlights = self
20485 .gutter_highlights
20486 .remove(&TypeId::of::<T>())
20487 .map(|(_, highlights)| highlights)
20488 .unwrap_or_default();
20489 let ix = highlights.binary_search_by(|highlight| {
20490 Ordering::Equal
20491 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20492 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20493 });
20494 if let Err(ix) = ix {
20495 highlights.insert(ix, range);
20496 }
20497 self.gutter_highlights
20498 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20499 }
20500
20501 pub fn remove_gutter_highlights<T: 'static>(
20502 &mut self,
20503 ranges_to_remove: Vec<Range<Anchor>>,
20504 cx: &mut Context<Self>,
20505 ) {
20506 let snapshot = self.buffer().read(cx).snapshot(cx);
20507 let Some((color_fetcher, mut gutter_highlights)) =
20508 self.gutter_highlights.remove(&TypeId::of::<T>())
20509 else {
20510 return;
20511 };
20512 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20513 gutter_highlights.retain(|highlight| {
20514 while let Some(range_to_remove) = ranges_to_remove.peek() {
20515 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20516 Ordering::Less | Ordering::Equal => {
20517 ranges_to_remove.next();
20518 }
20519 Ordering::Greater => {
20520 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20521 Ordering::Less | Ordering::Equal => {
20522 return false;
20523 }
20524 Ordering::Greater => break,
20525 }
20526 }
20527 }
20528 }
20529
20530 true
20531 });
20532 self.gutter_highlights
20533 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20534 }
20535
20536 #[cfg(feature = "test-support")]
20537 pub fn all_text_highlights(
20538 &self,
20539 window: &mut Window,
20540 cx: &mut Context<Self>,
20541 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20542 let snapshot = self.snapshot(window, cx);
20543 self.display_map.update(cx, |display_map, _| {
20544 display_map
20545 .all_text_highlights()
20546 .map(|highlight| {
20547 let (style, ranges) = highlight.as_ref();
20548 (
20549 *style,
20550 ranges
20551 .iter()
20552 .map(|range| range.clone().to_display_points(&snapshot))
20553 .collect(),
20554 )
20555 })
20556 .collect()
20557 })
20558 }
20559
20560 #[cfg(feature = "test-support")]
20561 pub fn all_text_background_highlights(
20562 &self,
20563 window: &mut Window,
20564 cx: &mut Context<Self>,
20565 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20566 let snapshot = self.snapshot(window, cx);
20567 let buffer = &snapshot.buffer_snapshot();
20568 let start = buffer.anchor_before(0);
20569 let end = buffer.anchor_after(buffer.len());
20570 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20571 }
20572
20573 #[cfg(any(test, feature = "test-support"))]
20574 pub fn sorted_background_highlights_in_range(
20575 &self,
20576 search_range: Range<Anchor>,
20577 display_snapshot: &DisplaySnapshot,
20578 theme: &Theme,
20579 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20580 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20581 res.sort_by(|a, b| {
20582 a.0.start
20583 .cmp(&b.0.start)
20584 .then_with(|| a.0.end.cmp(&b.0.end))
20585 .then_with(|| a.1.cmp(&b.1))
20586 });
20587 res
20588 }
20589
20590 #[cfg(feature = "test-support")]
20591 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20592 let snapshot = self.buffer().read(cx).snapshot(cx);
20593
20594 let highlights = self
20595 .background_highlights
20596 .get(&HighlightKey::Type(TypeId::of::<
20597 items::BufferSearchHighlights,
20598 >()));
20599
20600 if let Some((_color, ranges)) = highlights {
20601 ranges
20602 .iter()
20603 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20604 .collect_vec()
20605 } else {
20606 vec![]
20607 }
20608 }
20609
20610 fn document_highlights_for_position<'a>(
20611 &'a self,
20612 position: Anchor,
20613 buffer: &'a MultiBufferSnapshot,
20614 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20615 let read_highlights = self
20616 .background_highlights
20617 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20618 .map(|h| &h.1);
20619 let write_highlights = self
20620 .background_highlights
20621 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20622 .map(|h| &h.1);
20623 let left_position = position.bias_left(buffer);
20624 let right_position = position.bias_right(buffer);
20625 read_highlights
20626 .into_iter()
20627 .chain(write_highlights)
20628 .flat_map(move |ranges| {
20629 let start_ix = match ranges.binary_search_by(|probe| {
20630 let cmp = probe.end.cmp(&left_position, buffer);
20631 if cmp.is_ge() {
20632 Ordering::Greater
20633 } else {
20634 Ordering::Less
20635 }
20636 }) {
20637 Ok(i) | Err(i) => i,
20638 };
20639
20640 ranges[start_ix..]
20641 .iter()
20642 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20643 })
20644 }
20645
20646 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20647 self.background_highlights
20648 .get(&HighlightKey::Type(TypeId::of::<T>()))
20649 .is_some_and(|(_, highlights)| !highlights.is_empty())
20650 }
20651
20652 /// Returns all background highlights for a given range.
20653 ///
20654 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20655 pub fn background_highlights_in_range(
20656 &self,
20657 search_range: Range<Anchor>,
20658 display_snapshot: &DisplaySnapshot,
20659 theme: &Theme,
20660 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20661 let mut results = Vec::new();
20662 for (color_fetcher, ranges) in self.background_highlights.values() {
20663 let color = color_fetcher(theme);
20664 let start_ix = match ranges.binary_search_by(|probe| {
20665 let cmp = probe
20666 .end
20667 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20668 if cmp.is_gt() {
20669 Ordering::Greater
20670 } else {
20671 Ordering::Less
20672 }
20673 }) {
20674 Ok(i) | Err(i) => i,
20675 };
20676 for range in &ranges[start_ix..] {
20677 if range
20678 .start
20679 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20680 .is_ge()
20681 {
20682 break;
20683 }
20684
20685 let start = range.start.to_display_point(display_snapshot);
20686 let end = range.end.to_display_point(display_snapshot);
20687 results.push((start..end, color))
20688 }
20689 }
20690 results
20691 }
20692
20693 pub fn gutter_highlights_in_range(
20694 &self,
20695 search_range: Range<Anchor>,
20696 display_snapshot: &DisplaySnapshot,
20697 cx: &App,
20698 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20699 let mut results = Vec::new();
20700 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20701 let color = color_fetcher(cx);
20702 let start_ix = match ranges.binary_search_by(|probe| {
20703 let cmp = probe
20704 .end
20705 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20706 if cmp.is_gt() {
20707 Ordering::Greater
20708 } else {
20709 Ordering::Less
20710 }
20711 }) {
20712 Ok(i) | Err(i) => i,
20713 };
20714 for range in &ranges[start_ix..] {
20715 if range
20716 .start
20717 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20718 .is_ge()
20719 {
20720 break;
20721 }
20722
20723 let start = range.start.to_display_point(display_snapshot);
20724 let end = range.end.to_display_point(display_snapshot);
20725 results.push((start..end, color))
20726 }
20727 }
20728 results
20729 }
20730
20731 /// Get the text ranges corresponding to the redaction query
20732 pub fn redacted_ranges(
20733 &self,
20734 search_range: Range<Anchor>,
20735 display_snapshot: &DisplaySnapshot,
20736 cx: &App,
20737 ) -> Vec<Range<DisplayPoint>> {
20738 display_snapshot
20739 .buffer_snapshot()
20740 .redacted_ranges(search_range, |file| {
20741 if let Some(file) = file {
20742 file.is_private()
20743 && EditorSettings::get(
20744 Some(SettingsLocation {
20745 worktree_id: file.worktree_id(cx),
20746 path: file.path().as_ref(),
20747 }),
20748 cx,
20749 )
20750 .redact_private_values
20751 } else {
20752 false
20753 }
20754 })
20755 .map(|range| {
20756 range.start.to_display_point(display_snapshot)
20757 ..range.end.to_display_point(display_snapshot)
20758 })
20759 .collect()
20760 }
20761
20762 pub fn highlight_text_key<T: 'static>(
20763 &mut self,
20764 key: usize,
20765 ranges: Vec<Range<Anchor>>,
20766 style: HighlightStyle,
20767 cx: &mut Context<Self>,
20768 ) {
20769 self.display_map.update(cx, |map, _| {
20770 map.highlight_text(
20771 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20772 ranges,
20773 style,
20774 );
20775 });
20776 cx.notify();
20777 }
20778
20779 pub fn highlight_text<T: 'static>(
20780 &mut self,
20781 ranges: Vec<Range<Anchor>>,
20782 style: HighlightStyle,
20783 cx: &mut Context<Self>,
20784 ) {
20785 self.display_map.update(cx, |map, _| {
20786 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20787 });
20788 cx.notify();
20789 }
20790
20791 pub(crate) fn highlight_inlays<T: 'static>(
20792 &mut self,
20793 highlights: Vec<InlayHighlight>,
20794 style: HighlightStyle,
20795 cx: &mut Context<Self>,
20796 ) {
20797 self.display_map.update(cx, |map, _| {
20798 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20799 });
20800 cx.notify();
20801 }
20802
20803 pub fn text_highlights<'a, T: 'static>(
20804 &'a self,
20805 cx: &'a App,
20806 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20807 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20808 }
20809
20810 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20811 let cleared = self
20812 .display_map
20813 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20814 if cleared {
20815 cx.notify();
20816 }
20817 }
20818
20819 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20820 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20821 && self.focus_handle.is_focused(window)
20822 }
20823
20824 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20825 self.show_cursor_when_unfocused = is_enabled;
20826 cx.notify();
20827 }
20828
20829 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20830 cx.notify();
20831 }
20832
20833 fn on_debug_session_event(
20834 &mut self,
20835 _session: Entity<Session>,
20836 event: &SessionEvent,
20837 cx: &mut Context<Self>,
20838 ) {
20839 if let SessionEvent::InvalidateInlineValue = event {
20840 self.refresh_inline_values(cx);
20841 }
20842 }
20843
20844 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20845 let Some(project) = self.project.clone() else {
20846 return;
20847 };
20848
20849 if !self.inline_value_cache.enabled {
20850 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20851 self.splice_inlays(&inlays, Vec::new(), cx);
20852 return;
20853 }
20854
20855 let current_execution_position = self
20856 .highlighted_rows
20857 .get(&TypeId::of::<ActiveDebugLine>())
20858 .and_then(|lines| lines.last().map(|line| line.range.end));
20859
20860 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20861 let inline_values = editor
20862 .update(cx, |editor, cx| {
20863 let Some(current_execution_position) = current_execution_position else {
20864 return Some(Task::ready(Ok(Vec::new())));
20865 };
20866
20867 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20868 let snapshot = buffer.snapshot(cx);
20869
20870 let excerpt = snapshot.excerpt_containing(
20871 current_execution_position..current_execution_position,
20872 )?;
20873
20874 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20875 })?;
20876
20877 let range =
20878 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20879
20880 project.inline_values(buffer, range, cx)
20881 })
20882 .ok()
20883 .flatten()?
20884 .await
20885 .context("refreshing debugger inlays")
20886 .log_err()?;
20887
20888 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20889
20890 for (buffer_id, inline_value) in inline_values
20891 .into_iter()
20892 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20893 {
20894 buffer_inline_values
20895 .entry(buffer_id)
20896 .or_default()
20897 .push(inline_value);
20898 }
20899
20900 editor
20901 .update(cx, |editor, cx| {
20902 let snapshot = editor.buffer.read(cx).snapshot(cx);
20903 let mut new_inlays = Vec::default();
20904
20905 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20906 let buffer_id = buffer_snapshot.remote_id();
20907 buffer_inline_values
20908 .get(&buffer_id)
20909 .into_iter()
20910 .flatten()
20911 .for_each(|hint| {
20912 let inlay = Inlay::debugger(
20913 post_inc(&mut editor.next_inlay_id),
20914 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20915 hint.text(),
20916 );
20917 if !inlay.text().chars().contains(&'\n') {
20918 new_inlays.push(inlay);
20919 }
20920 });
20921 }
20922
20923 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20924 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20925
20926 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20927 })
20928 .ok()?;
20929 Some(())
20930 });
20931 }
20932
20933 fn on_buffer_event(
20934 &mut self,
20935 multibuffer: &Entity<MultiBuffer>,
20936 event: &multi_buffer::Event,
20937 window: &mut Window,
20938 cx: &mut Context<Self>,
20939 ) {
20940 match event {
20941 multi_buffer::Event::Edited { edited_buffer } => {
20942 self.scrollbar_marker_state.dirty = true;
20943 self.active_indent_guides_state.dirty = true;
20944 self.refresh_active_diagnostics(cx);
20945 self.refresh_code_actions(window, cx);
20946 self.refresh_selected_text_highlights(true, window, cx);
20947 self.refresh_single_line_folds(window, cx);
20948 refresh_matching_bracket_highlights(self, cx);
20949 if self.has_active_edit_prediction() {
20950 self.update_visible_edit_prediction(window, cx);
20951 }
20952
20953 if let Some(edited_buffer) = edited_buffer {
20954 if edited_buffer.read(cx).file().is_none() {
20955 cx.emit(EditorEvent::TitleChanged);
20956 }
20957
20958 let buffer_id = edited_buffer.read(cx).remote_id();
20959 if let Some(project) = self.project.clone() {
20960 self.register_buffer(buffer_id, cx);
20961 self.update_lsp_data(Some(buffer_id), window, cx);
20962 #[allow(clippy::mutable_key_type)]
20963 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20964 multibuffer
20965 .all_buffers()
20966 .into_iter()
20967 .filter_map(|buffer| {
20968 buffer.update(cx, |buffer, cx| {
20969 let language = buffer.language()?;
20970 let should_discard = project.update(cx, |project, cx| {
20971 project.is_local()
20972 && !project.has_language_servers_for(buffer, cx)
20973 });
20974 should_discard.not().then_some(language.clone())
20975 })
20976 })
20977 .collect::<HashSet<_>>()
20978 });
20979 if !languages_affected.is_empty() {
20980 self.refresh_inlay_hints(
20981 InlayHintRefreshReason::BufferEdited(languages_affected),
20982 cx,
20983 );
20984 }
20985 }
20986 }
20987
20988 cx.emit(EditorEvent::BufferEdited);
20989 cx.emit(SearchEvent::MatchesInvalidated);
20990
20991 let Some(project) = &self.project else { return };
20992 let (telemetry, is_via_ssh) = {
20993 let project = project.read(cx);
20994 let telemetry = project.client().telemetry().clone();
20995 let is_via_ssh = project.is_via_remote_server();
20996 (telemetry, is_via_ssh)
20997 };
20998 telemetry.log_edit_event("editor", is_via_ssh);
20999 }
21000 multi_buffer::Event::ExcerptsAdded {
21001 buffer,
21002 predecessor,
21003 excerpts,
21004 } => {
21005 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21006 let buffer_id = buffer.read(cx).remote_id();
21007 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21008 && let Some(project) = &self.project
21009 {
21010 update_uncommitted_diff_for_buffer(
21011 cx.entity(),
21012 project,
21013 [buffer.clone()],
21014 self.buffer.clone(),
21015 cx,
21016 )
21017 .detach();
21018 }
21019 self.update_lsp_data(Some(buffer_id), window, cx);
21020 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21021 cx.emit(EditorEvent::ExcerptsAdded {
21022 buffer: buffer.clone(),
21023 predecessor: *predecessor,
21024 excerpts: excerpts.clone(),
21025 });
21026 }
21027 multi_buffer::Event::ExcerptsRemoved {
21028 ids,
21029 removed_buffer_ids,
21030 } => {
21031 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21032 for buffer_id in removed_buffer_ids {
21033 self.registered_buffers.remove(buffer_id);
21034 }
21035 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21036 cx.emit(EditorEvent::ExcerptsRemoved {
21037 ids: ids.clone(),
21038 removed_buffer_ids: removed_buffer_ids.clone(),
21039 });
21040 }
21041 multi_buffer::Event::ExcerptsEdited {
21042 excerpt_ids,
21043 buffer_ids,
21044 } => {
21045 self.display_map.update(cx, |map, cx| {
21046 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21047 });
21048 cx.emit(EditorEvent::ExcerptsEdited {
21049 ids: excerpt_ids.clone(),
21050 });
21051 }
21052 multi_buffer::Event::ExcerptsExpanded { ids } => {
21053 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21054 self.refresh_document_highlights(cx);
21055 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21056 }
21057 multi_buffer::Event::Reparsed(buffer_id) => {
21058 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21059 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21060
21061 cx.emit(EditorEvent::Reparsed(*buffer_id));
21062 }
21063 multi_buffer::Event::DiffHunksToggled => {
21064 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21065 }
21066 multi_buffer::Event::LanguageChanged(buffer_id) => {
21067 self.registered_buffers.remove(&buffer_id);
21068 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21069 cx.emit(EditorEvent::Reparsed(*buffer_id));
21070 cx.notify();
21071 }
21072 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21073 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21074 multi_buffer::Event::FileHandleChanged
21075 | multi_buffer::Event::Reloaded
21076 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21077 multi_buffer::Event::DiagnosticsUpdated => {
21078 self.update_diagnostics_state(window, cx);
21079 }
21080 _ => {}
21081 };
21082 }
21083
21084 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21085 if !self.diagnostics_enabled() {
21086 return;
21087 }
21088 self.refresh_active_diagnostics(cx);
21089 self.refresh_inline_diagnostics(true, window, cx);
21090 self.scrollbar_marker_state.dirty = true;
21091 cx.notify();
21092 }
21093
21094 pub fn start_temporary_diff_override(&mut self) {
21095 self.load_diff_task.take();
21096 self.temporary_diff_override = true;
21097 }
21098
21099 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21100 self.temporary_diff_override = false;
21101 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21102 self.buffer.update(cx, |buffer, cx| {
21103 buffer.set_all_diff_hunks_collapsed(cx);
21104 });
21105
21106 if let Some(project) = self.project.clone() {
21107 self.load_diff_task = Some(
21108 update_uncommitted_diff_for_buffer(
21109 cx.entity(),
21110 &project,
21111 self.buffer.read(cx).all_buffers(),
21112 self.buffer.clone(),
21113 cx,
21114 )
21115 .shared(),
21116 );
21117 }
21118 }
21119
21120 fn on_display_map_changed(
21121 &mut self,
21122 _: Entity<DisplayMap>,
21123 _: &mut Window,
21124 cx: &mut Context<Self>,
21125 ) {
21126 cx.notify();
21127 }
21128
21129 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21130 if self.diagnostics_enabled() {
21131 let new_severity = EditorSettings::get_global(cx)
21132 .diagnostics_max_severity
21133 .unwrap_or(DiagnosticSeverity::Hint);
21134 self.set_max_diagnostics_severity(new_severity, cx);
21135 }
21136 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21137 self.update_edit_prediction_settings(cx);
21138 self.refresh_edit_prediction(true, false, window, cx);
21139 self.refresh_inline_values(cx);
21140 self.refresh_inlay_hints(
21141 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21142 self.selections.newest_anchor().head(),
21143 &self.buffer.read(cx).snapshot(cx),
21144 cx,
21145 )),
21146 cx,
21147 );
21148
21149 let old_cursor_shape = self.cursor_shape;
21150 let old_show_breadcrumbs = self.show_breadcrumbs;
21151
21152 {
21153 let editor_settings = EditorSettings::get_global(cx);
21154 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21155 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21156 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21157 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21158 }
21159
21160 if old_cursor_shape != self.cursor_shape {
21161 cx.emit(EditorEvent::CursorShapeChanged);
21162 }
21163
21164 if old_show_breadcrumbs != self.show_breadcrumbs {
21165 cx.emit(EditorEvent::BreadcrumbsChanged);
21166 }
21167
21168 let project_settings = ProjectSettings::get_global(cx);
21169 self.serialize_dirty_buffers =
21170 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
21171
21172 if self.mode.is_full() {
21173 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21174 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21175 if self.show_inline_diagnostics != show_inline_diagnostics {
21176 self.show_inline_diagnostics = show_inline_diagnostics;
21177 self.refresh_inline_diagnostics(false, window, cx);
21178 }
21179
21180 if self.git_blame_inline_enabled != inline_blame_enabled {
21181 self.toggle_git_blame_inline_internal(false, window, cx);
21182 }
21183
21184 let minimap_settings = EditorSettings::get_global(cx).minimap;
21185 if self.minimap_visibility != MinimapVisibility::Disabled {
21186 if self.minimap_visibility.settings_visibility()
21187 != minimap_settings.minimap_enabled()
21188 {
21189 self.set_minimap_visibility(
21190 MinimapVisibility::for_mode(self.mode(), cx),
21191 window,
21192 cx,
21193 );
21194 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21195 minimap_entity.update(cx, |minimap_editor, cx| {
21196 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21197 })
21198 }
21199 }
21200 }
21201
21202 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21203 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21204 }) {
21205 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
21206 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21207 }
21208 self.refresh_colors_for_visible_range(None, window, cx);
21209 }
21210
21211 cx.notify();
21212 }
21213
21214 pub fn set_searchable(&mut self, searchable: bool) {
21215 self.searchable = searchable;
21216 }
21217
21218 pub fn searchable(&self) -> bool {
21219 self.searchable
21220 }
21221
21222 fn open_proposed_changes_editor(
21223 &mut self,
21224 _: &OpenProposedChangesEditor,
21225 window: &mut Window,
21226 cx: &mut Context<Self>,
21227 ) {
21228 let Some(workspace) = self.workspace() else {
21229 cx.propagate();
21230 return;
21231 };
21232
21233 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
21234 let multi_buffer = self.buffer.read(cx);
21235 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
21236 let mut new_selections_by_buffer = HashMap::default();
21237 for selection in selections {
21238 for (buffer, range, _) in
21239 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
21240 {
21241 let mut range = range.to_point(buffer);
21242 range.start.column = 0;
21243 range.end.column = buffer.line_len(range.end.row);
21244 new_selections_by_buffer
21245 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
21246 .or_insert(Vec::new())
21247 .push(range)
21248 }
21249 }
21250
21251 let proposed_changes_buffers = new_selections_by_buffer
21252 .into_iter()
21253 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
21254 .collect::<Vec<_>>();
21255 let proposed_changes_editor = cx.new(|cx| {
21256 ProposedChangesEditor::new(
21257 "Proposed changes",
21258 proposed_changes_buffers,
21259 self.project.clone(),
21260 window,
21261 cx,
21262 )
21263 });
21264
21265 window.defer(cx, move |window, cx| {
21266 workspace.update(cx, |workspace, cx| {
21267 workspace.active_pane().update(cx, |pane, cx| {
21268 pane.add_item(
21269 Box::new(proposed_changes_editor),
21270 true,
21271 true,
21272 None,
21273 window,
21274 cx,
21275 );
21276 });
21277 });
21278 });
21279 }
21280
21281 pub fn open_excerpts_in_split(
21282 &mut self,
21283 _: &OpenExcerptsSplit,
21284 window: &mut Window,
21285 cx: &mut Context<Self>,
21286 ) {
21287 self.open_excerpts_common(None, true, window, cx)
21288 }
21289
21290 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21291 self.open_excerpts_common(None, false, window, cx)
21292 }
21293
21294 fn open_excerpts_common(
21295 &mut self,
21296 jump_data: Option<JumpData>,
21297 split: bool,
21298 window: &mut Window,
21299 cx: &mut Context<Self>,
21300 ) {
21301 let Some(workspace) = self.workspace() else {
21302 cx.propagate();
21303 return;
21304 };
21305
21306 if self.buffer.read(cx).is_singleton() {
21307 cx.propagate();
21308 return;
21309 }
21310
21311 let mut new_selections_by_buffer = HashMap::default();
21312 match &jump_data {
21313 Some(JumpData::MultiBufferPoint {
21314 excerpt_id,
21315 position,
21316 anchor,
21317 line_offset_from_top,
21318 }) => {
21319 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21320 if let Some(buffer) = multi_buffer_snapshot
21321 .buffer_id_for_excerpt(*excerpt_id)
21322 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21323 {
21324 let buffer_snapshot = buffer.read(cx).snapshot();
21325 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21326 language::ToPoint::to_point(anchor, &buffer_snapshot)
21327 } else {
21328 buffer_snapshot.clip_point(*position, Bias::Left)
21329 };
21330 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21331 new_selections_by_buffer.insert(
21332 buffer,
21333 (
21334 vec![jump_to_offset..jump_to_offset],
21335 Some(*line_offset_from_top),
21336 ),
21337 );
21338 }
21339 }
21340 Some(JumpData::MultiBufferRow {
21341 row,
21342 line_offset_from_top,
21343 }) => {
21344 let point = MultiBufferPoint::new(row.0, 0);
21345 if let Some((buffer, buffer_point, _)) =
21346 self.buffer.read(cx).point_to_buffer_point(point, cx)
21347 {
21348 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21349 new_selections_by_buffer
21350 .entry(buffer)
21351 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21352 .0
21353 .push(buffer_offset..buffer_offset)
21354 }
21355 }
21356 None => {
21357 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
21358 let multi_buffer = self.buffer.read(cx);
21359 for selection in selections {
21360 for (snapshot, range, _, anchor) in multi_buffer
21361 .snapshot(cx)
21362 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21363 {
21364 if let Some(anchor) = anchor {
21365 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21366 else {
21367 continue;
21368 };
21369 let offset = text::ToOffset::to_offset(
21370 &anchor.text_anchor,
21371 &buffer_handle.read(cx).snapshot(),
21372 );
21373 let range = offset..offset;
21374 new_selections_by_buffer
21375 .entry(buffer_handle)
21376 .or_insert((Vec::new(), None))
21377 .0
21378 .push(range)
21379 } else {
21380 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21381 else {
21382 continue;
21383 };
21384 new_selections_by_buffer
21385 .entry(buffer_handle)
21386 .or_insert((Vec::new(), None))
21387 .0
21388 .push(range)
21389 }
21390 }
21391 }
21392 }
21393 }
21394
21395 new_selections_by_buffer
21396 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21397
21398 if new_selections_by_buffer.is_empty() {
21399 return;
21400 }
21401
21402 // We defer the pane interaction because we ourselves are a workspace item
21403 // and activating a new item causes the pane to call a method on us reentrantly,
21404 // which panics if we're on the stack.
21405 window.defer(cx, move |window, cx| {
21406 workspace.update(cx, |workspace, cx| {
21407 let pane = if split {
21408 workspace.adjacent_pane(window, cx)
21409 } else {
21410 workspace.active_pane().clone()
21411 };
21412
21413 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21414 let editor = buffer
21415 .read(cx)
21416 .file()
21417 .is_none()
21418 .then(|| {
21419 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21420 // so `workspace.open_project_item` will never find them, always opening a new editor.
21421 // Instead, we try to activate the existing editor in the pane first.
21422 let (editor, pane_item_index) =
21423 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21424 let editor = item.downcast::<Editor>()?;
21425 let singleton_buffer =
21426 editor.read(cx).buffer().read(cx).as_singleton()?;
21427 if singleton_buffer == buffer {
21428 Some((editor, i))
21429 } else {
21430 None
21431 }
21432 })?;
21433 pane.update(cx, |pane, cx| {
21434 pane.activate_item(pane_item_index, true, true, window, cx)
21435 });
21436 Some(editor)
21437 })
21438 .flatten()
21439 .unwrap_or_else(|| {
21440 workspace.open_project_item::<Self>(
21441 pane.clone(),
21442 buffer,
21443 true,
21444 true,
21445 window,
21446 cx,
21447 )
21448 });
21449
21450 editor.update(cx, |editor, cx| {
21451 let autoscroll = match scroll_offset {
21452 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21453 None => Autoscroll::newest(),
21454 };
21455 let nav_history = editor.nav_history.take();
21456 editor.change_selections(
21457 SelectionEffects::scroll(autoscroll),
21458 window,
21459 cx,
21460 |s| {
21461 s.select_ranges(ranges);
21462 },
21463 );
21464 editor.nav_history = nav_history;
21465 });
21466 }
21467 })
21468 });
21469 }
21470
21471 // For now, don't allow opening excerpts in buffers that aren't backed by
21472 // regular project files.
21473 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21474 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21475 }
21476
21477 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21478 let snapshot = self.buffer.read(cx).read(cx);
21479 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21480 Some(
21481 ranges
21482 .iter()
21483 .map(move |range| {
21484 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21485 })
21486 .collect(),
21487 )
21488 }
21489
21490 fn selection_replacement_ranges(
21491 &self,
21492 range: Range<OffsetUtf16>,
21493 cx: &mut App,
21494 ) -> Vec<Range<OffsetUtf16>> {
21495 let selections = self
21496 .selections
21497 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21498 let newest_selection = selections
21499 .iter()
21500 .max_by_key(|selection| selection.id)
21501 .unwrap();
21502 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21503 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21504 let snapshot = self.buffer.read(cx).read(cx);
21505 selections
21506 .into_iter()
21507 .map(|mut selection| {
21508 selection.start.0 =
21509 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21510 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21511 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21512 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21513 })
21514 .collect()
21515 }
21516
21517 fn report_editor_event(
21518 &self,
21519 reported_event: ReportEditorEvent,
21520 file_extension: Option<String>,
21521 cx: &App,
21522 ) {
21523 if cfg!(any(test, feature = "test-support")) {
21524 return;
21525 }
21526
21527 let Some(project) = &self.project else { return };
21528
21529 // If None, we are in a file without an extension
21530 let file = self
21531 .buffer
21532 .read(cx)
21533 .as_singleton()
21534 .and_then(|b| b.read(cx).file());
21535 let file_extension = file_extension.or(file
21536 .as_ref()
21537 .and_then(|file| Path::new(file.file_name(cx)).extension())
21538 .and_then(|e| e.to_str())
21539 .map(|a| a.to_string()));
21540
21541 let vim_mode = vim_enabled(cx);
21542
21543 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21544 let copilot_enabled = edit_predictions_provider
21545 == language::language_settings::EditPredictionProvider::Copilot;
21546 let copilot_enabled_for_language = self
21547 .buffer
21548 .read(cx)
21549 .language_settings(cx)
21550 .show_edit_predictions;
21551
21552 let project = project.read(cx);
21553 let event_type = reported_event.event_type();
21554
21555 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21556 telemetry::event!(
21557 event_type,
21558 type = if auto_saved {"autosave"} else {"manual"},
21559 file_extension,
21560 vim_mode,
21561 copilot_enabled,
21562 copilot_enabled_for_language,
21563 edit_predictions_provider,
21564 is_via_ssh = project.is_via_remote_server(),
21565 );
21566 } else {
21567 telemetry::event!(
21568 event_type,
21569 file_extension,
21570 vim_mode,
21571 copilot_enabled,
21572 copilot_enabled_for_language,
21573 edit_predictions_provider,
21574 is_via_ssh = project.is_via_remote_server(),
21575 );
21576 };
21577 }
21578
21579 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21580 /// with each line being an array of {text, highlight} objects.
21581 fn copy_highlight_json(
21582 &mut self,
21583 _: &CopyHighlightJson,
21584 window: &mut Window,
21585 cx: &mut Context<Self>,
21586 ) {
21587 #[derive(Serialize)]
21588 struct Chunk<'a> {
21589 text: String,
21590 highlight: Option<&'a str>,
21591 }
21592
21593 let snapshot = self.buffer.read(cx).snapshot(cx);
21594 let range = self
21595 .selected_text_range(false, window, cx)
21596 .and_then(|selection| {
21597 if selection.range.is_empty() {
21598 None
21599 } else {
21600 Some(selection.range)
21601 }
21602 })
21603 .unwrap_or_else(|| 0..snapshot.len());
21604
21605 let chunks = snapshot.chunks(range, true);
21606 let mut lines = Vec::new();
21607 let mut line: VecDeque<Chunk> = VecDeque::new();
21608
21609 let Some(style) = self.style.as_ref() else {
21610 return;
21611 };
21612
21613 for chunk in chunks {
21614 let highlight = chunk
21615 .syntax_highlight_id
21616 .and_then(|id| id.name(&style.syntax));
21617 let mut chunk_lines = chunk.text.split('\n').peekable();
21618 while let Some(text) = chunk_lines.next() {
21619 let mut merged_with_last_token = false;
21620 if let Some(last_token) = line.back_mut()
21621 && last_token.highlight == highlight
21622 {
21623 last_token.text.push_str(text);
21624 merged_with_last_token = true;
21625 }
21626
21627 if !merged_with_last_token {
21628 line.push_back(Chunk {
21629 text: text.into(),
21630 highlight,
21631 });
21632 }
21633
21634 if chunk_lines.peek().is_some() {
21635 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21636 line.pop_front();
21637 }
21638 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21639 line.pop_back();
21640 }
21641
21642 lines.push(mem::take(&mut line));
21643 }
21644 }
21645 }
21646
21647 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21648 return;
21649 };
21650 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21651 }
21652
21653 pub fn open_context_menu(
21654 &mut self,
21655 _: &OpenContextMenu,
21656 window: &mut Window,
21657 cx: &mut Context<Self>,
21658 ) {
21659 self.request_autoscroll(Autoscroll::newest(), cx);
21660 let position = self
21661 .selections
21662 .newest_display(&self.display_snapshot(cx))
21663 .start;
21664 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21665 }
21666
21667 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
21668 &self.inlay_hint_cache
21669 }
21670
21671 pub fn replay_insert_event(
21672 &mut self,
21673 text: &str,
21674 relative_utf16_range: Option<Range<isize>>,
21675 window: &mut Window,
21676 cx: &mut Context<Self>,
21677 ) {
21678 if !self.input_enabled {
21679 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21680 return;
21681 }
21682 if let Some(relative_utf16_range) = relative_utf16_range {
21683 let selections = self
21684 .selections
21685 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21686 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21687 let new_ranges = selections.into_iter().map(|range| {
21688 let start = OffsetUtf16(
21689 range
21690 .head()
21691 .0
21692 .saturating_add_signed(relative_utf16_range.start),
21693 );
21694 let end = OffsetUtf16(
21695 range
21696 .head()
21697 .0
21698 .saturating_add_signed(relative_utf16_range.end),
21699 );
21700 start..end
21701 });
21702 s.select_ranges(new_ranges);
21703 });
21704 }
21705
21706 self.handle_input(text, window, cx);
21707 }
21708
21709 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
21710 let Some(provider) = self.semantics_provider.as_ref() else {
21711 return false;
21712 };
21713
21714 let mut supports = false;
21715 self.buffer().update(cx, |this, cx| {
21716 this.for_each_buffer(|buffer| {
21717 supports |= provider.supports_inlay_hints(buffer, cx);
21718 });
21719 });
21720
21721 supports
21722 }
21723
21724 pub fn is_focused(&self, window: &Window) -> bool {
21725 self.focus_handle.is_focused(window)
21726 }
21727
21728 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21729 cx.emit(EditorEvent::Focused);
21730
21731 if let Some(descendant) = self
21732 .last_focused_descendant
21733 .take()
21734 .and_then(|descendant| descendant.upgrade())
21735 {
21736 window.focus(&descendant);
21737 } else {
21738 if let Some(blame) = self.blame.as_ref() {
21739 blame.update(cx, GitBlame::focus)
21740 }
21741
21742 self.blink_manager.update(cx, BlinkManager::enable);
21743 self.show_cursor_names(window, cx);
21744 self.buffer.update(cx, |buffer, cx| {
21745 buffer.finalize_last_transaction(cx);
21746 if self.leader_id.is_none() {
21747 buffer.set_active_selections(
21748 &self.selections.disjoint_anchors_arc(),
21749 self.selections.line_mode(),
21750 self.cursor_shape,
21751 cx,
21752 );
21753 }
21754 });
21755 }
21756 }
21757
21758 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21759 cx.emit(EditorEvent::FocusedIn)
21760 }
21761
21762 fn handle_focus_out(
21763 &mut self,
21764 event: FocusOutEvent,
21765 _window: &mut Window,
21766 cx: &mut Context<Self>,
21767 ) {
21768 if event.blurred != self.focus_handle {
21769 self.last_focused_descendant = Some(event.blurred);
21770 }
21771 self.selection_drag_state = SelectionDragState::None;
21772 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21773 }
21774
21775 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21776 self.blink_manager.update(cx, BlinkManager::disable);
21777 self.buffer
21778 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21779
21780 if let Some(blame) = self.blame.as_ref() {
21781 blame.update(cx, GitBlame::blur)
21782 }
21783 if !self.hover_state.focused(window, cx) {
21784 hide_hover(self, cx);
21785 }
21786 if !self
21787 .context_menu
21788 .borrow()
21789 .as_ref()
21790 .is_some_and(|context_menu| context_menu.focused(window, cx))
21791 {
21792 self.hide_context_menu(window, cx);
21793 }
21794 self.take_active_edit_prediction(cx);
21795 cx.emit(EditorEvent::Blurred);
21796 cx.notify();
21797 }
21798
21799 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21800 let mut pending: String = window
21801 .pending_input_keystrokes()
21802 .into_iter()
21803 .flatten()
21804 .filter_map(|keystroke| {
21805 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21806 keystroke.key_char.clone()
21807 } else {
21808 None
21809 }
21810 })
21811 .collect();
21812
21813 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21814 pending = "".to_string();
21815 }
21816
21817 let existing_pending = self
21818 .text_highlights::<PendingInput>(cx)
21819 .map(|(_, ranges)| ranges.to_vec());
21820 if existing_pending.is_none() && pending.is_empty() {
21821 return;
21822 }
21823 let transaction =
21824 self.transact(window, cx, |this, window, cx| {
21825 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
21826 let edits = selections
21827 .iter()
21828 .map(|selection| (selection.end..selection.end, pending.clone()));
21829 this.edit(edits, cx);
21830 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21831 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21832 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21833 }));
21834 });
21835 if let Some(existing_ranges) = existing_pending {
21836 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21837 this.edit(edits, cx);
21838 }
21839 });
21840
21841 let snapshot = self.snapshot(window, cx);
21842 let ranges = self
21843 .selections
21844 .all::<usize>(&snapshot.display_snapshot)
21845 .into_iter()
21846 .map(|selection| {
21847 snapshot.buffer_snapshot().anchor_after(selection.end)
21848 ..snapshot
21849 .buffer_snapshot()
21850 .anchor_before(selection.end + pending.len())
21851 })
21852 .collect();
21853
21854 if pending.is_empty() {
21855 self.clear_highlights::<PendingInput>(cx);
21856 } else {
21857 self.highlight_text::<PendingInput>(
21858 ranges,
21859 HighlightStyle {
21860 underline: Some(UnderlineStyle {
21861 thickness: px(1.),
21862 color: None,
21863 wavy: false,
21864 }),
21865 ..Default::default()
21866 },
21867 cx,
21868 );
21869 }
21870
21871 self.ime_transaction = self.ime_transaction.or(transaction);
21872 if let Some(transaction) = self.ime_transaction {
21873 self.buffer.update(cx, |buffer, cx| {
21874 buffer.group_until_transaction(transaction, cx);
21875 });
21876 }
21877
21878 if self.text_highlights::<PendingInput>(cx).is_none() {
21879 self.ime_transaction.take();
21880 }
21881 }
21882
21883 pub fn register_action_renderer(
21884 &mut self,
21885 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21886 ) -> Subscription {
21887 let id = self.next_editor_action_id.post_inc();
21888 self.editor_actions
21889 .borrow_mut()
21890 .insert(id, Box::new(listener));
21891
21892 let editor_actions = self.editor_actions.clone();
21893 Subscription::new(move || {
21894 editor_actions.borrow_mut().remove(&id);
21895 })
21896 }
21897
21898 pub fn register_action<A: Action>(
21899 &mut self,
21900 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21901 ) -> Subscription {
21902 let id = self.next_editor_action_id.post_inc();
21903 let listener = Arc::new(listener);
21904 self.editor_actions.borrow_mut().insert(
21905 id,
21906 Box::new(move |_, window, _| {
21907 let listener = listener.clone();
21908 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21909 let action = action.downcast_ref().unwrap();
21910 if phase == DispatchPhase::Bubble {
21911 listener(action, window, cx)
21912 }
21913 })
21914 }),
21915 );
21916
21917 let editor_actions = self.editor_actions.clone();
21918 Subscription::new(move || {
21919 editor_actions.borrow_mut().remove(&id);
21920 })
21921 }
21922
21923 pub fn file_header_size(&self) -> u32 {
21924 FILE_HEADER_HEIGHT
21925 }
21926
21927 pub fn restore(
21928 &mut self,
21929 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21930 window: &mut Window,
21931 cx: &mut Context<Self>,
21932 ) {
21933 let workspace = self.workspace();
21934 let project = self.project();
21935 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21936 let mut tasks = Vec::new();
21937 for (buffer_id, changes) in revert_changes {
21938 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21939 buffer.update(cx, |buffer, cx| {
21940 buffer.edit(
21941 changes
21942 .into_iter()
21943 .map(|(range, text)| (range, text.to_string())),
21944 None,
21945 cx,
21946 );
21947 });
21948
21949 if let Some(project) =
21950 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21951 {
21952 project.update(cx, |project, cx| {
21953 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21954 })
21955 }
21956 }
21957 }
21958 tasks
21959 });
21960 cx.spawn_in(window, async move |_, cx| {
21961 for (buffer, task) in save_tasks {
21962 let result = task.await;
21963 if result.is_err() {
21964 let Some(path) = buffer
21965 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21966 .ok()
21967 else {
21968 continue;
21969 };
21970 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21971 let Some(task) = cx
21972 .update_window_entity(workspace, |workspace, window, cx| {
21973 workspace
21974 .open_path_preview(path, None, false, false, false, window, cx)
21975 })
21976 .ok()
21977 else {
21978 continue;
21979 };
21980 task.await.log_err();
21981 }
21982 }
21983 }
21984 })
21985 .detach();
21986 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21987 selections.refresh()
21988 });
21989 }
21990
21991 pub fn to_pixel_point(
21992 &self,
21993 source: multi_buffer::Anchor,
21994 editor_snapshot: &EditorSnapshot,
21995 window: &mut Window,
21996 ) -> Option<gpui::Point<Pixels>> {
21997 let source_point = source.to_display_point(editor_snapshot);
21998 self.display_to_pixel_point(source_point, editor_snapshot, window)
21999 }
22000
22001 pub fn display_to_pixel_point(
22002 &self,
22003 source: DisplayPoint,
22004 editor_snapshot: &EditorSnapshot,
22005 window: &mut Window,
22006 ) -> Option<gpui::Point<Pixels>> {
22007 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
22008 let text_layout_details = self.text_layout_details(window);
22009 let scroll_top = text_layout_details
22010 .scroll_anchor
22011 .scroll_position(editor_snapshot)
22012 .y;
22013
22014 if source.row().as_f64() < scroll_top.floor() {
22015 return None;
22016 }
22017 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
22018 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
22019 Some(gpui::Point::new(source_x, source_y))
22020 }
22021
22022 pub fn has_visible_completions_menu(&self) -> bool {
22023 !self.edit_prediction_preview_is_active()
22024 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
22025 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
22026 })
22027 }
22028
22029 pub fn register_addon<T: Addon>(&mut self, instance: T) {
22030 if self.mode.is_minimap() {
22031 return;
22032 }
22033 self.addons
22034 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
22035 }
22036
22037 pub fn unregister_addon<T: Addon>(&mut self) {
22038 self.addons.remove(&std::any::TypeId::of::<T>());
22039 }
22040
22041 pub fn addon<T: Addon>(&self) -> Option<&T> {
22042 let type_id = std::any::TypeId::of::<T>();
22043 self.addons
22044 .get(&type_id)
22045 .and_then(|item| item.to_any().downcast_ref::<T>())
22046 }
22047
22048 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
22049 let type_id = std::any::TypeId::of::<T>();
22050 self.addons
22051 .get_mut(&type_id)
22052 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
22053 }
22054
22055 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
22056 let text_layout_details = self.text_layout_details(window);
22057 let style = &text_layout_details.editor_style;
22058 let font_id = window.text_system().resolve_font(&style.text.font());
22059 let font_size = style.text.font_size.to_pixels(window.rem_size());
22060 let line_height = style.text.line_height_in_pixels(window.rem_size());
22061 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
22062 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
22063
22064 CharacterDimensions {
22065 em_width,
22066 em_advance,
22067 line_height,
22068 }
22069 }
22070
22071 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
22072 self.load_diff_task.clone()
22073 }
22074
22075 fn read_metadata_from_db(
22076 &mut self,
22077 item_id: u64,
22078 workspace_id: WorkspaceId,
22079 window: &mut Window,
22080 cx: &mut Context<Editor>,
22081 ) {
22082 if self.buffer_kind(cx) == ItemBufferKind::Singleton
22083 && !self.mode.is_minimap()
22084 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
22085 {
22086 let buffer_snapshot = OnceCell::new();
22087
22088 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
22089 && !folds.is_empty()
22090 {
22091 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22092 self.fold_ranges(
22093 folds
22094 .into_iter()
22095 .map(|(start, end)| {
22096 snapshot.clip_offset(start, Bias::Left)
22097 ..snapshot.clip_offset(end, Bias::Right)
22098 })
22099 .collect(),
22100 false,
22101 window,
22102 cx,
22103 );
22104 }
22105
22106 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22107 && !selections.is_empty()
22108 {
22109 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22110 // skip adding the initial selection to selection history
22111 self.selection_history.mode = SelectionHistoryMode::Skipping;
22112 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22113 s.select_ranges(selections.into_iter().map(|(start, end)| {
22114 snapshot.clip_offset(start, Bias::Left)
22115 ..snapshot.clip_offset(end, Bias::Right)
22116 }));
22117 });
22118 self.selection_history.mode = SelectionHistoryMode::Normal;
22119 };
22120 }
22121
22122 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22123 }
22124
22125 fn update_lsp_data(
22126 &mut self,
22127 for_buffer: Option<BufferId>,
22128 window: &mut Window,
22129 cx: &mut Context<'_, Self>,
22130 ) {
22131 self.pull_diagnostics(for_buffer, window, cx);
22132 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22133 }
22134
22135 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22136 if self.ignore_lsp_data() {
22137 return;
22138 }
22139 for (_, (visible_buffer, _, _)) in self.visible_excerpts(None, cx) {
22140 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22141 }
22142 }
22143
22144 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) -> bool {
22145 if !self.registered_buffers.contains_key(&buffer_id)
22146 && let Some(project) = self.project.as_ref()
22147 {
22148 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22149 project.update(cx, |project, cx| {
22150 self.registered_buffers.insert(
22151 buffer_id,
22152 project.register_buffer_with_language_servers(&buffer, cx),
22153 );
22154 });
22155 return true;
22156 } else {
22157 self.registered_buffers.remove(&buffer_id);
22158 }
22159 }
22160
22161 false
22162 }
22163
22164 fn ignore_lsp_data(&self) -> bool {
22165 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22166 // skip any LSP updates for it.
22167 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22168 }
22169}
22170
22171fn edit_for_markdown_paste<'a>(
22172 buffer: &MultiBufferSnapshot,
22173 range: Range<usize>,
22174 to_insert: &'a str,
22175 url: Option<url::Url>,
22176) -> (Range<usize>, Cow<'a, str>) {
22177 if url.is_none() {
22178 return (range, Cow::Borrowed(to_insert));
22179 };
22180
22181 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22182
22183 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22184 Cow::Borrowed(to_insert)
22185 } else {
22186 Cow::Owned(format!("[{old_text}]({to_insert})"))
22187 };
22188 (range, new_text)
22189}
22190
22191fn vim_enabled(cx: &App) -> bool {
22192 vim_mode_setting::VimModeSetting::try_get(cx)
22193 .map(|vim_mode| vim_mode.0)
22194 .unwrap_or(false)
22195}
22196
22197fn process_completion_for_edit(
22198 completion: &Completion,
22199 intent: CompletionIntent,
22200 buffer: &Entity<Buffer>,
22201 cursor_position: &text::Anchor,
22202 cx: &mut Context<Editor>,
22203) -> CompletionEdit {
22204 let buffer = buffer.read(cx);
22205 let buffer_snapshot = buffer.snapshot();
22206 let (snippet, new_text) = if completion.is_snippet() {
22207 let mut snippet_source = completion.new_text.clone();
22208 // Workaround for typescript language server issues so that methods don't expand within
22209 // strings and functions with type expressions. The previous point is used because the query
22210 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22211 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22212 let previous_point = if previous_point.column > 0 {
22213 cursor_position.to_previous_offset(&buffer_snapshot)
22214 } else {
22215 cursor_position.to_offset(&buffer_snapshot)
22216 };
22217 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22218 && scope.prefers_label_for_snippet_in_completion()
22219 && let Some(label) = completion.label()
22220 && matches!(
22221 completion.kind(),
22222 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22223 )
22224 {
22225 snippet_source = label;
22226 }
22227 match Snippet::parse(&snippet_source).log_err() {
22228 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22229 None => (None, completion.new_text.clone()),
22230 }
22231 } else {
22232 (None, completion.new_text.clone())
22233 };
22234
22235 let mut range_to_replace = {
22236 let replace_range = &completion.replace_range;
22237 if let CompletionSource::Lsp {
22238 insert_range: Some(insert_range),
22239 ..
22240 } = &completion.source
22241 {
22242 debug_assert_eq!(
22243 insert_range.start, replace_range.start,
22244 "insert_range and replace_range should start at the same position"
22245 );
22246 debug_assert!(
22247 insert_range
22248 .start
22249 .cmp(cursor_position, &buffer_snapshot)
22250 .is_le(),
22251 "insert_range should start before or at cursor position"
22252 );
22253 debug_assert!(
22254 replace_range
22255 .start
22256 .cmp(cursor_position, &buffer_snapshot)
22257 .is_le(),
22258 "replace_range should start before or at cursor position"
22259 );
22260
22261 let should_replace = match intent {
22262 CompletionIntent::CompleteWithInsert => false,
22263 CompletionIntent::CompleteWithReplace => true,
22264 CompletionIntent::Complete | CompletionIntent::Compose => {
22265 let insert_mode =
22266 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22267 .completions
22268 .lsp_insert_mode;
22269 match insert_mode {
22270 LspInsertMode::Insert => false,
22271 LspInsertMode::Replace => true,
22272 LspInsertMode::ReplaceSubsequence => {
22273 let mut text_to_replace = buffer.chars_for_range(
22274 buffer.anchor_before(replace_range.start)
22275 ..buffer.anchor_after(replace_range.end),
22276 );
22277 let mut current_needle = text_to_replace.next();
22278 for haystack_ch in completion.label.text.chars() {
22279 if let Some(needle_ch) = current_needle
22280 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22281 {
22282 current_needle = text_to_replace.next();
22283 }
22284 }
22285 current_needle.is_none()
22286 }
22287 LspInsertMode::ReplaceSuffix => {
22288 if replace_range
22289 .end
22290 .cmp(cursor_position, &buffer_snapshot)
22291 .is_gt()
22292 {
22293 let range_after_cursor = *cursor_position..replace_range.end;
22294 let text_after_cursor = buffer
22295 .text_for_range(
22296 buffer.anchor_before(range_after_cursor.start)
22297 ..buffer.anchor_after(range_after_cursor.end),
22298 )
22299 .collect::<String>()
22300 .to_ascii_lowercase();
22301 completion
22302 .label
22303 .text
22304 .to_ascii_lowercase()
22305 .ends_with(&text_after_cursor)
22306 } else {
22307 true
22308 }
22309 }
22310 }
22311 }
22312 };
22313
22314 if should_replace {
22315 replace_range.clone()
22316 } else {
22317 insert_range.clone()
22318 }
22319 } else {
22320 replace_range.clone()
22321 }
22322 };
22323
22324 if range_to_replace
22325 .end
22326 .cmp(cursor_position, &buffer_snapshot)
22327 .is_lt()
22328 {
22329 range_to_replace.end = *cursor_position;
22330 }
22331
22332 CompletionEdit {
22333 new_text,
22334 replace_range: range_to_replace.to_offset(buffer),
22335 snippet,
22336 }
22337}
22338
22339struct CompletionEdit {
22340 new_text: String,
22341 replace_range: Range<usize>,
22342 snippet: Option<Snippet>,
22343}
22344
22345fn insert_extra_newline_brackets(
22346 buffer: &MultiBufferSnapshot,
22347 range: Range<usize>,
22348 language: &language::LanguageScope,
22349) -> bool {
22350 let leading_whitespace_len = buffer
22351 .reversed_chars_at(range.start)
22352 .take_while(|c| c.is_whitespace() && *c != '\n')
22353 .map(|c| c.len_utf8())
22354 .sum::<usize>();
22355 let trailing_whitespace_len = buffer
22356 .chars_at(range.end)
22357 .take_while(|c| c.is_whitespace() && *c != '\n')
22358 .map(|c| c.len_utf8())
22359 .sum::<usize>();
22360 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22361
22362 language.brackets().any(|(pair, enabled)| {
22363 let pair_start = pair.start.trim_end();
22364 let pair_end = pair.end.trim_start();
22365
22366 enabled
22367 && pair.newline
22368 && buffer.contains_str_at(range.end, pair_end)
22369 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22370 })
22371}
22372
22373fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22374 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22375 [(buffer, range, _)] => (*buffer, range.clone()),
22376 _ => return false,
22377 };
22378 let pair = {
22379 let mut result: Option<BracketMatch> = None;
22380
22381 for pair in buffer
22382 .all_bracket_ranges(range.clone())
22383 .filter(move |pair| {
22384 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22385 })
22386 {
22387 let len = pair.close_range.end - pair.open_range.start;
22388
22389 if let Some(existing) = &result {
22390 let existing_len = existing.close_range.end - existing.open_range.start;
22391 if len > existing_len {
22392 continue;
22393 }
22394 }
22395
22396 result = Some(pair);
22397 }
22398
22399 result
22400 };
22401 let Some(pair) = pair else {
22402 return false;
22403 };
22404 pair.newline_only
22405 && buffer
22406 .chars_for_range(pair.open_range.end..range.start)
22407 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22408 .all(|c| c.is_whitespace() && c != '\n')
22409}
22410
22411fn update_uncommitted_diff_for_buffer(
22412 editor: Entity<Editor>,
22413 project: &Entity<Project>,
22414 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22415 buffer: Entity<MultiBuffer>,
22416 cx: &mut App,
22417) -> Task<()> {
22418 let mut tasks = Vec::new();
22419 project.update(cx, |project, cx| {
22420 for buffer in buffers {
22421 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22422 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22423 }
22424 }
22425 });
22426 cx.spawn(async move |cx| {
22427 let diffs = future::join_all(tasks).await;
22428 if editor
22429 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22430 .unwrap_or(false)
22431 {
22432 return;
22433 }
22434
22435 buffer
22436 .update(cx, |buffer, cx| {
22437 for diff in diffs.into_iter().flatten() {
22438 buffer.add_diff(diff, cx);
22439 }
22440 })
22441 .ok();
22442 })
22443}
22444
22445fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22446 let tab_size = tab_size.get() as usize;
22447 let mut width = offset;
22448
22449 for ch in text.chars() {
22450 width += if ch == '\t' {
22451 tab_size - (width % tab_size)
22452 } else {
22453 1
22454 };
22455 }
22456
22457 width - offset
22458}
22459
22460#[cfg(test)]
22461mod tests {
22462 use super::*;
22463
22464 #[test]
22465 fn test_string_size_with_expanded_tabs() {
22466 let nz = |val| NonZeroU32::new(val).unwrap();
22467 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22468 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22469 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22470 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22471 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22472 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22473 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22474 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22475 }
22476}
22477
22478/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22479struct WordBreakingTokenizer<'a> {
22480 input: &'a str,
22481}
22482
22483impl<'a> WordBreakingTokenizer<'a> {
22484 fn new(input: &'a str) -> Self {
22485 Self { input }
22486 }
22487}
22488
22489fn is_char_ideographic(ch: char) -> bool {
22490 use unicode_script::Script::*;
22491 use unicode_script::UnicodeScript;
22492 matches!(ch.script(), Han | Tangut | Yi)
22493}
22494
22495fn is_grapheme_ideographic(text: &str) -> bool {
22496 text.chars().any(is_char_ideographic)
22497}
22498
22499fn is_grapheme_whitespace(text: &str) -> bool {
22500 text.chars().any(|x| x.is_whitespace())
22501}
22502
22503fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22504 text.chars()
22505 .next()
22506 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22507}
22508
22509#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22510enum WordBreakToken<'a> {
22511 Word { token: &'a str, grapheme_len: usize },
22512 InlineWhitespace { token: &'a str, grapheme_len: usize },
22513 Newline,
22514}
22515
22516impl<'a> Iterator for WordBreakingTokenizer<'a> {
22517 /// Yields a span, the count of graphemes in the token, and whether it was
22518 /// whitespace. Note that it also breaks at word boundaries.
22519 type Item = WordBreakToken<'a>;
22520
22521 fn next(&mut self) -> Option<Self::Item> {
22522 use unicode_segmentation::UnicodeSegmentation;
22523 if self.input.is_empty() {
22524 return None;
22525 }
22526
22527 let mut iter = self.input.graphemes(true).peekable();
22528 let mut offset = 0;
22529 let mut grapheme_len = 0;
22530 if let Some(first_grapheme) = iter.next() {
22531 let is_newline = first_grapheme == "\n";
22532 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22533 offset += first_grapheme.len();
22534 grapheme_len += 1;
22535 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22536 if let Some(grapheme) = iter.peek().copied()
22537 && should_stay_with_preceding_ideograph(grapheme)
22538 {
22539 offset += grapheme.len();
22540 grapheme_len += 1;
22541 }
22542 } else {
22543 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22544 let mut next_word_bound = words.peek().copied();
22545 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22546 next_word_bound = words.next();
22547 }
22548 while let Some(grapheme) = iter.peek().copied() {
22549 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22550 break;
22551 };
22552 if is_grapheme_whitespace(grapheme) != is_whitespace
22553 || (grapheme == "\n") != is_newline
22554 {
22555 break;
22556 };
22557 offset += grapheme.len();
22558 grapheme_len += 1;
22559 iter.next();
22560 }
22561 }
22562 let token = &self.input[..offset];
22563 self.input = &self.input[offset..];
22564 if token == "\n" {
22565 Some(WordBreakToken::Newline)
22566 } else if is_whitespace {
22567 Some(WordBreakToken::InlineWhitespace {
22568 token,
22569 grapheme_len,
22570 })
22571 } else {
22572 Some(WordBreakToken::Word {
22573 token,
22574 grapheme_len,
22575 })
22576 }
22577 } else {
22578 None
22579 }
22580 }
22581}
22582
22583#[test]
22584fn test_word_breaking_tokenizer() {
22585 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22586 ("", &[]),
22587 (" ", &[whitespace(" ", 2)]),
22588 ("Ʒ", &[word("Ʒ", 1)]),
22589 ("Ǽ", &[word("Ǽ", 1)]),
22590 ("⋑", &[word("⋑", 1)]),
22591 ("⋑⋑", &[word("⋑⋑", 2)]),
22592 (
22593 "原理,进而",
22594 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22595 ),
22596 (
22597 "hello world",
22598 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22599 ),
22600 (
22601 "hello, world",
22602 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22603 ),
22604 (
22605 " hello world",
22606 &[
22607 whitespace(" ", 2),
22608 word("hello", 5),
22609 whitespace(" ", 1),
22610 word("world", 5),
22611 ],
22612 ),
22613 (
22614 "这是什么 \n 钢笔",
22615 &[
22616 word("这", 1),
22617 word("是", 1),
22618 word("什", 1),
22619 word("么", 1),
22620 whitespace(" ", 1),
22621 newline(),
22622 whitespace(" ", 1),
22623 word("钢", 1),
22624 word("笔", 1),
22625 ],
22626 ),
22627 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22628 ];
22629
22630 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22631 WordBreakToken::Word {
22632 token,
22633 grapheme_len,
22634 }
22635 }
22636
22637 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22638 WordBreakToken::InlineWhitespace {
22639 token,
22640 grapheme_len,
22641 }
22642 }
22643
22644 fn newline() -> WordBreakToken<'static> {
22645 WordBreakToken::Newline
22646 }
22647
22648 for (input, result) in tests {
22649 assert_eq!(
22650 WordBreakingTokenizer::new(input)
22651 .collect::<Vec<_>>()
22652 .as_slice(),
22653 *result,
22654 );
22655 }
22656}
22657
22658fn wrap_with_prefix(
22659 first_line_prefix: String,
22660 subsequent_lines_prefix: String,
22661 unwrapped_text: String,
22662 wrap_column: usize,
22663 tab_size: NonZeroU32,
22664 preserve_existing_whitespace: bool,
22665) -> String {
22666 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22667 let subsequent_lines_prefix_len =
22668 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22669 let mut wrapped_text = String::new();
22670 let mut current_line = first_line_prefix;
22671 let mut is_first_line = true;
22672
22673 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22674 let mut current_line_len = first_line_prefix_len;
22675 let mut in_whitespace = false;
22676 for token in tokenizer {
22677 let have_preceding_whitespace = in_whitespace;
22678 match token {
22679 WordBreakToken::Word {
22680 token,
22681 grapheme_len,
22682 } => {
22683 in_whitespace = false;
22684 let current_prefix_len = if is_first_line {
22685 first_line_prefix_len
22686 } else {
22687 subsequent_lines_prefix_len
22688 };
22689 if current_line_len + grapheme_len > wrap_column
22690 && current_line_len != current_prefix_len
22691 {
22692 wrapped_text.push_str(current_line.trim_end());
22693 wrapped_text.push('\n');
22694 is_first_line = false;
22695 current_line = subsequent_lines_prefix.clone();
22696 current_line_len = subsequent_lines_prefix_len;
22697 }
22698 current_line.push_str(token);
22699 current_line_len += grapheme_len;
22700 }
22701 WordBreakToken::InlineWhitespace {
22702 mut token,
22703 mut grapheme_len,
22704 } => {
22705 in_whitespace = true;
22706 if have_preceding_whitespace && !preserve_existing_whitespace {
22707 continue;
22708 }
22709 if !preserve_existing_whitespace {
22710 // Keep a single whitespace grapheme as-is
22711 if let Some(first) =
22712 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22713 {
22714 token = first;
22715 } else {
22716 token = " ";
22717 }
22718 grapheme_len = 1;
22719 }
22720 let current_prefix_len = if is_first_line {
22721 first_line_prefix_len
22722 } else {
22723 subsequent_lines_prefix_len
22724 };
22725 if current_line_len + grapheme_len > wrap_column {
22726 wrapped_text.push_str(current_line.trim_end());
22727 wrapped_text.push('\n');
22728 is_first_line = false;
22729 current_line = subsequent_lines_prefix.clone();
22730 current_line_len = subsequent_lines_prefix_len;
22731 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22732 current_line.push_str(token);
22733 current_line_len += grapheme_len;
22734 }
22735 }
22736 WordBreakToken::Newline => {
22737 in_whitespace = true;
22738 let current_prefix_len = if is_first_line {
22739 first_line_prefix_len
22740 } else {
22741 subsequent_lines_prefix_len
22742 };
22743 if preserve_existing_whitespace {
22744 wrapped_text.push_str(current_line.trim_end());
22745 wrapped_text.push('\n');
22746 is_first_line = false;
22747 current_line = subsequent_lines_prefix.clone();
22748 current_line_len = subsequent_lines_prefix_len;
22749 } else if have_preceding_whitespace {
22750 continue;
22751 } else if current_line_len + 1 > wrap_column
22752 && current_line_len != current_prefix_len
22753 {
22754 wrapped_text.push_str(current_line.trim_end());
22755 wrapped_text.push('\n');
22756 is_first_line = false;
22757 current_line = subsequent_lines_prefix.clone();
22758 current_line_len = subsequent_lines_prefix_len;
22759 } else if current_line_len != current_prefix_len {
22760 current_line.push(' ');
22761 current_line_len += 1;
22762 }
22763 }
22764 }
22765 }
22766
22767 if !current_line.is_empty() {
22768 wrapped_text.push_str(¤t_line);
22769 }
22770 wrapped_text
22771}
22772
22773#[test]
22774fn test_wrap_with_prefix() {
22775 assert_eq!(
22776 wrap_with_prefix(
22777 "# ".to_string(),
22778 "# ".to_string(),
22779 "abcdefg".to_string(),
22780 4,
22781 NonZeroU32::new(4).unwrap(),
22782 false,
22783 ),
22784 "# abcdefg"
22785 );
22786 assert_eq!(
22787 wrap_with_prefix(
22788 "".to_string(),
22789 "".to_string(),
22790 "\thello world".to_string(),
22791 8,
22792 NonZeroU32::new(4).unwrap(),
22793 false,
22794 ),
22795 "hello\nworld"
22796 );
22797 assert_eq!(
22798 wrap_with_prefix(
22799 "// ".to_string(),
22800 "// ".to_string(),
22801 "xx \nyy zz aa bb cc".to_string(),
22802 12,
22803 NonZeroU32::new(4).unwrap(),
22804 false,
22805 ),
22806 "// xx yy zz\n// aa bb cc"
22807 );
22808 assert_eq!(
22809 wrap_with_prefix(
22810 String::new(),
22811 String::new(),
22812 "这是什么 \n 钢笔".to_string(),
22813 3,
22814 NonZeroU32::new(4).unwrap(),
22815 false,
22816 ),
22817 "这是什\n么 钢\n笔"
22818 );
22819 assert_eq!(
22820 wrap_with_prefix(
22821 String::new(),
22822 String::new(),
22823 format!("foo{}bar", '\u{2009}'), // thin space
22824 80,
22825 NonZeroU32::new(4).unwrap(),
22826 false,
22827 ),
22828 format!("foo{}bar", '\u{2009}')
22829 );
22830}
22831
22832pub trait CollaborationHub {
22833 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22834 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22835 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22836}
22837
22838impl CollaborationHub for Entity<Project> {
22839 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22840 self.read(cx).collaborators()
22841 }
22842
22843 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22844 self.read(cx).user_store().read(cx).participant_indices()
22845 }
22846
22847 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22848 let this = self.read(cx);
22849 let user_ids = this.collaborators().values().map(|c| c.user_id);
22850 this.user_store().read(cx).participant_names(user_ids, cx)
22851 }
22852}
22853
22854pub trait SemanticsProvider {
22855 fn hover(
22856 &self,
22857 buffer: &Entity<Buffer>,
22858 position: text::Anchor,
22859 cx: &mut App,
22860 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22861
22862 fn inline_values(
22863 &self,
22864 buffer_handle: Entity<Buffer>,
22865 range: Range<text::Anchor>,
22866 cx: &mut App,
22867 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22868
22869 fn inlay_hints(
22870 &self,
22871 buffer_handle: Entity<Buffer>,
22872 range: Range<text::Anchor>,
22873 cx: &mut App,
22874 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22875
22876 fn resolve_inlay_hint(
22877 &self,
22878 hint: InlayHint,
22879 buffer_handle: Entity<Buffer>,
22880 server_id: LanguageServerId,
22881 cx: &mut App,
22882 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22883
22884 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22885
22886 fn document_highlights(
22887 &self,
22888 buffer: &Entity<Buffer>,
22889 position: text::Anchor,
22890 cx: &mut App,
22891 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22892
22893 fn definitions(
22894 &self,
22895 buffer: &Entity<Buffer>,
22896 position: text::Anchor,
22897 kind: GotoDefinitionKind,
22898 cx: &mut App,
22899 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22900
22901 fn range_for_rename(
22902 &self,
22903 buffer: &Entity<Buffer>,
22904 position: text::Anchor,
22905 cx: &mut App,
22906 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22907
22908 fn perform_rename(
22909 &self,
22910 buffer: &Entity<Buffer>,
22911 position: text::Anchor,
22912 new_name: String,
22913 cx: &mut App,
22914 ) -> Option<Task<Result<ProjectTransaction>>>;
22915}
22916
22917pub trait CompletionProvider {
22918 fn completions(
22919 &self,
22920 excerpt_id: ExcerptId,
22921 buffer: &Entity<Buffer>,
22922 buffer_position: text::Anchor,
22923 trigger: CompletionContext,
22924 window: &mut Window,
22925 cx: &mut Context<Editor>,
22926 ) -> Task<Result<Vec<CompletionResponse>>>;
22927
22928 fn resolve_completions(
22929 &self,
22930 _buffer: Entity<Buffer>,
22931 _completion_indices: Vec<usize>,
22932 _completions: Rc<RefCell<Box<[Completion]>>>,
22933 _cx: &mut Context<Editor>,
22934 ) -> Task<Result<bool>> {
22935 Task::ready(Ok(false))
22936 }
22937
22938 fn apply_additional_edits_for_completion(
22939 &self,
22940 _buffer: Entity<Buffer>,
22941 _completions: Rc<RefCell<Box<[Completion]>>>,
22942 _completion_index: usize,
22943 _push_to_history: bool,
22944 _cx: &mut Context<Editor>,
22945 ) -> Task<Result<Option<language::Transaction>>> {
22946 Task::ready(Ok(None))
22947 }
22948
22949 fn is_completion_trigger(
22950 &self,
22951 buffer: &Entity<Buffer>,
22952 position: language::Anchor,
22953 text: &str,
22954 trigger_in_words: bool,
22955 menu_is_open: bool,
22956 cx: &mut Context<Editor>,
22957 ) -> bool;
22958
22959 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22960
22961 fn sort_completions(&self) -> bool {
22962 true
22963 }
22964
22965 fn filter_completions(&self) -> bool {
22966 true
22967 }
22968}
22969
22970pub trait CodeActionProvider {
22971 fn id(&self) -> Arc<str>;
22972
22973 fn code_actions(
22974 &self,
22975 buffer: &Entity<Buffer>,
22976 range: Range<text::Anchor>,
22977 window: &mut Window,
22978 cx: &mut App,
22979 ) -> Task<Result<Vec<CodeAction>>>;
22980
22981 fn apply_code_action(
22982 &self,
22983 buffer_handle: Entity<Buffer>,
22984 action: CodeAction,
22985 excerpt_id: ExcerptId,
22986 push_to_history: bool,
22987 window: &mut Window,
22988 cx: &mut App,
22989 ) -> Task<Result<ProjectTransaction>>;
22990}
22991
22992impl CodeActionProvider for Entity<Project> {
22993 fn id(&self) -> Arc<str> {
22994 "project".into()
22995 }
22996
22997 fn code_actions(
22998 &self,
22999 buffer: &Entity<Buffer>,
23000 range: Range<text::Anchor>,
23001 _window: &mut Window,
23002 cx: &mut App,
23003 ) -> Task<Result<Vec<CodeAction>>> {
23004 self.update(cx, |project, cx| {
23005 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
23006 let code_actions = project.code_actions(buffer, range, None, cx);
23007 cx.background_spawn(async move {
23008 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
23009 Ok(code_lens_actions
23010 .context("code lens fetch")?
23011 .into_iter()
23012 .flatten()
23013 .chain(
23014 code_actions
23015 .context("code action fetch")?
23016 .into_iter()
23017 .flatten(),
23018 )
23019 .collect())
23020 })
23021 })
23022 }
23023
23024 fn apply_code_action(
23025 &self,
23026 buffer_handle: Entity<Buffer>,
23027 action: CodeAction,
23028 _excerpt_id: ExcerptId,
23029 push_to_history: bool,
23030 _window: &mut Window,
23031 cx: &mut App,
23032 ) -> Task<Result<ProjectTransaction>> {
23033 self.update(cx, |project, cx| {
23034 project.apply_code_action(buffer_handle, action, push_to_history, cx)
23035 })
23036 }
23037}
23038
23039fn snippet_completions(
23040 project: &Project,
23041 buffer: &Entity<Buffer>,
23042 buffer_position: text::Anchor,
23043 cx: &mut App,
23044) -> Task<Result<CompletionResponse>> {
23045 let languages = buffer.read(cx).languages_at(buffer_position);
23046 let snippet_store = project.snippets().read(cx);
23047
23048 let scopes: Vec<_> = languages
23049 .iter()
23050 .filter_map(|language| {
23051 let language_name = language.lsp_id();
23052 let snippets = snippet_store.snippets_for(Some(language_name), cx);
23053
23054 if snippets.is_empty() {
23055 None
23056 } else {
23057 Some((language.default_scope(), snippets))
23058 }
23059 })
23060 .collect();
23061
23062 if scopes.is_empty() {
23063 return Task::ready(Ok(CompletionResponse {
23064 completions: vec![],
23065 display_options: CompletionDisplayOptions::default(),
23066 is_incomplete: false,
23067 }));
23068 }
23069
23070 let snapshot = buffer.read(cx).text_snapshot();
23071 let executor = cx.background_executor().clone();
23072
23073 cx.background_spawn(async move {
23074 let mut is_incomplete = false;
23075 let mut completions: Vec<Completion> = Vec::new();
23076 for (scope, snippets) in scopes.into_iter() {
23077 let classifier =
23078 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
23079
23080 const MAX_WORD_PREFIX_LEN: usize = 128;
23081 let last_word: String = snapshot
23082 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
23083 .take(MAX_WORD_PREFIX_LEN)
23084 .take_while(|c| classifier.is_word(*c))
23085 .collect::<String>()
23086 .chars()
23087 .rev()
23088 .collect();
23089
23090 if last_word.is_empty() {
23091 return Ok(CompletionResponse {
23092 completions: vec![],
23093 display_options: CompletionDisplayOptions::default(),
23094 is_incomplete: true,
23095 });
23096 }
23097
23098 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
23099 let to_lsp = |point: &text::Anchor| {
23100 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
23101 point_to_lsp(end)
23102 };
23103 let lsp_end = to_lsp(&buffer_position);
23104
23105 let candidates = snippets
23106 .iter()
23107 .enumerate()
23108 .flat_map(|(ix, snippet)| {
23109 snippet
23110 .prefix
23111 .iter()
23112 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
23113 })
23114 .collect::<Vec<StringMatchCandidate>>();
23115
23116 const MAX_RESULTS: usize = 100;
23117 let mut matches = fuzzy::match_strings(
23118 &candidates,
23119 &last_word,
23120 last_word.chars().any(|c| c.is_uppercase()),
23121 true,
23122 MAX_RESULTS,
23123 &Default::default(),
23124 executor.clone(),
23125 )
23126 .await;
23127
23128 if matches.len() >= MAX_RESULTS {
23129 is_incomplete = true;
23130 }
23131
23132 // Remove all candidates where the query's start does not match the start of any word in the candidate
23133 if let Some(query_start) = last_word.chars().next() {
23134 matches.retain(|string_match| {
23135 split_words(&string_match.string).any(|word| {
23136 // Check that the first codepoint of the word as lowercase matches the first
23137 // codepoint of the query as lowercase
23138 word.chars()
23139 .flat_map(|codepoint| codepoint.to_lowercase())
23140 .zip(query_start.to_lowercase())
23141 .all(|(word_cp, query_cp)| word_cp == query_cp)
23142 })
23143 });
23144 }
23145
23146 let matched_strings = matches
23147 .into_iter()
23148 .map(|m| m.string)
23149 .collect::<HashSet<_>>();
23150
23151 completions.extend(snippets.iter().filter_map(|snippet| {
23152 let matching_prefix = snippet
23153 .prefix
23154 .iter()
23155 .find(|prefix| matched_strings.contains(*prefix))?;
23156 let start = as_offset - last_word.len();
23157 let start = snapshot.anchor_before(start);
23158 let range = start..buffer_position;
23159 let lsp_start = to_lsp(&start);
23160 let lsp_range = lsp::Range {
23161 start: lsp_start,
23162 end: lsp_end,
23163 };
23164 Some(Completion {
23165 replace_range: range,
23166 new_text: snippet.body.clone(),
23167 source: CompletionSource::Lsp {
23168 insert_range: None,
23169 server_id: LanguageServerId(usize::MAX),
23170 resolved: true,
23171 lsp_completion: Box::new(lsp::CompletionItem {
23172 label: snippet.prefix.first().unwrap().clone(),
23173 kind: Some(CompletionItemKind::SNIPPET),
23174 label_details: snippet.description.as_ref().map(|description| {
23175 lsp::CompletionItemLabelDetails {
23176 detail: Some(description.clone()),
23177 description: None,
23178 }
23179 }),
23180 insert_text_format: Some(InsertTextFormat::SNIPPET),
23181 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23182 lsp::InsertReplaceEdit {
23183 new_text: snippet.body.clone(),
23184 insert: lsp_range,
23185 replace: lsp_range,
23186 },
23187 )),
23188 filter_text: Some(snippet.body.clone()),
23189 sort_text: Some(char::MAX.to_string()),
23190 ..lsp::CompletionItem::default()
23191 }),
23192 lsp_defaults: None,
23193 },
23194 label: CodeLabel::plain(matching_prefix.clone(), None),
23195 icon_path: None,
23196 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23197 single_line: snippet.name.clone().into(),
23198 plain_text: snippet
23199 .description
23200 .clone()
23201 .map(|description| description.into()),
23202 }),
23203 insert_text_mode: None,
23204 confirm: None,
23205 })
23206 }))
23207 }
23208
23209 Ok(CompletionResponse {
23210 completions,
23211 display_options: CompletionDisplayOptions::default(),
23212 is_incomplete,
23213 })
23214 })
23215}
23216
23217impl CompletionProvider for Entity<Project> {
23218 fn completions(
23219 &self,
23220 _excerpt_id: ExcerptId,
23221 buffer: &Entity<Buffer>,
23222 buffer_position: text::Anchor,
23223 options: CompletionContext,
23224 _window: &mut Window,
23225 cx: &mut Context<Editor>,
23226 ) -> Task<Result<Vec<CompletionResponse>>> {
23227 self.update(cx, |project, cx| {
23228 let snippets = snippet_completions(project, buffer, buffer_position, cx);
23229 let project_completions = project.completions(buffer, buffer_position, options, cx);
23230 cx.background_spawn(async move {
23231 let mut responses = project_completions.await?;
23232 let snippets = snippets.await?;
23233 if !snippets.completions.is_empty() {
23234 responses.push(snippets);
23235 }
23236 Ok(responses)
23237 })
23238 })
23239 }
23240
23241 fn resolve_completions(
23242 &self,
23243 buffer: Entity<Buffer>,
23244 completion_indices: Vec<usize>,
23245 completions: Rc<RefCell<Box<[Completion]>>>,
23246 cx: &mut Context<Editor>,
23247 ) -> Task<Result<bool>> {
23248 self.update(cx, |project, cx| {
23249 project.lsp_store().update(cx, |lsp_store, cx| {
23250 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23251 })
23252 })
23253 }
23254
23255 fn apply_additional_edits_for_completion(
23256 &self,
23257 buffer: Entity<Buffer>,
23258 completions: Rc<RefCell<Box<[Completion]>>>,
23259 completion_index: usize,
23260 push_to_history: bool,
23261 cx: &mut Context<Editor>,
23262 ) -> Task<Result<Option<language::Transaction>>> {
23263 self.update(cx, |project, cx| {
23264 project.lsp_store().update(cx, |lsp_store, cx| {
23265 lsp_store.apply_additional_edits_for_completion(
23266 buffer,
23267 completions,
23268 completion_index,
23269 push_to_history,
23270 cx,
23271 )
23272 })
23273 })
23274 }
23275
23276 fn is_completion_trigger(
23277 &self,
23278 buffer: &Entity<Buffer>,
23279 position: language::Anchor,
23280 text: &str,
23281 trigger_in_words: bool,
23282 menu_is_open: bool,
23283 cx: &mut Context<Editor>,
23284 ) -> bool {
23285 let mut chars = text.chars();
23286 let char = if let Some(char) = chars.next() {
23287 char
23288 } else {
23289 return false;
23290 };
23291 if chars.next().is_some() {
23292 return false;
23293 }
23294
23295 let buffer = buffer.read(cx);
23296 let snapshot = buffer.snapshot();
23297 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23298 return false;
23299 }
23300 let classifier = snapshot
23301 .char_classifier_at(position)
23302 .scope_context(Some(CharScopeContext::Completion));
23303 if trigger_in_words && classifier.is_word(char) {
23304 return true;
23305 }
23306
23307 buffer.completion_triggers().contains(text)
23308 }
23309}
23310
23311impl SemanticsProvider for Entity<Project> {
23312 fn hover(
23313 &self,
23314 buffer: &Entity<Buffer>,
23315 position: text::Anchor,
23316 cx: &mut App,
23317 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23318 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23319 }
23320
23321 fn document_highlights(
23322 &self,
23323 buffer: &Entity<Buffer>,
23324 position: text::Anchor,
23325 cx: &mut App,
23326 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23327 Some(self.update(cx, |project, cx| {
23328 project.document_highlights(buffer, position, cx)
23329 }))
23330 }
23331
23332 fn definitions(
23333 &self,
23334 buffer: &Entity<Buffer>,
23335 position: text::Anchor,
23336 kind: GotoDefinitionKind,
23337 cx: &mut App,
23338 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23339 Some(self.update(cx, |project, cx| match kind {
23340 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23341 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23342 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23343 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23344 }))
23345 }
23346
23347 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23348 self.update(cx, |project, cx| {
23349 if project
23350 .active_debug_session(cx)
23351 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23352 {
23353 return true;
23354 }
23355
23356 buffer.update(cx, |buffer, cx| {
23357 project.any_language_server_supports_inlay_hints(buffer, cx)
23358 })
23359 })
23360 }
23361
23362 fn inline_values(
23363 &self,
23364 buffer_handle: Entity<Buffer>,
23365 range: Range<text::Anchor>,
23366 cx: &mut App,
23367 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23368 self.update(cx, |project, cx| {
23369 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23370
23371 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23372 })
23373 }
23374
23375 fn inlay_hints(
23376 &self,
23377 buffer_handle: Entity<Buffer>,
23378 range: Range<text::Anchor>,
23379 cx: &mut App,
23380 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23381 Some(self.update(cx, |project, cx| {
23382 project.inlay_hints(buffer_handle, range, cx)
23383 }))
23384 }
23385
23386 fn resolve_inlay_hint(
23387 &self,
23388 hint: InlayHint,
23389 buffer_handle: Entity<Buffer>,
23390 server_id: LanguageServerId,
23391 cx: &mut App,
23392 ) -> Option<Task<anyhow::Result<InlayHint>>> {
23393 Some(self.update(cx, |project, cx| {
23394 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
23395 }))
23396 }
23397
23398 fn range_for_rename(
23399 &self,
23400 buffer: &Entity<Buffer>,
23401 position: text::Anchor,
23402 cx: &mut App,
23403 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23404 Some(self.update(cx, |project, cx| {
23405 let buffer = buffer.clone();
23406 let task = project.prepare_rename(buffer.clone(), position, cx);
23407 cx.spawn(async move |_, cx| {
23408 Ok(match task.await? {
23409 PrepareRenameResponse::Success(range) => Some(range),
23410 PrepareRenameResponse::InvalidPosition => None,
23411 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23412 // Fallback on using TreeSitter info to determine identifier range
23413 buffer.read_with(cx, |buffer, _| {
23414 let snapshot = buffer.snapshot();
23415 let (range, kind) = snapshot.surrounding_word(position, None);
23416 if kind != Some(CharKind::Word) {
23417 return None;
23418 }
23419 Some(
23420 snapshot.anchor_before(range.start)
23421 ..snapshot.anchor_after(range.end),
23422 )
23423 })?
23424 }
23425 })
23426 })
23427 }))
23428 }
23429
23430 fn perform_rename(
23431 &self,
23432 buffer: &Entity<Buffer>,
23433 position: text::Anchor,
23434 new_name: String,
23435 cx: &mut App,
23436 ) -> Option<Task<Result<ProjectTransaction>>> {
23437 Some(self.update(cx, |project, cx| {
23438 project.perform_rename(buffer.clone(), position, new_name, cx)
23439 }))
23440 }
23441}
23442
23443fn inlay_hint_settings(
23444 location: Anchor,
23445 snapshot: &MultiBufferSnapshot,
23446 cx: &mut Context<Editor>,
23447) -> InlayHintSettings {
23448 let file = snapshot.file_at(location);
23449 let language = snapshot.language_at(location).map(|l| l.name());
23450 language_settings(language, file, cx).inlay_hints
23451}
23452
23453fn consume_contiguous_rows(
23454 contiguous_row_selections: &mut Vec<Selection<Point>>,
23455 selection: &Selection<Point>,
23456 display_map: &DisplaySnapshot,
23457 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23458) -> (MultiBufferRow, MultiBufferRow) {
23459 contiguous_row_selections.push(selection.clone());
23460 let start_row = starting_row(selection, display_map);
23461 let mut end_row = ending_row(selection, display_map);
23462
23463 while let Some(next_selection) = selections.peek() {
23464 if next_selection.start.row <= end_row.0 {
23465 end_row = ending_row(next_selection, display_map);
23466 contiguous_row_selections.push(selections.next().unwrap().clone());
23467 } else {
23468 break;
23469 }
23470 }
23471 (start_row, end_row)
23472}
23473
23474fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23475 if selection.start.column > 0 {
23476 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23477 } else {
23478 MultiBufferRow(selection.start.row)
23479 }
23480}
23481
23482fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23483 if next_selection.end.column > 0 || next_selection.is_empty() {
23484 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23485 } else {
23486 MultiBufferRow(next_selection.end.row)
23487 }
23488}
23489
23490impl EditorSnapshot {
23491 pub fn remote_selections_in_range<'a>(
23492 &'a self,
23493 range: &'a Range<Anchor>,
23494 collaboration_hub: &dyn CollaborationHub,
23495 cx: &'a App,
23496 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23497 let participant_names = collaboration_hub.user_names(cx);
23498 let participant_indices = collaboration_hub.user_participant_indices(cx);
23499 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23500 let collaborators_by_replica_id = collaborators_by_peer_id
23501 .values()
23502 .map(|collaborator| (collaborator.replica_id, collaborator))
23503 .collect::<HashMap<_, _>>();
23504 self.buffer_snapshot()
23505 .selections_in_range(range, false)
23506 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23507 if replica_id == ReplicaId::AGENT {
23508 Some(RemoteSelection {
23509 replica_id,
23510 selection,
23511 cursor_shape,
23512 line_mode,
23513 collaborator_id: CollaboratorId::Agent,
23514 user_name: Some("Agent".into()),
23515 color: cx.theme().players().agent(),
23516 })
23517 } else {
23518 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23519 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23520 let user_name = participant_names.get(&collaborator.user_id).cloned();
23521 Some(RemoteSelection {
23522 replica_id,
23523 selection,
23524 cursor_shape,
23525 line_mode,
23526 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23527 user_name,
23528 color: if let Some(index) = participant_index {
23529 cx.theme().players().color_for_participant(index.0)
23530 } else {
23531 cx.theme().players().absent()
23532 },
23533 })
23534 }
23535 })
23536 }
23537
23538 pub fn hunks_for_ranges(
23539 &self,
23540 ranges: impl IntoIterator<Item = Range<Point>>,
23541 ) -> Vec<MultiBufferDiffHunk> {
23542 let mut hunks = Vec::new();
23543 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23544 HashMap::default();
23545 for query_range in ranges {
23546 let query_rows =
23547 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23548 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23549 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23550 ) {
23551 // Include deleted hunks that are adjacent to the query range, because
23552 // otherwise they would be missed.
23553 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23554 if hunk.status().is_deleted() {
23555 intersects_range |= hunk.row_range.start == query_rows.end;
23556 intersects_range |= hunk.row_range.end == query_rows.start;
23557 }
23558 if intersects_range {
23559 if !processed_buffer_rows
23560 .entry(hunk.buffer_id)
23561 .or_default()
23562 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23563 {
23564 continue;
23565 }
23566 hunks.push(hunk);
23567 }
23568 }
23569 }
23570
23571 hunks
23572 }
23573
23574 fn display_diff_hunks_for_rows<'a>(
23575 &'a self,
23576 display_rows: Range<DisplayRow>,
23577 folded_buffers: &'a HashSet<BufferId>,
23578 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23579 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23580 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23581
23582 self.buffer_snapshot()
23583 .diff_hunks_in_range(buffer_start..buffer_end)
23584 .filter_map(|hunk| {
23585 if folded_buffers.contains(&hunk.buffer_id) {
23586 return None;
23587 }
23588
23589 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23590 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23591
23592 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23593 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23594
23595 let display_hunk = if hunk_display_start.column() != 0 {
23596 DisplayDiffHunk::Folded {
23597 display_row: hunk_display_start.row(),
23598 }
23599 } else {
23600 let mut end_row = hunk_display_end.row();
23601 if hunk_display_end.column() > 0 {
23602 end_row.0 += 1;
23603 }
23604 let is_created_file = hunk.is_created_file();
23605 DisplayDiffHunk::Unfolded {
23606 status: hunk.status(),
23607 diff_base_byte_range: hunk.diff_base_byte_range,
23608 display_row_range: hunk_display_start.row()..end_row,
23609 multi_buffer_range: Anchor::range_in_buffer(
23610 hunk.excerpt_id,
23611 hunk.buffer_id,
23612 hunk.buffer_range,
23613 ),
23614 is_created_file,
23615 }
23616 };
23617
23618 Some(display_hunk)
23619 })
23620 }
23621
23622 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23623 self.display_snapshot
23624 .buffer_snapshot()
23625 .language_at(position)
23626 }
23627
23628 pub fn is_focused(&self) -> bool {
23629 self.is_focused
23630 }
23631
23632 pub fn placeholder_text(&self) -> Option<String> {
23633 self.placeholder_display_snapshot
23634 .as_ref()
23635 .map(|display_map| display_map.text())
23636 }
23637
23638 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23639 self.scroll_anchor.scroll_position(&self.display_snapshot)
23640 }
23641
23642 fn gutter_dimensions(
23643 &self,
23644 font_id: FontId,
23645 font_size: Pixels,
23646 max_line_number_width: Pixels,
23647 cx: &App,
23648 ) -> Option<GutterDimensions> {
23649 if !self.show_gutter {
23650 return None;
23651 }
23652
23653 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23654 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23655
23656 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23657 matches!(
23658 ProjectSettings::get_global(cx).git.git_gutter,
23659 GitGutterSetting::TrackedFiles
23660 )
23661 });
23662 let gutter_settings = EditorSettings::get_global(cx).gutter;
23663 let show_line_numbers = self
23664 .show_line_numbers
23665 .unwrap_or(gutter_settings.line_numbers);
23666 let line_gutter_width = if show_line_numbers {
23667 // Avoid flicker-like gutter resizes when the line number gains another digit by
23668 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23669 let min_width_for_number_on_gutter =
23670 ch_advance * gutter_settings.min_line_number_digits as f32;
23671 max_line_number_width.max(min_width_for_number_on_gutter)
23672 } else {
23673 0.0.into()
23674 };
23675
23676 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23677 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23678
23679 let git_blame_entries_width =
23680 self.git_blame_gutter_max_author_length
23681 .map(|max_author_length| {
23682 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23683 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23684
23685 /// The number of characters to dedicate to gaps and margins.
23686 const SPACING_WIDTH: usize = 4;
23687
23688 let max_char_count = max_author_length.min(renderer.max_author_length())
23689 + ::git::SHORT_SHA_LENGTH
23690 + MAX_RELATIVE_TIMESTAMP.len()
23691 + SPACING_WIDTH;
23692
23693 ch_advance * max_char_count
23694 });
23695
23696 let is_singleton = self.buffer_snapshot().is_singleton();
23697
23698 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23699 left_padding += if !is_singleton {
23700 ch_width * 4.0
23701 } else if show_runnables || show_breakpoints {
23702 ch_width * 3.0
23703 } else if show_git_gutter && show_line_numbers {
23704 ch_width * 2.0
23705 } else if show_git_gutter || show_line_numbers {
23706 ch_width
23707 } else {
23708 px(0.)
23709 };
23710
23711 let shows_folds = is_singleton && gutter_settings.folds;
23712
23713 let right_padding = if shows_folds && show_line_numbers {
23714 ch_width * 4.0
23715 } else if shows_folds || (!is_singleton && show_line_numbers) {
23716 ch_width * 3.0
23717 } else if show_line_numbers {
23718 ch_width
23719 } else {
23720 px(0.)
23721 };
23722
23723 Some(GutterDimensions {
23724 left_padding,
23725 right_padding,
23726 width: line_gutter_width + left_padding + right_padding,
23727 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23728 git_blame_entries_width,
23729 })
23730 }
23731
23732 pub fn render_crease_toggle(
23733 &self,
23734 buffer_row: MultiBufferRow,
23735 row_contains_cursor: bool,
23736 editor: Entity<Editor>,
23737 window: &mut Window,
23738 cx: &mut App,
23739 ) -> Option<AnyElement> {
23740 let folded = self.is_line_folded(buffer_row);
23741 let mut is_foldable = false;
23742
23743 if let Some(crease) = self
23744 .crease_snapshot
23745 .query_row(buffer_row, self.buffer_snapshot())
23746 {
23747 is_foldable = true;
23748 match crease {
23749 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23750 if let Some(render_toggle) = render_toggle {
23751 let toggle_callback =
23752 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23753 if folded {
23754 editor.update(cx, |editor, cx| {
23755 editor.fold_at(buffer_row, window, cx)
23756 });
23757 } else {
23758 editor.update(cx, |editor, cx| {
23759 editor.unfold_at(buffer_row, window, cx)
23760 });
23761 }
23762 });
23763 return Some((render_toggle)(
23764 buffer_row,
23765 folded,
23766 toggle_callback,
23767 window,
23768 cx,
23769 ));
23770 }
23771 }
23772 }
23773 }
23774
23775 is_foldable |= self.starts_indent(buffer_row);
23776
23777 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23778 Some(
23779 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23780 .toggle_state(folded)
23781 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23782 if folded {
23783 this.unfold_at(buffer_row, window, cx);
23784 } else {
23785 this.fold_at(buffer_row, window, cx);
23786 }
23787 }))
23788 .into_any_element(),
23789 )
23790 } else {
23791 None
23792 }
23793 }
23794
23795 pub fn render_crease_trailer(
23796 &self,
23797 buffer_row: MultiBufferRow,
23798 window: &mut Window,
23799 cx: &mut App,
23800 ) -> Option<AnyElement> {
23801 let folded = self.is_line_folded(buffer_row);
23802 if let Crease::Inline { render_trailer, .. } = self
23803 .crease_snapshot
23804 .query_row(buffer_row, self.buffer_snapshot())?
23805 {
23806 let render_trailer = render_trailer.as_ref()?;
23807 Some(render_trailer(buffer_row, folded, window, cx))
23808 } else {
23809 None
23810 }
23811 }
23812}
23813
23814impl Deref for EditorSnapshot {
23815 type Target = DisplaySnapshot;
23816
23817 fn deref(&self) -> &Self::Target {
23818 &self.display_snapshot
23819 }
23820}
23821
23822#[derive(Clone, Debug, PartialEq, Eq)]
23823pub enum EditorEvent {
23824 InputIgnored {
23825 text: Arc<str>,
23826 },
23827 InputHandled {
23828 utf16_range_to_replace: Option<Range<isize>>,
23829 text: Arc<str>,
23830 },
23831 ExcerptsAdded {
23832 buffer: Entity<Buffer>,
23833 predecessor: ExcerptId,
23834 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23835 },
23836 ExcerptsRemoved {
23837 ids: Vec<ExcerptId>,
23838 removed_buffer_ids: Vec<BufferId>,
23839 },
23840 BufferFoldToggled {
23841 ids: Vec<ExcerptId>,
23842 folded: bool,
23843 },
23844 ExcerptsEdited {
23845 ids: Vec<ExcerptId>,
23846 },
23847 ExcerptsExpanded {
23848 ids: Vec<ExcerptId>,
23849 },
23850 BufferEdited,
23851 Edited {
23852 transaction_id: clock::Lamport,
23853 },
23854 Reparsed(BufferId),
23855 Focused,
23856 FocusedIn,
23857 Blurred,
23858 DirtyChanged,
23859 Saved,
23860 TitleChanged,
23861 SelectionsChanged {
23862 local: bool,
23863 },
23864 ScrollPositionChanged {
23865 local: bool,
23866 autoscroll: bool,
23867 },
23868 TransactionUndone {
23869 transaction_id: clock::Lamport,
23870 },
23871 TransactionBegun {
23872 transaction_id: clock::Lamport,
23873 },
23874 CursorShapeChanged,
23875 BreadcrumbsChanged,
23876 PushedToNavHistory {
23877 anchor: Anchor,
23878 is_deactivate: bool,
23879 },
23880}
23881
23882impl EventEmitter<EditorEvent> for Editor {}
23883
23884impl Focusable for Editor {
23885 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23886 self.focus_handle.clone()
23887 }
23888}
23889
23890impl Render for Editor {
23891 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23892 let settings = ThemeSettings::get_global(cx);
23893
23894 let mut text_style = match self.mode {
23895 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23896 color: cx.theme().colors().editor_foreground,
23897 font_family: settings.ui_font.family.clone(),
23898 font_features: settings.ui_font.features.clone(),
23899 font_fallbacks: settings.ui_font.fallbacks.clone(),
23900 font_size: rems(0.875).into(),
23901 font_weight: settings.ui_font.weight,
23902 line_height: relative(settings.buffer_line_height.value()),
23903 ..Default::default()
23904 },
23905 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23906 color: cx.theme().colors().editor_foreground,
23907 font_family: settings.buffer_font.family.clone(),
23908 font_features: settings.buffer_font.features.clone(),
23909 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23910 font_size: settings.buffer_font_size(cx).into(),
23911 font_weight: settings.buffer_font.weight,
23912 line_height: relative(settings.buffer_line_height.value()),
23913 ..Default::default()
23914 },
23915 };
23916 if let Some(text_style_refinement) = &self.text_style_refinement {
23917 text_style.refine(text_style_refinement)
23918 }
23919
23920 let background = match self.mode {
23921 EditorMode::SingleLine => cx.theme().system().transparent,
23922 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23923 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23924 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23925 };
23926
23927 EditorElement::new(
23928 &cx.entity(),
23929 EditorStyle {
23930 background,
23931 border: cx.theme().colors().border,
23932 local_player: cx.theme().players().local(),
23933 text: text_style,
23934 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23935 syntax: cx.theme().syntax().clone(),
23936 status: cx.theme().status().clone(),
23937 inlay_hints_style: make_inlay_hints_style(cx),
23938 edit_prediction_styles: make_suggestion_styles(cx),
23939 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23940 show_underlines: self.diagnostics_enabled(),
23941 },
23942 )
23943 }
23944}
23945
23946impl EntityInputHandler for Editor {
23947 fn text_for_range(
23948 &mut self,
23949 range_utf16: Range<usize>,
23950 adjusted_range: &mut Option<Range<usize>>,
23951 _: &mut Window,
23952 cx: &mut Context<Self>,
23953 ) -> Option<String> {
23954 let snapshot = self.buffer.read(cx).read(cx);
23955 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23956 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23957 if (start.0..end.0) != range_utf16 {
23958 adjusted_range.replace(start.0..end.0);
23959 }
23960 Some(snapshot.text_for_range(start..end).collect())
23961 }
23962
23963 fn selected_text_range(
23964 &mut self,
23965 ignore_disabled_input: bool,
23966 _: &mut Window,
23967 cx: &mut Context<Self>,
23968 ) -> Option<UTF16Selection> {
23969 // Prevent the IME menu from appearing when holding down an alphabetic key
23970 // while input is disabled.
23971 if !ignore_disabled_input && !self.input_enabled {
23972 return None;
23973 }
23974
23975 let selection = self
23976 .selections
23977 .newest::<OffsetUtf16>(&self.display_snapshot(cx));
23978 let range = selection.range();
23979
23980 Some(UTF16Selection {
23981 range: range.start.0..range.end.0,
23982 reversed: selection.reversed,
23983 })
23984 }
23985
23986 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23987 let snapshot = self.buffer.read(cx).read(cx);
23988 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23989 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23990 }
23991
23992 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23993 self.clear_highlights::<InputComposition>(cx);
23994 self.ime_transaction.take();
23995 }
23996
23997 fn replace_text_in_range(
23998 &mut self,
23999 range_utf16: Option<Range<usize>>,
24000 text: &str,
24001 window: &mut Window,
24002 cx: &mut Context<Self>,
24003 ) {
24004 if !self.input_enabled {
24005 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24006 return;
24007 }
24008
24009 self.transact(window, cx, |this, window, cx| {
24010 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
24011 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24012 Some(this.selection_replacement_ranges(range_utf16, cx))
24013 } else {
24014 this.marked_text_ranges(cx)
24015 };
24016
24017 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
24018 let newest_selection_id = this.selections.newest_anchor().id;
24019 this.selections
24020 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24021 .iter()
24022 .zip(ranges_to_replace.iter())
24023 .find_map(|(selection, range)| {
24024 if selection.id == newest_selection_id {
24025 Some(
24026 (range.start.0 as isize - selection.head().0 as isize)
24027 ..(range.end.0 as isize - selection.head().0 as isize),
24028 )
24029 } else {
24030 None
24031 }
24032 })
24033 });
24034
24035 cx.emit(EditorEvent::InputHandled {
24036 utf16_range_to_replace: range_to_replace,
24037 text: text.into(),
24038 });
24039
24040 if let Some(new_selected_ranges) = new_selected_ranges {
24041 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24042 selections.select_ranges(new_selected_ranges)
24043 });
24044 this.backspace(&Default::default(), window, cx);
24045 }
24046
24047 this.handle_input(text, window, cx);
24048 });
24049
24050 if let Some(transaction) = self.ime_transaction {
24051 self.buffer.update(cx, |buffer, cx| {
24052 buffer.group_until_transaction(transaction, cx);
24053 });
24054 }
24055
24056 self.unmark_text(window, cx);
24057 }
24058
24059 fn replace_and_mark_text_in_range(
24060 &mut self,
24061 range_utf16: Option<Range<usize>>,
24062 text: &str,
24063 new_selected_range_utf16: Option<Range<usize>>,
24064 window: &mut Window,
24065 cx: &mut Context<Self>,
24066 ) {
24067 if !self.input_enabled {
24068 return;
24069 }
24070
24071 let transaction = self.transact(window, cx, |this, window, cx| {
24072 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
24073 let snapshot = this.buffer.read(cx).read(cx);
24074 if let Some(relative_range_utf16) = range_utf16.as_ref() {
24075 for marked_range in &mut marked_ranges {
24076 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
24077 marked_range.start.0 += relative_range_utf16.start;
24078 marked_range.start =
24079 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
24080 marked_range.end =
24081 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
24082 }
24083 }
24084 Some(marked_ranges)
24085 } else if let Some(range_utf16) = range_utf16 {
24086 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24087 Some(this.selection_replacement_ranges(range_utf16, cx))
24088 } else {
24089 None
24090 };
24091
24092 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
24093 let newest_selection_id = this.selections.newest_anchor().id;
24094 this.selections
24095 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24096 .iter()
24097 .zip(ranges_to_replace.iter())
24098 .find_map(|(selection, range)| {
24099 if selection.id == newest_selection_id {
24100 Some(
24101 (range.start.0 as isize - selection.head().0 as isize)
24102 ..(range.end.0 as isize - selection.head().0 as isize),
24103 )
24104 } else {
24105 None
24106 }
24107 })
24108 });
24109
24110 cx.emit(EditorEvent::InputHandled {
24111 utf16_range_to_replace: range_to_replace,
24112 text: text.into(),
24113 });
24114
24115 if let Some(ranges) = ranges_to_replace {
24116 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24117 s.select_ranges(ranges)
24118 });
24119 }
24120
24121 let marked_ranges = {
24122 let snapshot = this.buffer.read(cx).read(cx);
24123 this.selections
24124 .disjoint_anchors_arc()
24125 .iter()
24126 .map(|selection| {
24127 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24128 })
24129 .collect::<Vec<_>>()
24130 };
24131
24132 if text.is_empty() {
24133 this.unmark_text(window, cx);
24134 } else {
24135 this.highlight_text::<InputComposition>(
24136 marked_ranges.clone(),
24137 HighlightStyle {
24138 underline: Some(UnderlineStyle {
24139 thickness: px(1.),
24140 color: None,
24141 wavy: false,
24142 }),
24143 ..Default::default()
24144 },
24145 cx,
24146 );
24147 }
24148
24149 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24150 let use_autoclose = this.use_autoclose;
24151 let use_auto_surround = this.use_auto_surround;
24152 this.set_use_autoclose(false);
24153 this.set_use_auto_surround(false);
24154 this.handle_input(text, window, cx);
24155 this.set_use_autoclose(use_autoclose);
24156 this.set_use_auto_surround(use_auto_surround);
24157
24158 if let Some(new_selected_range) = new_selected_range_utf16 {
24159 let snapshot = this.buffer.read(cx).read(cx);
24160 let new_selected_ranges = marked_ranges
24161 .into_iter()
24162 .map(|marked_range| {
24163 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24164 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
24165 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
24166 snapshot.clip_offset_utf16(new_start, Bias::Left)
24167 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24168 })
24169 .collect::<Vec<_>>();
24170
24171 drop(snapshot);
24172 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24173 selections.select_ranges(new_selected_ranges)
24174 });
24175 }
24176 });
24177
24178 self.ime_transaction = self.ime_transaction.or(transaction);
24179 if let Some(transaction) = self.ime_transaction {
24180 self.buffer.update(cx, |buffer, cx| {
24181 buffer.group_until_transaction(transaction, cx);
24182 });
24183 }
24184
24185 if self.text_highlights::<InputComposition>(cx).is_none() {
24186 self.ime_transaction.take();
24187 }
24188 }
24189
24190 fn bounds_for_range(
24191 &mut self,
24192 range_utf16: Range<usize>,
24193 element_bounds: gpui::Bounds<Pixels>,
24194 window: &mut Window,
24195 cx: &mut Context<Self>,
24196 ) -> Option<gpui::Bounds<Pixels>> {
24197 let text_layout_details = self.text_layout_details(window);
24198 let CharacterDimensions {
24199 em_width,
24200 em_advance,
24201 line_height,
24202 } = self.character_dimensions(window);
24203
24204 let snapshot = self.snapshot(window, cx);
24205 let scroll_position = snapshot.scroll_position();
24206 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24207
24208 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
24209 let x = Pixels::from(
24210 ScrollOffset::from(
24211 snapshot.x_for_display_point(start, &text_layout_details)
24212 + self.gutter_dimensions.full_width(),
24213 ) - scroll_left,
24214 );
24215 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24216
24217 Some(Bounds {
24218 origin: element_bounds.origin + point(x, y),
24219 size: size(em_width, line_height),
24220 })
24221 }
24222
24223 fn character_index_for_point(
24224 &mut self,
24225 point: gpui::Point<Pixels>,
24226 _window: &mut Window,
24227 _cx: &mut Context<Self>,
24228 ) -> Option<usize> {
24229 let position_map = self.last_position_map.as_ref()?;
24230 if !position_map.text_hitbox.contains(&point) {
24231 return None;
24232 }
24233 let display_point = position_map.point_for_position(point).previous_valid;
24234 let anchor = position_map
24235 .snapshot
24236 .display_point_to_anchor(display_point, Bias::Left);
24237 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24238 Some(utf16_offset.0)
24239 }
24240}
24241
24242trait SelectionExt {
24243 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24244 fn spanned_rows(
24245 &self,
24246 include_end_if_at_line_start: bool,
24247 map: &DisplaySnapshot,
24248 ) -> Range<MultiBufferRow>;
24249}
24250
24251impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24252 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24253 let start = self
24254 .start
24255 .to_point(map.buffer_snapshot())
24256 .to_display_point(map);
24257 let end = self
24258 .end
24259 .to_point(map.buffer_snapshot())
24260 .to_display_point(map);
24261 if self.reversed {
24262 end..start
24263 } else {
24264 start..end
24265 }
24266 }
24267
24268 fn spanned_rows(
24269 &self,
24270 include_end_if_at_line_start: bool,
24271 map: &DisplaySnapshot,
24272 ) -> Range<MultiBufferRow> {
24273 let start = self.start.to_point(map.buffer_snapshot());
24274 let mut end = self.end.to_point(map.buffer_snapshot());
24275 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24276 end.row -= 1;
24277 }
24278
24279 let buffer_start = map.prev_line_boundary(start).0;
24280 let buffer_end = map.next_line_boundary(end).0;
24281 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24282 }
24283}
24284
24285impl<T: InvalidationRegion> InvalidationStack<T> {
24286 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24287 where
24288 S: Clone + ToOffset,
24289 {
24290 while let Some(region) = self.last() {
24291 let all_selections_inside_invalidation_ranges =
24292 if selections.len() == region.ranges().len() {
24293 selections
24294 .iter()
24295 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24296 .all(|(selection, invalidation_range)| {
24297 let head = selection.head().to_offset(buffer);
24298 invalidation_range.start <= head && invalidation_range.end >= head
24299 })
24300 } else {
24301 false
24302 };
24303
24304 if all_selections_inside_invalidation_ranges {
24305 break;
24306 } else {
24307 self.pop();
24308 }
24309 }
24310 }
24311}
24312
24313impl<T> Default for InvalidationStack<T> {
24314 fn default() -> Self {
24315 Self(Default::default())
24316 }
24317}
24318
24319impl<T> Deref for InvalidationStack<T> {
24320 type Target = Vec<T>;
24321
24322 fn deref(&self) -> &Self::Target {
24323 &self.0
24324 }
24325}
24326
24327impl<T> DerefMut for InvalidationStack<T> {
24328 fn deref_mut(&mut self) -> &mut Self::Target {
24329 &mut self.0
24330 }
24331}
24332
24333impl InvalidationRegion for SnippetState {
24334 fn ranges(&self) -> &[Range<Anchor>] {
24335 &self.ranges[self.active_index]
24336 }
24337}
24338
24339fn edit_prediction_edit_text(
24340 current_snapshot: &BufferSnapshot,
24341 edits: &[(Range<Anchor>, String)],
24342 edit_preview: &EditPreview,
24343 include_deletions: bool,
24344 cx: &App,
24345) -> HighlightedText {
24346 let edits = edits
24347 .iter()
24348 .map(|(anchor, text)| {
24349 (
24350 anchor.start.text_anchor..anchor.end.text_anchor,
24351 text.clone(),
24352 )
24353 })
24354 .collect::<Vec<_>>();
24355
24356 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24357}
24358
24359fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
24360 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24361 // Just show the raw edit text with basic styling
24362 let mut text = String::new();
24363 let mut highlights = Vec::new();
24364
24365 let insertion_highlight_style = HighlightStyle {
24366 color: Some(cx.theme().colors().text),
24367 ..Default::default()
24368 };
24369
24370 for (_, edit_text) in edits {
24371 let start_offset = text.len();
24372 text.push_str(edit_text);
24373 let end_offset = text.len();
24374
24375 if start_offset < end_offset {
24376 highlights.push((start_offset..end_offset, insertion_highlight_style));
24377 }
24378 }
24379
24380 HighlightedText {
24381 text: text.into(),
24382 highlights,
24383 }
24384}
24385
24386pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24387 match severity {
24388 lsp::DiagnosticSeverity::ERROR => colors.error,
24389 lsp::DiagnosticSeverity::WARNING => colors.warning,
24390 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24391 lsp::DiagnosticSeverity::HINT => colors.info,
24392 _ => colors.ignored,
24393 }
24394}
24395
24396pub fn styled_runs_for_code_label<'a>(
24397 label: &'a CodeLabel,
24398 syntax_theme: &'a theme::SyntaxTheme,
24399) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24400 let fade_out = HighlightStyle {
24401 fade_out: Some(0.35),
24402 ..Default::default()
24403 };
24404
24405 let mut prev_end = label.filter_range.end;
24406 label
24407 .runs
24408 .iter()
24409 .enumerate()
24410 .flat_map(move |(ix, (range, highlight_id))| {
24411 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24412 style
24413 } else {
24414 return Default::default();
24415 };
24416 let muted_style = style.highlight(fade_out);
24417
24418 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24419 if range.start >= label.filter_range.end {
24420 if range.start > prev_end {
24421 runs.push((prev_end..range.start, fade_out));
24422 }
24423 runs.push((range.clone(), muted_style));
24424 } else if range.end <= label.filter_range.end {
24425 runs.push((range.clone(), style));
24426 } else {
24427 runs.push((range.start..label.filter_range.end, style));
24428 runs.push((label.filter_range.end..range.end, muted_style));
24429 }
24430 prev_end = cmp::max(prev_end, range.end);
24431
24432 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24433 runs.push((prev_end..label.text.len(), fade_out));
24434 }
24435
24436 runs
24437 })
24438}
24439
24440pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24441 let mut prev_index = 0;
24442 let mut prev_codepoint: Option<char> = None;
24443 text.char_indices()
24444 .chain([(text.len(), '\0')])
24445 .filter_map(move |(index, codepoint)| {
24446 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24447 let is_boundary = index == text.len()
24448 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24449 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24450 if is_boundary {
24451 let chunk = &text[prev_index..index];
24452 prev_index = index;
24453 Some(chunk)
24454 } else {
24455 None
24456 }
24457 })
24458}
24459
24460pub trait RangeToAnchorExt: Sized {
24461 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24462
24463 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24464 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24465 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24466 }
24467}
24468
24469impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24470 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24471 let start_offset = self.start.to_offset(snapshot);
24472 let end_offset = self.end.to_offset(snapshot);
24473 if start_offset == end_offset {
24474 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24475 } else {
24476 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24477 }
24478 }
24479}
24480
24481pub trait RowExt {
24482 fn as_f64(&self) -> f64;
24483
24484 fn next_row(&self) -> Self;
24485
24486 fn previous_row(&self) -> Self;
24487
24488 fn minus(&self, other: Self) -> u32;
24489}
24490
24491impl RowExt for DisplayRow {
24492 fn as_f64(&self) -> f64 {
24493 self.0 as _
24494 }
24495
24496 fn next_row(&self) -> Self {
24497 Self(self.0 + 1)
24498 }
24499
24500 fn previous_row(&self) -> Self {
24501 Self(self.0.saturating_sub(1))
24502 }
24503
24504 fn minus(&self, other: Self) -> u32 {
24505 self.0 - other.0
24506 }
24507}
24508
24509impl RowExt for MultiBufferRow {
24510 fn as_f64(&self) -> f64 {
24511 self.0 as _
24512 }
24513
24514 fn next_row(&self) -> Self {
24515 Self(self.0 + 1)
24516 }
24517
24518 fn previous_row(&self) -> Self {
24519 Self(self.0.saturating_sub(1))
24520 }
24521
24522 fn minus(&self, other: Self) -> u32 {
24523 self.0 - other.0
24524 }
24525}
24526
24527trait RowRangeExt {
24528 type Row;
24529
24530 fn len(&self) -> usize;
24531
24532 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24533}
24534
24535impl RowRangeExt for Range<MultiBufferRow> {
24536 type Row = MultiBufferRow;
24537
24538 fn len(&self) -> usize {
24539 (self.end.0 - self.start.0) as usize
24540 }
24541
24542 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24543 (self.start.0..self.end.0).map(MultiBufferRow)
24544 }
24545}
24546
24547impl RowRangeExt for Range<DisplayRow> {
24548 type Row = DisplayRow;
24549
24550 fn len(&self) -> usize {
24551 (self.end.0 - self.start.0) as usize
24552 }
24553
24554 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24555 (self.start.0..self.end.0).map(DisplayRow)
24556 }
24557}
24558
24559/// If select range has more than one line, we
24560/// just point the cursor to range.start.
24561fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24562 if range.start.row == range.end.row {
24563 range
24564 } else {
24565 range.start..range.start
24566 }
24567}
24568pub struct KillRing(ClipboardItem);
24569impl Global for KillRing {}
24570
24571const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24572
24573enum BreakpointPromptEditAction {
24574 Log,
24575 Condition,
24576 HitCondition,
24577}
24578
24579struct BreakpointPromptEditor {
24580 pub(crate) prompt: Entity<Editor>,
24581 editor: WeakEntity<Editor>,
24582 breakpoint_anchor: Anchor,
24583 breakpoint: Breakpoint,
24584 edit_action: BreakpointPromptEditAction,
24585 block_ids: HashSet<CustomBlockId>,
24586 editor_margins: Arc<Mutex<EditorMargins>>,
24587 _subscriptions: Vec<Subscription>,
24588}
24589
24590impl BreakpointPromptEditor {
24591 const MAX_LINES: u8 = 4;
24592
24593 fn new(
24594 editor: WeakEntity<Editor>,
24595 breakpoint_anchor: Anchor,
24596 breakpoint: Breakpoint,
24597 edit_action: BreakpointPromptEditAction,
24598 window: &mut Window,
24599 cx: &mut Context<Self>,
24600 ) -> Self {
24601 let base_text = match edit_action {
24602 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24603 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24604 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24605 }
24606 .map(|msg| msg.to_string())
24607 .unwrap_or_default();
24608
24609 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24610 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24611
24612 let prompt = cx.new(|cx| {
24613 let mut prompt = Editor::new(
24614 EditorMode::AutoHeight {
24615 min_lines: 1,
24616 max_lines: Some(Self::MAX_LINES as usize),
24617 },
24618 buffer,
24619 None,
24620 window,
24621 cx,
24622 );
24623 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24624 prompt.set_show_cursor_when_unfocused(false, cx);
24625 prompt.set_placeholder_text(
24626 match edit_action {
24627 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24628 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24629 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24630 },
24631 window,
24632 cx,
24633 );
24634
24635 prompt
24636 });
24637
24638 Self {
24639 prompt,
24640 editor,
24641 breakpoint_anchor,
24642 breakpoint,
24643 edit_action,
24644 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24645 block_ids: Default::default(),
24646 _subscriptions: vec![],
24647 }
24648 }
24649
24650 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24651 self.block_ids.extend(block_ids)
24652 }
24653
24654 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24655 if let Some(editor) = self.editor.upgrade() {
24656 let message = self
24657 .prompt
24658 .read(cx)
24659 .buffer
24660 .read(cx)
24661 .as_singleton()
24662 .expect("A multi buffer in breakpoint prompt isn't possible")
24663 .read(cx)
24664 .as_rope()
24665 .to_string();
24666
24667 editor.update(cx, |editor, cx| {
24668 editor.edit_breakpoint_at_anchor(
24669 self.breakpoint_anchor,
24670 self.breakpoint.clone(),
24671 match self.edit_action {
24672 BreakpointPromptEditAction::Log => {
24673 BreakpointEditAction::EditLogMessage(message.into())
24674 }
24675 BreakpointPromptEditAction::Condition => {
24676 BreakpointEditAction::EditCondition(message.into())
24677 }
24678 BreakpointPromptEditAction::HitCondition => {
24679 BreakpointEditAction::EditHitCondition(message.into())
24680 }
24681 },
24682 cx,
24683 );
24684
24685 editor.remove_blocks(self.block_ids.clone(), None, cx);
24686 cx.focus_self(window);
24687 });
24688 }
24689 }
24690
24691 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24692 self.editor
24693 .update(cx, |editor, cx| {
24694 editor.remove_blocks(self.block_ids.clone(), None, cx);
24695 window.focus(&editor.focus_handle);
24696 })
24697 .log_err();
24698 }
24699
24700 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24701 let settings = ThemeSettings::get_global(cx);
24702 let text_style = TextStyle {
24703 color: if self.prompt.read(cx).read_only(cx) {
24704 cx.theme().colors().text_disabled
24705 } else {
24706 cx.theme().colors().text
24707 },
24708 font_family: settings.buffer_font.family.clone(),
24709 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24710 font_size: settings.buffer_font_size(cx).into(),
24711 font_weight: settings.buffer_font.weight,
24712 line_height: relative(settings.buffer_line_height.value()),
24713 ..Default::default()
24714 };
24715 EditorElement::new(
24716 &self.prompt,
24717 EditorStyle {
24718 background: cx.theme().colors().editor_background,
24719 local_player: cx.theme().players().local(),
24720 text: text_style,
24721 ..Default::default()
24722 },
24723 )
24724 }
24725}
24726
24727impl Render for BreakpointPromptEditor {
24728 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24729 let editor_margins = *self.editor_margins.lock();
24730 let gutter_dimensions = editor_margins.gutter;
24731 h_flex()
24732 .key_context("Editor")
24733 .bg(cx.theme().colors().editor_background)
24734 .border_y_1()
24735 .border_color(cx.theme().status().info_border)
24736 .size_full()
24737 .py(window.line_height() / 2.5)
24738 .on_action(cx.listener(Self::confirm))
24739 .on_action(cx.listener(Self::cancel))
24740 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24741 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24742 }
24743}
24744
24745impl Focusable for BreakpointPromptEditor {
24746 fn focus_handle(&self, cx: &App) -> FocusHandle {
24747 self.prompt.focus_handle(cx)
24748 }
24749}
24750
24751fn all_edits_insertions_or_deletions(
24752 edits: &Vec<(Range<Anchor>, String)>,
24753 snapshot: &MultiBufferSnapshot,
24754) -> bool {
24755 let mut all_insertions = true;
24756 let mut all_deletions = true;
24757
24758 for (range, new_text) in edits.iter() {
24759 let range_is_empty = range.to_offset(snapshot).is_empty();
24760 let text_is_empty = new_text.is_empty();
24761
24762 if range_is_empty != text_is_empty {
24763 if range_is_empty {
24764 all_deletions = false;
24765 } else {
24766 all_insertions = false;
24767 }
24768 } else {
24769 return false;
24770 }
24771
24772 if !all_insertions && !all_deletions {
24773 return false;
24774 }
24775 }
24776 all_insertions || all_deletions
24777}
24778
24779struct MissingEditPredictionKeybindingTooltip;
24780
24781impl Render for MissingEditPredictionKeybindingTooltip {
24782 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24783 ui::tooltip_container(cx, |container, cx| {
24784 container
24785 .flex_shrink_0()
24786 .max_w_80()
24787 .min_h(rems_from_px(124.))
24788 .justify_between()
24789 .child(
24790 v_flex()
24791 .flex_1()
24792 .text_ui_sm(cx)
24793 .child(Label::new("Conflict with Accept Keybinding"))
24794 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24795 )
24796 .child(
24797 h_flex()
24798 .pb_1()
24799 .gap_1()
24800 .items_end()
24801 .w_full()
24802 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24803 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
24804 }))
24805 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24806 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24807 })),
24808 )
24809 })
24810 }
24811}
24812
24813#[derive(Debug, Clone, Copy, PartialEq)]
24814pub struct LineHighlight {
24815 pub background: Background,
24816 pub border: Option<gpui::Hsla>,
24817 pub include_gutter: bool,
24818 pub type_id: Option<TypeId>,
24819}
24820
24821struct LineManipulationResult {
24822 pub new_text: String,
24823 pub line_count_before: usize,
24824 pub line_count_after: usize,
24825}
24826
24827fn render_diff_hunk_controls(
24828 row: u32,
24829 status: &DiffHunkStatus,
24830 hunk_range: Range<Anchor>,
24831 is_created_file: bool,
24832 line_height: Pixels,
24833 editor: &Entity<Editor>,
24834 _window: &mut Window,
24835 cx: &mut App,
24836) -> AnyElement {
24837 h_flex()
24838 .h(line_height)
24839 .mr_1()
24840 .gap_1()
24841 .px_0p5()
24842 .pb_1()
24843 .border_x_1()
24844 .border_b_1()
24845 .border_color(cx.theme().colors().border_variant)
24846 .rounded_b_lg()
24847 .bg(cx.theme().colors().editor_background)
24848 .gap_1()
24849 .block_mouse_except_scroll()
24850 .shadow_md()
24851 .child(if status.has_secondary_hunk() {
24852 Button::new(("stage", row as u64), "Stage")
24853 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24854 .tooltip({
24855 let focus_handle = editor.focus_handle(cx);
24856 move |window, cx| {
24857 Tooltip::for_action_in(
24858 "Stage Hunk",
24859 &::git::ToggleStaged,
24860 &focus_handle,
24861 window,
24862 cx,
24863 )
24864 }
24865 })
24866 .on_click({
24867 let editor = editor.clone();
24868 move |_event, _window, cx| {
24869 editor.update(cx, |editor, cx| {
24870 editor.stage_or_unstage_diff_hunks(
24871 true,
24872 vec![hunk_range.start..hunk_range.start],
24873 cx,
24874 );
24875 });
24876 }
24877 })
24878 } else {
24879 Button::new(("unstage", row as u64), "Unstage")
24880 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24881 .tooltip({
24882 let focus_handle = editor.focus_handle(cx);
24883 move |window, cx| {
24884 Tooltip::for_action_in(
24885 "Unstage Hunk",
24886 &::git::ToggleStaged,
24887 &focus_handle,
24888 window,
24889 cx,
24890 )
24891 }
24892 })
24893 .on_click({
24894 let editor = editor.clone();
24895 move |_event, _window, cx| {
24896 editor.update(cx, |editor, cx| {
24897 editor.stage_or_unstage_diff_hunks(
24898 false,
24899 vec![hunk_range.start..hunk_range.start],
24900 cx,
24901 );
24902 });
24903 }
24904 })
24905 })
24906 .child(
24907 Button::new(("restore", row as u64), "Restore")
24908 .tooltip({
24909 let focus_handle = editor.focus_handle(cx);
24910 move |window, cx| {
24911 Tooltip::for_action_in(
24912 "Restore Hunk",
24913 &::git::Restore,
24914 &focus_handle,
24915 window,
24916 cx,
24917 )
24918 }
24919 })
24920 .on_click({
24921 let editor = editor.clone();
24922 move |_event, window, cx| {
24923 editor.update(cx, |editor, cx| {
24924 let snapshot = editor.snapshot(window, cx);
24925 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
24926 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24927 });
24928 }
24929 })
24930 .disabled(is_created_file),
24931 )
24932 .when(
24933 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24934 |el| {
24935 el.child(
24936 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24937 .shape(IconButtonShape::Square)
24938 .icon_size(IconSize::Small)
24939 // .disabled(!has_multiple_hunks)
24940 .tooltip({
24941 let focus_handle = editor.focus_handle(cx);
24942 move |window, cx| {
24943 Tooltip::for_action_in(
24944 "Next Hunk",
24945 &GoToHunk,
24946 &focus_handle,
24947 window,
24948 cx,
24949 )
24950 }
24951 })
24952 .on_click({
24953 let editor = editor.clone();
24954 move |_event, window, cx| {
24955 editor.update(cx, |editor, cx| {
24956 let snapshot = editor.snapshot(window, cx);
24957 let position =
24958 hunk_range.end.to_point(&snapshot.buffer_snapshot());
24959 editor.go_to_hunk_before_or_after_position(
24960 &snapshot,
24961 position,
24962 Direction::Next,
24963 window,
24964 cx,
24965 );
24966 editor.expand_selected_diff_hunks(cx);
24967 });
24968 }
24969 }),
24970 )
24971 .child(
24972 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24973 .shape(IconButtonShape::Square)
24974 .icon_size(IconSize::Small)
24975 // .disabled(!has_multiple_hunks)
24976 .tooltip({
24977 let focus_handle = editor.focus_handle(cx);
24978 move |window, cx| {
24979 Tooltip::for_action_in(
24980 "Previous Hunk",
24981 &GoToPreviousHunk,
24982 &focus_handle,
24983 window,
24984 cx,
24985 )
24986 }
24987 })
24988 .on_click({
24989 let editor = editor.clone();
24990 move |_event, window, cx| {
24991 editor.update(cx, |editor, cx| {
24992 let snapshot = editor.snapshot(window, cx);
24993 let point =
24994 hunk_range.start.to_point(&snapshot.buffer_snapshot());
24995 editor.go_to_hunk_before_or_after_position(
24996 &snapshot,
24997 point,
24998 Direction::Prev,
24999 window,
25000 cx,
25001 );
25002 editor.expand_selected_diff_hunks(cx);
25003 });
25004 }
25005 }),
25006 )
25007 },
25008 )
25009 .into_any_element()
25010}
25011
25012pub fn multibuffer_context_lines(cx: &App) -> u32 {
25013 EditorSettings::try_get(cx)
25014 .map(|settings| settings.excerpt_context_lines)
25015 .unwrap_or(2)
25016 .min(32)
25017}