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::{AGENT_REPLICA_ID, 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_vertical);
362 workspace.register_action(Editor::new_file_horizontal);
363 workspace.register_action(Editor::cancel_language_server_work);
364 workspace.register_action(Editor::toggle_focus);
365 },
366 )
367 .detach();
368
369 cx.on_action(move |_: &workspace::NewFile, cx| {
370 let app_state = workspace::AppState::global(cx);
371 if let Some(app_state) = app_state.upgrade() {
372 workspace::open_new(
373 Default::default(),
374 app_state,
375 cx,
376 |workspace, window, cx| {
377 Editor::new_file(workspace, &Default::default(), window, cx)
378 },
379 )
380 .detach();
381 }
382 });
383 cx.on_action(move |_: &workspace::NewWindow, cx| {
384 let app_state = workspace::AppState::global(cx);
385 if let Some(app_state) = app_state.upgrade() {
386 workspace::open_new(
387 Default::default(),
388 app_state,
389 cx,
390 |workspace, window, cx| {
391 cx.activate(true);
392 Editor::new_file(workspace, &Default::default(), window, cx)
393 },
394 )
395 .detach();
396 }
397 });
398}
399
400pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
401 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
402}
403
404pub trait DiagnosticRenderer {
405 fn render_group(
406 &self,
407 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
408 buffer_id: BufferId,
409 snapshot: EditorSnapshot,
410 editor: WeakEntity<Editor>,
411 cx: &mut App,
412 ) -> Vec<BlockProperties<Anchor>>;
413
414 fn render_hover(
415 &self,
416 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
417 range: Range<Point>,
418 buffer_id: BufferId,
419 cx: &mut App,
420 ) -> Option<Entity<markdown::Markdown>>;
421
422 fn open_link(
423 &self,
424 editor: &mut Editor,
425 link: SharedString,
426 window: &mut Window,
427 cx: &mut Context<Editor>,
428 );
429}
430
431pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
432
433impl GlobalDiagnosticRenderer {
434 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
435 cx.try_global::<Self>().map(|g| g.0.clone())
436 }
437}
438
439impl gpui::Global for GlobalDiagnosticRenderer {}
440pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
441 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
442}
443
444pub struct SearchWithinRange;
445
446trait InvalidationRegion {
447 fn ranges(&self) -> &[Range<Anchor>];
448}
449
450#[derive(Clone, Debug, PartialEq)]
451pub enum SelectPhase {
452 Begin {
453 position: DisplayPoint,
454 add: bool,
455 click_count: usize,
456 },
457 BeginColumnar {
458 position: DisplayPoint,
459 reset: bool,
460 mode: ColumnarMode,
461 goal_column: u32,
462 },
463 Extend {
464 position: DisplayPoint,
465 click_count: usize,
466 },
467 Update {
468 position: DisplayPoint,
469 goal_column: u32,
470 scroll_delta: gpui::Point<f32>,
471 },
472 End,
473}
474
475#[derive(Clone, Debug, PartialEq)]
476pub enum ColumnarMode {
477 FromMouse,
478 FromSelection,
479}
480
481#[derive(Clone, Debug)]
482pub enum SelectMode {
483 Character,
484 Word(Range<Anchor>),
485 Line(Range<Anchor>),
486 All,
487}
488
489#[derive(Clone, PartialEq, Eq, Debug)]
490pub enum EditorMode {
491 SingleLine,
492 AutoHeight {
493 min_lines: usize,
494 max_lines: Option<usize>,
495 },
496 Full {
497 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
498 scale_ui_elements_with_buffer_font_size: bool,
499 /// When set to `true`, the editor will render a background for the active line.
500 show_active_line_background: bool,
501 /// When set to `true`, the editor's height will be determined by its content.
502 sized_by_content: bool,
503 },
504 Minimap {
505 parent: WeakEntity<Editor>,
506 },
507}
508
509impl EditorMode {
510 pub fn full() -> Self {
511 Self::Full {
512 scale_ui_elements_with_buffer_font_size: true,
513 show_active_line_background: true,
514 sized_by_content: false,
515 }
516 }
517
518 #[inline]
519 pub fn is_full(&self) -> bool {
520 matches!(self, Self::Full { .. })
521 }
522
523 #[inline]
524 pub fn is_single_line(&self) -> bool {
525 matches!(self, Self::SingleLine { .. })
526 }
527
528 #[inline]
529 fn is_minimap(&self) -> bool {
530 matches!(self, Self::Minimap { .. })
531 }
532}
533
534#[derive(Copy, Clone, Debug)]
535pub enum SoftWrap {
536 /// Prefer not to wrap at all.
537 ///
538 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
539 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
540 GitDiff,
541 /// Prefer a single line generally, unless an overly long line is encountered.
542 None,
543 /// Soft wrap lines that exceed the editor width.
544 EditorWidth,
545 /// Soft wrap lines at the preferred line length.
546 Column(u32),
547 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
548 Bounded(u32),
549}
550
551#[derive(Clone)]
552pub struct EditorStyle {
553 pub background: Hsla,
554 pub border: Hsla,
555 pub local_player: PlayerColor,
556 pub text: TextStyle,
557 pub scrollbar_width: Pixels,
558 pub syntax: Arc<SyntaxTheme>,
559 pub status: StatusColors,
560 pub inlay_hints_style: HighlightStyle,
561 pub edit_prediction_styles: EditPredictionStyles,
562 pub unnecessary_code_fade: f32,
563 pub show_underlines: bool,
564}
565
566impl Default for EditorStyle {
567 fn default() -> Self {
568 Self {
569 background: Hsla::default(),
570 border: Hsla::default(),
571 local_player: PlayerColor::default(),
572 text: TextStyle::default(),
573 scrollbar_width: Pixels::default(),
574 syntax: Default::default(),
575 // HACK: Status colors don't have a real default.
576 // We should look into removing the status colors from the editor
577 // style and retrieve them directly from the theme.
578 status: StatusColors::dark(),
579 inlay_hints_style: HighlightStyle::default(),
580 edit_prediction_styles: EditPredictionStyles {
581 insertion: HighlightStyle::default(),
582 whitespace: HighlightStyle::default(),
583 },
584 unnecessary_code_fade: Default::default(),
585 show_underlines: true,
586 }
587 }
588}
589
590pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
591 let show_background = language_settings::language_settings(None, None, cx)
592 .inlay_hints
593 .show_background;
594
595 let mut style = cx.theme().syntax().get("hint");
596
597 if style.color.is_none() {
598 style.color = Some(cx.theme().status().hint);
599 }
600
601 if !show_background {
602 style.background_color = None;
603 return style;
604 }
605
606 if style.background_color.is_none() {
607 style.background_color = Some(cx.theme().status().hint_background);
608 }
609
610 style
611}
612
613pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
614 EditPredictionStyles {
615 insertion: HighlightStyle {
616 color: Some(cx.theme().status().predictive),
617 ..HighlightStyle::default()
618 },
619 whitespace: HighlightStyle {
620 background_color: Some(cx.theme().status().created_background),
621 ..HighlightStyle::default()
622 },
623 }
624}
625
626type CompletionId = usize;
627
628pub(crate) enum EditDisplayMode {
629 TabAccept,
630 DiffPopover,
631 Inline,
632}
633
634enum EditPrediction {
635 Edit {
636 edits: Vec<(Range<Anchor>, String)>,
637 edit_preview: Option<EditPreview>,
638 display_mode: EditDisplayMode,
639 snapshot: BufferSnapshot,
640 },
641 /// Move to a specific location in the active editor
642 MoveWithin {
643 target: Anchor,
644 snapshot: BufferSnapshot,
645 },
646 /// Move to a specific location in a different editor (not the active one)
647 MoveOutside {
648 target: language::Anchor,
649 snapshot: BufferSnapshot,
650 },
651}
652
653struct EditPredictionState {
654 inlay_ids: Vec<InlayId>,
655 completion: EditPrediction,
656 completion_id: Option<SharedString>,
657 invalidation_range: Option<Range<Anchor>>,
658}
659
660enum EditPredictionSettings {
661 Disabled,
662 Enabled {
663 show_in_menu: bool,
664 preview_requires_modifier: bool,
665 },
666}
667
668enum EditPredictionHighlight {}
669
670#[derive(Debug, Clone)]
671struct InlineDiagnostic {
672 message: SharedString,
673 group_id: usize,
674 is_primary: bool,
675 start: Point,
676 severity: lsp::DiagnosticSeverity,
677}
678
679pub enum MenuEditPredictionsPolicy {
680 Never,
681 ByProvider,
682}
683
684pub enum EditPredictionPreview {
685 /// Modifier is not pressed
686 Inactive { released_too_fast: bool },
687 /// Modifier pressed
688 Active {
689 since: Instant,
690 previous_scroll_position: Option<ScrollAnchor>,
691 },
692}
693
694impl EditPredictionPreview {
695 pub fn released_too_fast(&self) -> bool {
696 match self {
697 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
698 EditPredictionPreview::Active { .. } => false,
699 }
700 }
701
702 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
703 if let EditPredictionPreview::Active {
704 previous_scroll_position,
705 ..
706 } = self
707 {
708 *previous_scroll_position = scroll_position;
709 }
710 }
711}
712
713pub struct ContextMenuOptions {
714 pub min_entries_visible: usize,
715 pub max_entries_visible: usize,
716 pub placement: Option<ContextMenuPlacement>,
717}
718
719#[derive(Debug, Clone, PartialEq, Eq)]
720pub enum ContextMenuPlacement {
721 Above,
722 Below,
723}
724
725#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
726struct EditorActionId(usize);
727
728impl EditorActionId {
729 pub fn post_inc(&mut self) -> Self {
730 let answer = self.0;
731
732 *self = Self(answer + 1);
733
734 Self(answer)
735 }
736}
737
738// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
739// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
740
741type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
742type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
743
744#[derive(Default)]
745struct ScrollbarMarkerState {
746 scrollbar_size: Size<Pixels>,
747 dirty: bool,
748 markers: Arc<[PaintQuad]>,
749 pending_refresh: Option<Task<Result<()>>>,
750}
751
752impl ScrollbarMarkerState {
753 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
754 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
755 }
756}
757
758#[derive(Clone, Copy, PartialEq, Eq)]
759pub enum MinimapVisibility {
760 Disabled,
761 Enabled {
762 /// The configuration currently present in the users settings.
763 setting_configuration: bool,
764 /// Whether to override the currently set visibility from the users setting.
765 toggle_override: bool,
766 },
767}
768
769impl MinimapVisibility {
770 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
771 if mode.is_full() {
772 Self::Enabled {
773 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
774 toggle_override: false,
775 }
776 } else {
777 Self::Disabled
778 }
779 }
780
781 fn hidden(&self) -> Self {
782 match *self {
783 Self::Enabled {
784 setting_configuration,
785 ..
786 } => Self::Enabled {
787 setting_configuration,
788 toggle_override: setting_configuration,
789 },
790 Self::Disabled => Self::Disabled,
791 }
792 }
793
794 fn disabled(&self) -> bool {
795 matches!(*self, Self::Disabled)
796 }
797
798 fn settings_visibility(&self) -> bool {
799 match *self {
800 Self::Enabled {
801 setting_configuration,
802 ..
803 } => setting_configuration,
804 _ => false,
805 }
806 }
807
808 fn visible(&self) -> bool {
809 match *self {
810 Self::Enabled {
811 setting_configuration,
812 toggle_override,
813 } => setting_configuration ^ toggle_override,
814 _ => false,
815 }
816 }
817
818 fn toggle_visibility(&self) -> Self {
819 match *self {
820 Self::Enabled {
821 toggle_override,
822 setting_configuration,
823 } => Self::Enabled {
824 setting_configuration,
825 toggle_override: !toggle_override,
826 },
827 Self::Disabled => Self::Disabled,
828 }
829 }
830}
831
832#[derive(Clone, Debug)]
833struct RunnableTasks {
834 templates: Vec<(TaskSourceKind, TaskTemplate)>,
835 offset: multi_buffer::Anchor,
836 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
837 column: u32,
838 // Values of all named captures, including those starting with '_'
839 extra_variables: HashMap<String, String>,
840 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
841 context_range: Range<BufferOffset>,
842}
843
844impl RunnableTasks {
845 fn resolve<'a>(
846 &'a self,
847 cx: &'a task::TaskContext,
848 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
849 self.templates.iter().filter_map(|(kind, template)| {
850 template
851 .resolve_task(&kind.to_id_base(), cx)
852 .map(|task| (kind.clone(), task))
853 })
854 }
855}
856
857#[derive(Clone)]
858pub struct ResolvedTasks {
859 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
860 position: Anchor,
861}
862
863#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
864struct BufferOffset(usize);
865
866/// Addons allow storing per-editor state in other crates (e.g. Vim)
867pub trait Addon: 'static {
868 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
869
870 fn render_buffer_header_controls(
871 &self,
872 _: &ExcerptInfo,
873 _: &Window,
874 _: &App,
875 ) -> Option<AnyElement> {
876 None
877 }
878
879 fn to_any(&self) -> &dyn std::any::Any;
880
881 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
882 None
883 }
884}
885
886struct ChangeLocation {
887 current: Option<Vec<Anchor>>,
888 original: Vec<Anchor>,
889}
890impl ChangeLocation {
891 fn locations(&self) -> &[Anchor] {
892 self.current.as_ref().unwrap_or(&self.original)
893 }
894}
895
896/// A set of caret positions, registered when the editor was edited.
897pub struct ChangeList {
898 changes: Vec<ChangeLocation>,
899 /// Currently "selected" change.
900 position: Option<usize>,
901}
902
903impl ChangeList {
904 pub fn new() -> Self {
905 Self {
906 changes: Vec::new(),
907 position: None,
908 }
909 }
910
911 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
912 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
913 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
914 if self.changes.is_empty() {
915 return None;
916 }
917
918 let prev = self.position.unwrap_or(self.changes.len());
919 let next = if direction == Direction::Prev {
920 prev.saturating_sub(count)
921 } else {
922 (prev + count).min(self.changes.len() - 1)
923 };
924 self.position = Some(next);
925 self.changes.get(next).map(|change| change.locations())
926 }
927
928 /// Adds a new change to the list, resetting the change list position.
929 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
930 self.position.take();
931 if let Some(last) = self.changes.last_mut()
932 && group
933 {
934 last.current = Some(new_positions)
935 } else {
936 self.changes.push(ChangeLocation {
937 original: new_positions,
938 current: None,
939 });
940 }
941 }
942
943 pub fn last(&self) -> Option<&[Anchor]> {
944 self.changes.last().map(|change| change.locations())
945 }
946
947 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
948 self.changes.last().map(|change| change.original.as_slice())
949 }
950
951 pub fn invert_last_group(&mut self) {
952 if let Some(last) = self.changes.last_mut()
953 && let Some(current) = last.current.as_mut()
954 {
955 mem::swap(&mut last.original, current);
956 }
957 }
958}
959
960#[derive(Clone)]
961struct InlineBlamePopoverState {
962 scroll_handle: ScrollHandle,
963 commit_message: Option<ParsedCommitMessage>,
964 markdown: Entity<Markdown>,
965}
966
967struct InlineBlamePopover {
968 position: gpui::Point<Pixels>,
969 hide_task: Option<Task<()>>,
970 popover_bounds: Option<Bounds<Pixels>>,
971 popover_state: InlineBlamePopoverState,
972 keyboard_grace: bool,
973}
974
975enum SelectionDragState {
976 /// State when no drag related activity is detected.
977 None,
978 /// State when the mouse is down on a selection that is about to be dragged.
979 ReadyToDrag {
980 selection: Selection<Anchor>,
981 click_position: gpui::Point<Pixels>,
982 mouse_down_time: Instant,
983 },
984 /// State when the mouse is dragging the selection in the editor.
985 Dragging {
986 selection: Selection<Anchor>,
987 drop_cursor: Selection<Anchor>,
988 hide_drop_cursor: bool,
989 },
990}
991
992enum ColumnarSelectionState {
993 FromMouse {
994 selection_tail: Anchor,
995 display_point: Option<DisplayPoint>,
996 },
997 FromSelection {
998 selection_tail: Anchor,
999 },
1000}
1001
1002/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1003/// a breakpoint on them.
1004#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1005struct PhantomBreakpointIndicator {
1006 display_row: DisplayRow,
1007 /// There's a small debounce between hovering over the line and showing the indicator.
1008 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1009 is_active: bool,
1010 collides_with_existing_breakpoint: bool,
1011}
1012
1013/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1014///
1015/// See the [module level documentation](self) for more information.
1016pub struct Editor {
1017 focus_handle: FocusHandle,
1018 last_focused_descendant: Option<WeakFocusHandle>,
1019 /// The text buffer being edited
1020 buffer: Entity<MultiBuffer>,
1021 /// Map of how text in the buffer should be displayed.
1022 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1023 pub display_map: Entity<DisplayMap>,
1024 placeholder_display_map: Option<Entity<DisplayMap>>,
1025 pub selections: SelectionsCollection,
1026 pub scroll_manager: ScrollManager,
1027 /// When inline assist editors are linked, they all render cursors because
1028 /// typing enters text into each of them, even the ones that aren't focused.
1029 pub(crate) show_cursor_when_unfocused: bool,
1030 columnar_selection_state: Option<ColumnarSelectionState>,
1031 add_selections_state: Option<AddSelectionsState>,
1032 select_next_state: Option<SelectNextState>,
1033 select_prev_state: Option<SelectNextState>,
1034 selection_history: SelectionHistory,
1035 defer_selection_effects: bool,
1036 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1037 autoclose_regions: Vec<AutocloseRegion>,
1038 snippet_stack: InvalidationStack<SnippetState>,
1039 select_syntax_node_history: SelectSyntaxNodeHistory,
1040 ime_transaction: Option<TransactionId>,
1041 pub diagnostics_max_severity: DiagnosticSeverity,
1042 active_diagnostics: ActiveDiagnostic,
1043 show_inline_diagnostics: bool,
1044 inline_diagnostics_update: Task<()>,
1045 inline_diagnostics_enabled: bool,
1046 diagnostics_enabled: bool,
1047 word_completions_enabled: bool,
1048 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1049 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1050 hard_wrap: Option<usize>,
1051 project: Option<Entity<Project>>,
1052 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1053 completion_provider: Option<Rc<dyn CompletionProvider>>,
1054 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1055 blink_manager: Entity<BlinkManager>,
1056 show_cursor_names: bool,
1057 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1058 pub show_local_selections: bool,
1059 mode: EditorMode,
1060 show_breadcrumbs: bool,
1061 show_gutter: bool,
1062 show_scrollbars: ScrollbarAxes,
1063 minimap_visibility: MinimapVisibility,
1064 offset_content: bool,
1065 disable_expand_excerpt_buttons: bool,
1066 show_line_numbers: Option<bool>,
1067 use_relative_line_numbers: Option<bool>,
1068 show_git_diff_gutter: Option<bool>,
1069 show_code_actions: Option<bool>,
1070 show_runnables: Option<bool>,
1071 show_breakpoints: Option<bool>,
1072 show_wrap_guides: Option<bool>,
1073 show_indent_guides: Option<bool>,
1074 highlight_order: usize,
1075 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1076 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1077 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1078 scrollbar_marker_state: ScrollbarMarkerState,
1079 active_indent_guides_state: ActiveIndentGuidesState,
1080 nav_history: Option<ItemNavHistory>,
1081 context_menu: RefCell<Option<CodeContextMenu>>,
1082 context_menu_options: Option<ContextMenuOptions>,
1083 mouse_context_menu: Option<MouseContextMenu>,
1084 completion_tasks: Vec<(CompletionId, Task<()>)>,
1085 inline_blame_popover: Option<InlineBlamePopover>,
1086 inline_blame_popover_show_task: Option<Task<()>>,
1087 signature_help_state: SignatureHelpState,
1088 auto_signature_help: Option<bool>,
1089 find_all_references_task_sources: Vec<Anchor>,
1090 next_completion_id: CompletionId,
1091 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1092 code_actions_task: Option<Task<Result<()>>>,
1093 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1094 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1095 document_highlights_task: Option<Task<()>>,
1096 linked_editing_range_task: Option<Task<Option<()>>>,
1097 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1098 pending_rename: Option<RenameState>,
1099 searchable: bool,
1100 cursor_shape: CursorShape,
1101 current_line_highlight: Option<CurrentLineHighlight>,
1102 collapse_matches: bool,
1103 autoindent_mode: Option<AutoindentMode>,
1104 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1105 input_enabled: bool,
1106 use_modal_editing: bool,
1107 read_only: bool,
1108 leader_id: Option<CollaboratorId>,
1109 remote_id: Option<ViewId>,
1110 pub hover_state: HoverState,
1111 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1112 gutter_hovered: bool,
1113 hovered_link_state: Option<HoveredLinkState>,
1114 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1115 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1116 active_edit_prediction: Option<EditPredictionState>,
1117 /// Used to prevent flickering as the user types while the menu is open
1118 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1119 edit_prediction_settings: EditPredictionSettings,
1120 edit_predictions_hidden_for_vim_mode: bool,
1121 show_edit_predictions_override: Option<bool>,
1122 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1123 edit_prediction_preview: EditPredictionPreview,
1124 edit_prediction_indent_conflict: bool,
1125 edit_prediction_requires_modifier_in_indent_conflict: bool,
1126 inlay_hint_cache: InlayHintCache,
1127 next_inlay_id: u32,
1128 next_color_inlay_id: u32,
1129 _subscriptions: Vec<Subscription>,
1130 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1131 gutter_dimensions: GutterDimensions,
1132 style: Option<EditorStyle>,
1133 text_style_refinement: Option<TextStyleRefinement>,
1134 next_editor_action_id: EditorActionId,
1135 editor_actions: Rc<
1136 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1137 >,
1138 use_autoclose: bool,
1139 use_auto_surround: bool,
1140 auto_replace_emoji_shortcode: bool,
1141 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1142 show_git_blame_gutter: bool,
1143 show_git_blame_inline: bool,
1144 show_git_blame_inline_delay_task: Option<Task<()>>,
1145 git_blame_inline_enabled: bool,
1146 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1147 serialize_dirty_buffers: bool,
1148 show_selection_menu: Option<bool>,
1149 blame: Option<Entity<GitBlame>>,
1150 blame_subscription: Option<Subscription>,
1151 custom_context_menu: Option<
1152 Box<
1153 dyn 'static
1154 + Fn(
1155 &mut Self,
1156 DisplayPoint,
1157 &mut Window,
1158 &mut Context<Self>,
1159 ) -> Option<Entity<ui::ContextMenu>>,
1160 >,
1161 >,
1162 last_bounds: Option<Bounds<Pixels>>,
1163 last_position_map: Option<Rc<PositionMap>>,
1164 expect_bounds_change: Option<Bounds<Pixels>>,
1165 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1166 tasks_update_task: Option<Task<()>>,
1167 breakpoint_store: Option<Entity<BreakpointStore>>,
1168 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1169 hovered_diff_hunk_row: Option<DisplayRow>,
1170 pull_diagnostics_task: Task<()>,
1171 in_project_search: bool,
1172 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1173 breadcrumb_header: Option<String>,
1174 focused_block: Option<FocusedBlock>,
1175 next_scroll_position: NextScrollCursorCenterTopBottom,
1176 addons: HashMap<TypeId, Box<dyn Addon>>,
1177 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1178 load_diff_task: Option<Shared<Task<()>>>,
1179 /// Whether we are temporarily displaying a diff other than git's
1180 temporary_diff_override: bool,
1181 selection_mark_mode: bool,
1182 toggle_fold_multiple_buffers: Task<()>,
1183 _scroll_cursor_center_top_bottom_task: Task<()>,
1184 serialize_selections: Task<()>,
1185 serialize_folds: Task<()>,
1186 mouse_cursor_hidden: bool,
1187 minimap: Option<Entity<Self>>,
1188 hide_mouse_mode: HideMouseMode,
1189 pub change_list: ChangeList,
1190 inline_value_cache: InlineValueCache,
1191 selection_drag_state: SelectionDragState,
1192 colors: Option<LspColorData>,
1193 refresh_colors_task: Task<()>,
1194 folding_newlines: Task<()>,
1195 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1196}
1197
1198#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1199enum NextScrollCursorCenterTopBottom {
1200 #[default]
1201 Center,
1202 Top,
1203 Bottom,
1204}
1205
1206impl NextScrollCursorCenterTopBottom {
1207 fn next(&self) -> Self {
1208 match self {
1209 Self::Center => Self::Top,
1210 Self::Top => Self::Bottom,
1211 Self::Bottom => Self::Center,
1212 }
1213 }
1214}
1215
1216#[derive(Clone)]
1217pub struct EditorSnapshot {
1218 pub mode: EditorMode,
1219 show_gutter: bool,
1220 show_line_numbers: Option<bool>,
1221 show_git_diff_gutter: Option<bool>,
1222 show_code_actions: Option<bool>,
1223 show_runnables: Option<bool>,
1224 show_breakpoints: Option<bool>,
1225 git_blame_gutter_max_author_length: Option<usize>,
1226 pub display_snapshot: DisplaySnapshot,
1227 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1228 is_focused: bool,
1229 scroll_anchor: ScrollAnchor,
1230 ongoing_scroll: OngoingScroll,
1231 current_line_highlight: CurrentLineHighlight,
1232 gutter_hovered: bool,
1233}
1234
1235#[derive(Default, Debug, Clone, Copy)]
1236pub struct GutterDimensions {
1237 pub left_padding: Pixels,
1238 pub right_padding: Pixels,
1239 pub width: Pixels,
1240 pub margin: Pixels,
1241 pub git_blame_entries_width: Option<Pixels>,
1242}
1243
1244impl GutterDimensions {
1245 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1246 Self {
1247 margin: Self::default_gutter_margin(font_id, font_size, cx),
1248 ..Default::default()
1249 }
1250 }
1251
1252 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1253 -cx.text_system().descent(font_id, font_size)
1254 }
1255 /// The full width of the space taken up by the gutter.
1256 pub fn full_width(&self) -> Pixels {
1257 self.margin + self.width
1258 }
1259
1260 /// The width of the space reserved for the fold indicators,
1261 /// use alongside 'justify_end' and `gutter_width` to
1262 /// right align content with the line numbers
1263 pub fn fold_area_width(&self) -> Pixels {
1264 self.margin + self.right_padding
1265 }
1266}
1267
1268struct CharacterDimensions {
1269 em_width: Pixels,
1270 em_advance: Pixels,
1271 line_height: Pixels,
1272}
1273
1274#[derive(Debug)]
1275pub struct RemoteSelection {
1276 pub replica_id: ReplicaId,
1277 pub selection: Selection<Anchor>,
1278 pub cursor_shape: CursorShape,
1279 pub collaborator_id: CollaboratorId,
1280 pub line_mode: bool,
1281 pub user_name: Option<SharedString>,
1282 pub color: PlayerColor,
1283}
1284
1285#[derive(Clone, Debug)]
1286struct SelectionHistoryEntry {
1287 selections: Arc<[Selection<Anchor>]>,
1288 select_next_state: Option<SelectNextState>,
1289 select_prev_state: Option<SelectNextState>,
1290 add_selections_state: Option<AddSelectionsState>,
1291}
1292
1293#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1294enum SelectionHistoryMode {
1295 Normal,
1296 Undoing,
1297 Redoing,
1298 Skipping,
1299}
1300
1301#[derive(Clone, PartialEq, Eq, Hash)]
1302struct HoveredCursor {
1303 replica_id: u16,
1304 selection_id: usize,
1305}
1306
1307impl Default for SelectionHistoryMode {
1308 fn default() -> Self {
1309 Self::Normal
1310 }
1311}
1312
1313#[derive(Debug)]
1314/// SelectionEffects controls the side-effects of updating the selection.
1315///
1316/// The default behaviour does "what you mostly want":
1317/// - it pushes to the nav history if the cursor moved by >10 lines
1318/// - it re-triggers completion requests
1319/// - it scrolls to fit
1320///
1321/// You might want to modify these behaviours. For example when doing a "jump"
1322/// like go to definition, we always want to add to nav history; but when scrolling
1323/// in vim mode we never do.
1324///
1325/// Similarly, you might want to disable scrolling if you don't want the viewport to
1326/// move.
1327#[derive(Clone)]
1328pub struct SelectionEffects {
1329 nav_history: Option<bool>,
1330 completions: bool,
1331 scroll: Option<Autoscroll>,
1332}
1333
1334impl Default for SelectionEffects {
1335 fn default() -> Self {
1336 Self {
1337 nav_history: None,
1338 completions: true,
1339 scroll: Some(Autoscroll::fit()),
1340 }
1341 }
1342}
1343impl SelectionEffects {
1344 pub fn scroll(scroll: Autoscroll) -> Self {
1345 Self {
1346 scroll: Some(scroll),
1347 ..Default::default()
1348 }
1349 }
1350
1351 pub fn no_scroll() -> Self {
1352 Self {
1353 scroll: None,
1354 ..Default::default()
1355 }
1356 }
1357
1358 pub fn completions(self, completions: bool) -> Self {
1359 Self {
1360 completions,
1361 ..self
1362 }
1363 }
1364
1365 pub fn nav_history(self, nav_history: bool) -> Self {
1366 Self {
1367 nav_history: Some(nav_history),
1368 ..self
1369 }
1370 }
1371}
1372
1373struct DeferredSelectionEffectsState {
1374 changed: bool,
1375 effects: SelectionEffects,
1376 old_cursor_position: Anchor,
1377 history_entry: SelectionHistoryEntry,
1378}
1379
1380#[derive(Default)]
1381struct SelectionHistory {
1382 #[allow(clippy::type_complexity)]
1383 selections_by_transaction:
1384 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1385 mode: SelectionHistoryMode,
1386 undo_stack: VecDeque<SelectionHistoryEntry>,
1387 redo_stack: VecDeque<SelectionHistoryEntry>,
1388}
1389
1390impl SelectionHistory {
1391 #[track_caller]
1392 fn insert_transaction(
1393 &mut self,
1394 transaction_id: TransactionId,
1395 selections: Arc<[Selection<Anchor>]>,
1396 ) {
1397 if selections.is_empty() {
1398 log::error!(
1399 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1400 std::panic::Location::caller()
1401 );
1402 return;
1403 }
1404 self.selections_by_transaction
1405 .insert(transaction_id, (selections, None));
1406 }
1407
1408 #[allow(clippy::type_complexity)]
1409 fn transaction(
1410 &self,
1411 transaction_id: TransactionId,
1412 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1413 self.selections_by_transaction.get(&transaction_id)
1414 }
1415
1416 #[allow(clippy::type_complexity)]
1417 fn transaction_mut(
1418 &mut self,
1419 transaction_id: TransactionId,
1420 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1421 self.selections_by_transaction.get_mut(&transaction_id)
1422 }
1423
1424 fn push(&mut self, entry: SelectionHistoryEntry) {
1425 if !entry.selections.is_empty() {
1426 match self.mode {
1427 SelectionHistoryMode::Normal => {
1428 self.push_undo(entry);
1429 self.redo_stack.clear();
1430 }
1431 SelectionHistoryMode::Undoing => self.push_redo(entry),
1432 SelectionHistoryMode::Redoing => self.push_undo(entry),
1433 SelectionHistoryMode::Skipping => {}
1434 }
1435 }
1436 }
1437
1438 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1439 if self
1440 .undo_stack
1441 .back()
1442 .is_none_or(|e| e.selections != entry.selections)
1443 {
1444 self.undo_stack.push_back(entry);
1445 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1446 self.undo_stack.pop_front();
1447 }
1448 }
1449 }
1450
1451 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1452 if self
1453 .redo_stack
1454 .back()
1455 .is_none_or(|e| e.selections != entry.selections)
1456 {
1457 self.redo_stack.push_back(entry);
1458 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1459 self.redo_stack.pop_front();
1460 }
1461 }
1462 }
1463}
1464
1465#[derive(Clone, Copy)]
1466pub struct RowHighlightOptions {
1467 pub autoscroll: bool,
1468 pub include_gutter: bool,
1469}
1470
1471impl Default for RowHighlightOptions {
1472 fn default() -> Self {
1473 Self {
1474 autoscroll: Default::default(),
1475 include_gutter: true,
1476 }
1477 }
1478}
1479
1480struct RowHighlight {
1481 index: usize,
1482 range: Range<Anchor>,
1483 color: Hsla,
1484 options: RowHighlightOptions,
1485 type_id: TypeId,
1486}
1487
1488#[derive(Clone, Debug)]
1489struct AddSelectionsState {
1490 groups: Vec<AddSelectionsGroup>,
1491}
1492
1493#[derive(Clone, Debug)]
1494struct AddSelectionsGroup {
1495 above: bool,
1496 stack: Vec<usize>,
1497}
1498
1499#[derive(Clone)]
1500struct SelectNextState {
1501 query: AhoCorasick,
1502 wordwise: bool,
1503 done: bool,
1504}
1505
1506impl std::fmt::Debug for SelectNextState {
1507 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1508 f.debug_struct(std::any::type_name::<Self>())
1509 .field("wordwise", &self.wordwise)
1510 .field("done", &self.done)
1511 .finish()
1512 }
1513}
1514
1515#[derive(Debug)]
1516struct AutocloseRegion {
1517 selection_id: usize,
1518 range: Range<Anchor>,
1519 pair: BracketPair,
1520}
1521
1522#[derive(Debug)]
1523struct SnippetState {
1524 ranges: Vec<Vec<Range<Anchor>>>,
1525 active_index: usize,
1526 choices: Vec<Option<Vec<String>>>,
1527}
1528
1529#[doc(hidden)]
1530pub struct RenameState {
1531 pub range: Range<Anchor>,
1532 pub old_name: Arc<str>,
1533 pub editor: Entity<Editor>,
1534 block_id: CustomBlockId,
1535}
1536
1537struct InvalidationStack<T>(Vec<T>);
1538
1539struct RegisteredEditPredictionProvider {
1540 provider: Arc<dyn EditPredictionProviderHandle>,
1541 _subscription: Subscription,
1542}
1543
1544#[derive(Debug, PartialEq, Eq)]
1545pub struct ActiveDiagnosticGroup {
1546 pub active_range: Range<Anchor>,
1547 pub active_message: String,
1548 pub group_id: usize,
1549 pub blocks: HashSet<CustomBlockId>,
1550}
1551
1552#[derive(Debug, PartialEq, Eq)]
1553
1554pub(crate) enum ActiveDiagnostic {
1555 None,
1556 All,
1557 Group(ActiveDiagnosticGroup),
1558}
1559
1560#[derive(Serialize, Deserialize, Clone, Debug)]
1561pub struct ClipboardSelection {
1562 /// The number of bytes in this selection.
1563 pub len: usize,
1564 /// Whether this was a full-line selection.
1565 pub is_entire_line: bool,
1566 /// The indentation of the first line when this content was originally copied.
1567 pub first_line_indent: u32,
1568}
1569
1570// selections, scroll behavior, was newest selection reversed
1571type SelectSyntaxNodeHistoryState = (
1572 Box<[Selection<usize>]>,
1573 SelectSyntaxNodeScrollBehavior,
1574 bool,
1575);
1576
1577#[derive(Default)]
1578struct SelectSyntaxNodeHistory {
1579 stack: Vec<SelectSyntaxNodeHistoryState>,
1580 // disable temporarily to allow changing selections without losing the stack
1581 pub disable_clearing: bool,
1582}
1583
1584impl SelectSyntaxNodeHistory {
1585 pub fn try_clear(&mut self) {
1586 if !self.disable_clearing {
1587 self.stack.clear();
1588 }
1589 }
1590
1591 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1592 self.stack.push(selection);
1593 }
1594
1595 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1596 self.stack.pop()
1597 }
1598}
1599
1600enum SelectSyntaxNodeScrollBehavior {
1601 CursorTop,
1602 FitSelection,
1603 CursorBottom,
1604}
1605
1606#[derive(Debug)]
1607pub(crate) struct NavigationData {
1608 cursor_anchor: Anchor,
1609 cursor_position: Point,
1610 scroll_anchor: ScrollAnchor,
1611 scroll_top_row: u32,
1612}
1613
1614#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1615pub enum GotoDefinitionKind {
1616 Symbol,
1617 Declaration,
1618 Type,
1619 Implementation,
1620}
1621
1622#[derive(Debug, Clone)]
1623enum InlayHintRefreshReason {
1624 ModifiersChanged(bool),
1625 Toggle(bool),
1626 SettingsChange(InlayHintSettings),
1627 NewLinesShown,
1628 BufferEdited(HashSet<Arc<Language>>),
1629 RefreshRequested,
1630 ExcerptsRemoved(Vec<ExcerptId>),
1631}
1632
1633impl InlayHintRefreshReason {
1634 fn description(&self) -> &'static str {
1635 match self {
1636 Self::ModifiersChanged(_) => "modifiers changed",
1637 Self::Toggle(_) => "toggle",
1638 Self::SettingsChange(_) => "settings change",
1639 Self::NewLinesShown => "new lines shown",
1640 Self::BufferEdited(_) => "buffer edited",
1641 Self::RefreshRequested => "refresh requested",
1642 Self::ExcerptsRemoved(_) => "excerpts removed",
1643 }
1644 }
1645}
1646
1647pub enum FormatTarget {
1648 Buffers(HashSet<Entity<Buffer>>),
1649 Ranges(Vec<Range<MultiBufferPoint>>),
1650}
1651
1652pub(crate) struct FocusedBlock {
1653 id: BlockId,
1654 focus_handle: WeakFocusHandle,
1655}
1656
1657#[derive(Clone)]
1658enum JumpData {
1659 MultiBufferRow {
1660 row: MultiBufferRow,
1661 line_offset_from_top: u32,
1662 },
1663 MultiBufferPoint {
1664 excerpt_id: ExcerptId,
1665 position: Point,
1666 anchor: text::Anchor,
1667 line_offset_from_top: u32,
1668 },
1669}
1670
1671pub enum MultibufferSelectionMode {
1672 First,
1673 All,
1674}
1675
1676#[derive(Clone, Copy, Debug, Default)]
1677pub struct RewrapOptions {
1678 pub override_language_settings: bool,
1679 pub preserve_existing_whitespace: bool,
1680}
1681
1682impl Editor {
1683 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1684 let buffer = cx.new(|cx| Buffer::local("", cx));
1685 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1686 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1687 }
1688
1689 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1690 let buffer = cx.new(|cx| Buffer::local("", cx));
1691 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1692 Self::new(EditorMode::full(), buffer, None, window, cx)
1693 }
1694
1695 pub fn auto_height(
1696 min_lines: usize,
1697 max_lines: usize,
1698 window: &mut Window,
1699 cx: &mut Context<Self>,
1700 ) -> Self {
1701 let buffer = cx.new(|cx| Buffer::local("", cx));
1702 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1703 Self::new(
1704 EditorMode::AutoHeight {
1705 min_lines,
1706 max_lines: Some(max_lines),
1707 },
1708 buffer,
1709 None,
1710 window,
1711 cx,
1712 )
1713 }
1714
1715 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1716 /// The editor grows as tall as needed to fit its content.
1717 pub fn auto_height_unbounded(
1718 min_lines: usize,
1719 window: &mut Window,
1720 cx: &mut Context<Self>,
1721 ) -> Self {
1722 let buffer = cx.new(|cx| Buffer::local("", cx));
1723 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1724 Self::new(
1725 EditorMode::AutoHeight {
1726 min_lines,
1727 max_lines: None,
1728 },
1729 buffer,
1730 None,
1731 window,
1732 cx,
1733 )
1734 }
1735
1736 pub fn for_buffer(
1737 buffer: Entity<Buffer>,
1738 project: Option<Entity<Project>>,
1739 window: &mut Window,
1740 cx: &mut Context<Self>,
1741 ) -> Self {
1742 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1743 Self::new(EditorMode::full(), buffer, project, window, cx)
1744 }
1745
1746 pub fn for_multibuffer(
1747 buffer: Entity<MultiBuffer>,
1748 project: Option<Entity<Project>>,
1749 window: &mut Window,
1750 cx: &mut Context<Self>,
1751 ) -> Self {
1752 Self::new(EditorMode::full(), buffer, project, window, cx)
1753 }
1754
1755 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1756 let mut clone = Self::new(
1757 self.mode.clone(),
1758 self.buffer.clone(),
1759 self.project.clone(),
1760 window,
1761 cx,
1762 );
1763 self.display_map.update(cx, |display_map, cx| {
1764 let snapshot = display_map.snapshot(cx);
1765 clone.display_map.update(cx, |display_map, cx| {
1766 display_map.set_state(&snapshot, cx);
1767 });
1768 });
1769 clone.folds_did_change(cx);
1770 clone.selections.clone_state(&self.selections);
1771 clone.scroll_manager.clone_state(&self.scroll_manager);
1772 clone.searchable = self.searchable;
1773 clone.read_only = self.read_only;
1774 clone
1775 }
1776
1777 pub fn new(
1778 mode: EditorMode,
1779 buffer: Entity<MultiBuffer>,
1780 project: Option<Entity<Project>>,
1781 window: &mut Window,
1782 cx: &mut Context<Self>,
1783 ) -> Self {
1784 Editor::new_internal(mode, buffer, project, None, window, cx)
1785 }
1786
1787 fn new_internal(
1788 mode: EditorMode,
1789 buffer: Entity<MultiBuffer>,
1790 project: Option<Entity<Project>>,
1791 display_map: Option<Entity<DisplayMap>>,
1792 window: &mut Window,
1793 cx: &mut Context<Self>,
1794 ) -> Self {
1795 debug_assert!(
1796 display_map.is_none() || mode.is_minimap(),
1797 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1798 );
1799
1800 let full_mode = mode.is_full();
1801 let is_minimap = mode.is_minimap();
1802 let diagnostics_max_severity = if full_mode {
1803 EditorSettings::get_global(cx)
1804 .diagnostics_max_severity
1805 .unwrap_or(DiagnosticSeverity::Hint)
1806 } else {
1807 DiagnosticSeverity::Off
1808 };
1809 let style = window.text_style();
1810 let font_size = style.font_size.to_pixels(window.rem_size());
1811 let editor = cx.entity().downgrade();
1812 let fold_placeholder = FoldPlaceholder {
1813 constrain_width: false,
1814 render: Arc::new(move |fold_id, fold_range, cx| {
1815 let editor = editor.clone();
1816 div()
1817 .id(fold_id)
1818 .bg(cx.theme().colors().ghost_element_background)
1819 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1820 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1821 .rounded_xs()
1822 .size_full()
1823 .cursor_pointer()
1824 .child("⋯")
1825 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1826 .on_click(move |_, _window, cx| {
1827 editor
1828 .update(cx, |editor, cx| {
1829 editor.unfold_ranges(
1830 &[fold_range.start..fold_range.end],
1831 true,
1832 false,
1833 cx,
1834 );
1835 cx.stop_propagation();
1836 })
1837 .ok();
1838 })
1839 .into_any()
1840 }),
1841 merge_adjacent: true,
1842 ..FoldPlaceholder::default()
1843 };
1844 let display_map = display_map.unwrap_or_else(|| {
1845 cx.new(|cx| {
1846 DisplayMap::new(
1847 buffer.clone(),
1848 style.font(),
1849 font_size,
1850 None,
1851 FILE_HEADER_HEIGHT,
1852 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1853 fold_placeholder,
1854 diagnostics_max_severity,
1855 cx,
1856 )
1857 })
1858 });
1859
1860 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1861
1862 let blink_manager = cx.new(|cx| {
1863 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1864 if is_minimap {
1865 blink_manager.disable(cx);
1866 }
1867 blink_manager
1868 });
1869
1870 let soft_wrap_mode_override =
1871 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1872
1873 let mut project_subscriptions = Vec::new();
1874 if full_mode && let Some(project) = project.as_ref() {
1875 project_subscriptions.push(cx.subscribe_in(
1876 project,
1877 window,
1878 |editor, _, event, window, cx| match event {
1879 project::Event::RefreshCodeLens => {
1880 // we always query lens with actions, without storing them, always refreshing them
1881 }
1882 project::Event::RefreshInlayHints => {
1883 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1884 }
1885 project::Event::LanguageServerAdded(..)
1886 | project::Event::LanguageServerRemoved(..) => {
1887 if editor.tasks_update_task.is_none() {
1888 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1889 }
1890 }
1891 project::Event::SnippetEdit(id, snippet_edits) => {
1892 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1893 let focus_handle = editor.focus_handle(cx);
1894 if focus_handle.is_focused(window) {
1895 let snapshot = buffer.read(cx).snapshot();
1896 for (range, snippet) in snippet_edits {
1897 let editor_range =
1898 language::range_from_lsp(*range).to_offset(&snapshot);
1899 editor
1900 .insert_snippet(
1901 &[editor_range],
1902 snippet.clone(),
1903 window,
1904 cx,
1905 )
1906 .ok();
1907 }
1908 }
1909 }
1910 }
1911 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1912 if editor.buffer().read(cx).buffer(*buffer_id).is_some() {
1913 editor.update_lsp_data(false, Some(*buffer_id), window, cx);
1914 }
1915 }
1916
1917 project::Event::EntryRenamed(transaction) => {
1918 let Some(workspace) = editor.workspace() else {
1919 return;
1920 };
1921 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1922 else {
1923 return;
1924 };
1925 if active_editor.entity_id() == cx.entity_id() {
1926 let edited_buffers_already_open = {
1927 let other_editors: Vec<Entity<Editor>> = workspace
1928 .read(cx)
1929 .panes()
1930 .iter()
1931 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1932 .filter(|editor| editor.entity_id() != cx.entity_id())
1933 .collect();
1934
1935 transaction.0.keys().all(|buffer| {
1936 other_editors.iter().any(|editor| {
1937 let multi_buffer = editor.read(cx).buffer();
1938 multi_buffer.read(cx).is_singleton()
1939 && multi_buffer.read(cx).as_singleton().map_or(
1940 false,
1941 |singleton| {
1942 singleton.entity_id() == buffer.entity_id()
1943 },
1944 )
1945 })
1946 })
1947 };
1948
1949 if !edited_buffers_already_open {
1950 let workspace = workspace.downgrade();
1951 let transaction = transaction.clone();
1952 cx.defer_in(window, move |_, window, cx| {
1953 cx.spawn_in(window, async move |editor, cx| {
1954 Self::open_project_transaction(
1955 &editor,
1956 workspace,
1957 transaction,
1958 "Rename".to_string(),
1959 cx,
1960 )
1961 .await
1962 .ok()
1963 })
1964 .detach();
1965 });
1966 }
1967 }
1968 }
1969
1970 _ => {}
1971 },
1972 ));
1973 if let Some(task_inventory) = project
1974 .read(cx)
1975 .task_store()
1976 .read(cx)
1977 .task_inventory()
1978 .cloned()
1979 {
1980 project_subscriptions.push(cx.observe_in(
1981 &task_inventory,
1982 window,
1983 |editor, _, window, cx| {
1984 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1985 },
1986 ));
1987 };
1988
1989 project_subscriptions.push(cx.subscribe_in(
1990 &project.read(cx).breakpoint_store(),
1991 window,
1992 |editor, _, event, window, cx| match event {
1993 BreakpointStoreEvent::ClearDebugLines => {
1994 editor.clear_row_highlights::<ActiveDebugLine>();
1995 editor.refresh_inline_values(cx);
1996 }
1997 BreakpointStoreEvent::SetDebugLine => {
1998 if editor.go_to_active_debug_line(window, cx) {
1999 cx.stop_propagation();
2000 }
2001
2002 editor.refresh_inline_values(cx);
2003 }
2004 _ => {}
2005 },
2006 ));
2007 let git_store = project.read(cx).git_store().clone();
2008 let project = project.clone();
2009 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2010 if let GitStoreEvent::RepositoryUpdated(
2011 _,
2012 RepositoryEvent::Updated {
2013 new_instance: true, ..
2014 },
2015 _,
2016 ) = event
2017 {
2018 this.load_diff_task = Some(
2019 update_uncommitted_diff_for_buffer(
2020 cx.entity(),
2021 &project,
2022 this.buffer.read(cx).all_buffers(),
2023 this.buffer.clone(),
2024 cx,
2025 )
2026 .shared(),
2027 );
2028 }
2029 }));
2030 }
2031
2032 let buffer_snapshot = buffer.read(cx).snapshot(cx);
2033
2034 let inlay_hint_settings =
2035 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2036 let focus_handle = cx.focus_handle();
2037 if !is_minimap {
2038 cx.on_focus(&focus_handle, window, Self::handle_focus)
2039 .detach();
2040 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2041 .detach();
2042 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2043 .detach();
2044 cx.on_blur(&focus_handle, window, Self::handle_blur)
2045 .detach();
2046 cx.observe_pending_input(window, Self::observe_pending_input)
2047 .detach();
2048 }
2049
2050 let show_indent_guides =
2051 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2052 Some(false)
2053 } else {
2054 None
2055 };
2056
2057 let breakpoint_store = match (&mode, project.as_ref()) {
2058 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2059 _ => None,
2060 };
2061
2062 let mut code_action_providers = Vec::new();
2063 let mut load_uncommitted_diff = None;
2064 if let Some(project) = project.clone() {
2065 load_uncommitted_diff = Some(
2066 update_uncommitted_diff_for_buffer(
2067 cx.entity(),
2068 &project,
2069 buffer.read(cx).all_buffers(),
2070 buffer.clone(),
2071 cx,
2072 )
2073 .shared(),
2074 );
2075 code_action_providers.push(Rc::new(project) as Rc<_>);
2076 }
2077
2078 let mut editor = Self {
2079 focus_handle,
2080 show_cursor_when_unfocused: false,
2081 last_focused_descendant: None,
2082 buffer: buffer.clone(),
2083 display_map: display_map.clone(),
2084 placeholder_display_map: None,
2085 selections,
2086 scroll_manager: ScrollManager::new(cx),
2087 columnar_selection_state: None,
2088 add_selections_state: None,
2089 select_next_state: None,
2090 select_prev_state: None,
2091 selection_history: SelectionHistory::default(),
2092 defer_selection_effects: false,
2093 deferred_selection_effects_state: None,
2094 autoclose_regions: Vec::new(),
2095 snippet_stack: InvalidationStack::default(),
2096 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2097 ime_transaction: None,
2098 active_diagnostics: ActiveDiagnostic::None,
2099 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2100 inline_diagnostics_update: Task::ready(()),
2101 inline_diagnostics: Vec::new(),
2102 soft_wrap_mode_override,
2103 diagnostics_max_severity,
2104 hard_wrap: None,
2105 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2106 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2107 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2108 project,
2109 blink_manager: blink_manager.clone(),
2110 show_local_selections: true,
2111 show_scrollbars: ScrollbarAxes {
2112 horizontal: full_mode,
2113 vertical: full_mode,
2114 },
2115 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2116 offset_content: !matches!(mode, EditorMode::SingleLine),
2117 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2118 show_gutter: full_mode,
2119 show_line_numbers: (!full_mode).then_some(false),
2120 use_relative_line_numbers: None,
2121 disable_expand_excerpt_buttons: !full_mode,
2122 show_git_diff_gutter: None,
2123 show_code_actions: None,
2124 show_runnables: None,
2125 show_breakpoints: None,
2126 show_wrap_guides: None,
2127 show_indent_guides,
2128 highlight_order: 0,
2129 highlighted_rows: HashMap::default(),
2130 background_highlights: HashMap::default(),
2131 gutter_highlights: HashMap::default(),
2132 scrollbar_marker_state: ScrollbarMarkerState::default(),
2133 active_indent_guides_state: ActiveIndentGuidesState::default(),
2134 nav_history: None,
2135 context_menu: RefCell::new(None),
2136 context_menu_options: None,
2137 mouse_context_menu: None,
2138 completion_tasks: Vec::new(),
2139 inline_blame_popover: None,
2140 inline_blame_popover_show_task: None,
2141 signature_help_state: SignatureHelpState::default(),
2142 auto_signature_help: None,
2143 find_all_references_task_sources: Vec::new(),
2144 next_completion_id: 0,
2145 next_inlay_id: 0,
2146 code_action_providers,
2147 available_code_actions: None,
2148 code_actions_task: None,
2149 quick_selection_highlight_task: None,
2150 debounced_selection_highlight_task: None,
2151 document_highlights_task: None,
2152 linked_editing_range_task: None,
2153 pending_rename: None,
2154 searchable: !is_minimap,
2155 cursor_shape: EditorSettings::get_global(cx)
2156 .cursor_shape
2157 .unwrap_or_default(),
2158 current_line_highlight: None,
2159 autoindent_mode: Some(AutoindentMode::EachLine),
2160 collapse_matches: false,
2161 workspace: None,
2162 input_enabled: !is_minimap,
2163 use_modal_editing: full_mode,
2164 read_only: is_minimap,
2165 use_autoclose: true,
2166 use_auto_surround: true,
2167 auto_replace_emoji_shortcode: false,
2168 jsx_tag_auto_close_enabled_in_any_buffer: false,
2169 leader_id: None,
2170 remote_id: None,
2171 hover_state: HoverState::default(),
2172 pending_mouse_down: None,
2173 hovered_link_state: None,
2174 edit_prediction_provider: None,
2175 active_edit_prediction: None,
2176 stale_edit_prediction_in_menu: None,
2177 edit_prediction_preview: EditPredictionPreview::Inactive {
2178 released_too_fast: false,
2179 },
2180 inline_diagnostics_enabled: full_mode,
2181 diagnostics_enabled: full_mode,
2182 word_completions_enabled: full_mode,
2183 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2184 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2185 gutter_hovered: false,
2186 pixel_position_of_newest_cursor: None,
2187 last_bounds: None,
2188 last_position_map: None,
2189 expect_bounds_change: None,
2190 gutter_dimensions: GutterDimensions::default(),
2191 style: None,
2192 show_cursor_names: false,
2193 hovered_cursors: HashMap::default(),
2194 next_editor_action_id: EditorActionId::default(),
2195 editor_actions: Rc::default(),
2196 edit_predictions_hidden_for_vim_mode: false,
2197 show_edit_predictions_override: None,
2198 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2199 edit_prediction_settings: EditPredictionSettings::Disabled,
2200 edit_prediction_indent_conflict: false,
2201 edit_prediction_requires_modifier_in_indent_conflict: true,
2202 custom_context_menu: None,
2203 show_git_blame_gutter: false,
2204 show_git_blame_inline: false,
2205 show_selection_menu: None,
2206 show_git_blame_inline_delay_task: None,
2207 git_blame_inline_enabled: full_mode
2208 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2209 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2210 serialize_dirty_buffers: !is_minimap
2211 && ProjectSettings::get_global(cx)
2212 .session
2213 .restore_unsaved_buffers,
2214 blame: None,
2215 blame_subscription: None,
2216 tasks: BTreeMap::default(),
2217
2218 breakpoint_store,
2219 gutter_breakpoint_indicator: (None, None),
2220 hovered_diff_hunk_row: None,
2221 _subscriptions: (!is_minimap)
2222 .then(|| {
2223 vec![
2224 cx.observe(&buffer, Self::on_buffer_changed),
2225 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2226 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2227 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2228 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2229 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2230 cx.observe_window_activation(window, |editor, window, cx| {
2231 let active = window.is_window_active();
2232 editor.blink_manager.update(cx, |blink_manager, cx| {
2233 if active {
2234 blink_manager.enable(cx);
2235 } else {
2236 blink_manager.disable(cx);
2237 }
2238 });
2239 if active {
2240 editor.show_mouse_cursor(cx);
2241 }
2242 }),
2243 ]
2244 })
2245 .unwrap_or_default(),
2246 tasks_update_task: None,
2247 pull_diagnostics_task: Task::ready(()),
2248 colors: None,
2249 refresh_colors_task: Task::ready(()),
2250 next_color_inlay_id: 0,
2251 linked_edit_ranges: Default::default(),
2252 in_project_search: false,
2253 previous_search_ranges: None,
2254 breadcrumb_header: None,
2255 focused_block: None,
2256 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2257 addons: HashMap::default(),
2258 registered_buffers: HashMap::default(),
2259 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2260 selection_mark_mode: false,
2261 toggle_fold_multiple_buffers: Task::ready(()),
2262 serialize_selections: Task::ready(()),
2263 serialize_folds: Task::ready(()),
2264 text_style_refinement: None,
2265 load_diff_task: load_uncommitted_diff,
2266 temporary_diff_override: false,
2267 mouse_cursor_hidden: false,
2268 minimap: None,
2269 hide_mouse_mode: EditorSettings::get_global(cx)
2270 .hide_mouse
2271 .unwrap_or_default(),
2272 change_list: ChangeList::new(),
2273 mode,
2274 selection_drag_state: SelectionDragState::None,
2275 folding_newlines: Task::ready(()),
2276 lookup_key: None,
2277 };
2278
2279 if is_minimap {
2280 return editor;
2281 }
2282
2283 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2284 editor
2285 ._subscriptions
2286 .push(cx.observe(breakpoints, |_, _, cx| {
2287 cx.notify();
2288 }));
2289 }
2290 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2291 editor._subscriptions.extend(project_subscriptions);
2292
2293 editor._subscriptions.push(cx.subscribe_in(
2294 &cx.entity(),
2295 window,
2296 |editor, _, e: &EditorEvent, window, cx| match e {
2297 EditorEvent::ScrollPositionChanged { local, .. } => {
2298 if *local {
2299 let new_anchor = editor.scroll_manager.anchor();
2300 let snapshot = editor.snapshot(window, cx);
2301 editor.update_restoration_data(cx, move |data| {
2302 data.scroll_position = (
2303 new_anchor.top_row(snapshot.buffer_snapshot()),
2304 new_anchor.offset,
2305 );
2306 });
2307 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2308 editor.inline_blame_popover.take();
2309 }
2310 }
2311 EditorEvent::Edited { .. } => {
2312 if !vim_enabled(cx) {
2313 let (map, selections) = editor.selections.all_adjusted_display(cx);
2314 let pop_state = editor
2315 .change_list
2316 .last()
2317 .map(|previous| {
2318 previous.len() == selections.len()
2319 && previous.iter().enumerate().all(|(ix, p)| {
2320 p.to_display_point(&map).row()
2321 == selections[ix].head().row()
2322 })
2323 })
2324 .unwrap_or(false);
2325 let new_positions = selections
2326 .into_iter()
2327 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2328 .collect();
2329 editor
2330 .change_list
2331 .push_to_change_list(pop_state, new_positions);
2332 }
2333 }
2334 _ => (),
2335 },
2336 ));
2337
2338 if let Some(dap_store) = editor
2339 .project
2340 .as_ref()
2341 .map(|project| project.read(cx).dap_store())
2342 {
2343 let weak_editor = cx.weak_entity();
2344
2345 editor
2346 ._subscriptions
2347 .push(
2348 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2349 let session_entity = cx.entity();
2350 weak_editor
2351 .update(cx, |editor, cx| {
2352 editor._subscriptions.push(
2353 cx.subscribe(&session_entity, Self::on_debug_session_event),
2354 );
2355 })
2356 .ok();
2357 }),
2358 );
2359
2360 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2361 editor
2362 ._subscriptions
2363 .push(cx.subscribe(&session, Self::on_debug_session_event));
2364 }
2365 }
2366
2367 // skip adding the initial selection to selection history
2368 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2369 editor.end_selection(window, cx);
2370 editor.selection_history.mode = SelectionHistoryMode::Normal;
2371
2372 editor.scroll_manager.show_scrollbars(window, cx);
2373 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2374
2375 if full_mode {
2376 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2377 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2378
2379 if editor.git_blame_inline_enabled {
2380 editor.start_git_blame_inline(false, window, cx);
2381 }
2382
2383 editor.go_to_active_debug_line(window, cx);
2384
2385 if let Some(buffer) = buffer.read(cx).as_singleton()
2386 && let Some(project) = editor.project()
2387 {
2388 let handle = project.update(cx, |project, cx| {
2389 project.register_buffer_with_language_servers(&buffer, cx)
2390 });
2391 editor
2392 .registered_buffers
2393 .insert(buffer.read(cx).remote_id(), handle);
2394 }
2395
2396 editor.minimap =
2397 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2398 editor.colors = Some(LspColorData::new(cx));
2399 editor.update_lsp_data(false, None, window, cx);
2400 }
2401
2402 if editor.mode.is_full() {
2403 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2404 }
2405
2406 editor
2407 }
2408
2409 pub fn deploy_mouse_context_menu(
2410 &mut self,
2411 position: gpui::Point<Pixels>,
2412 context_menu: Entity<ContextMenu>,
2413 window: &mut Window,
2414 cx: &mut Context<Self>,
2415 ) {
2416 self.mouse_context_menu = Some(MouseContextMenu::new(
2417 self,
2418 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2419 context_menu,
2420 window,
2421 cx,
2422 ));
2423 }
2424
2425 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2426 self.mouse_context_menu
2427 .as_ref()
2428 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2429 }
2430
2431 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2432 if self
2433 .selections
2434 .pending_anchor()
2435 .is_some_and(|pending_selection| {
2436 let snapshot = self.buffer().read(cx).snapshot(cx);
2437 pending_selection.range().includes(range, &snapshot)
2438 })
2439 {
2440 return true;
2441 }
2442
2443 self.selections
2444 .disjoint_in_range::<usize>(range.clone(), cx)
2445 .into_iter()
2446 .any(|selection| {
2447 // This is needed to cover a corner case, if we just check for an existing
2448 // selection in the fold range, having a cursor at the start of the fold
2449 // marks it as selected. Non-empty selections don't cause this.
2450 let length = selection.end - selection.start;
2451 length > 0
2452 })
2453 }
2454
2455 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2456 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2457 }
2458
2459 fn key_context_internal(
2460 &self,
2461 has_active_edit_prediction: bool,
2462 window: &Window,
2463 cx: &App,
2464 ) -> KeyContext {
2465 let mut key_context = KeyContext::new_with_defaults();
2466 key_context.add("Editor");
2467 let mode = match self.mode {
2468 EditorMode::SingleLine => "single_line",
2469 EditorMode::AutoHeight { .. } => "auto_height",
2470 EditorMode::Minimap { .. } => "minimap",
2471 EditorMode::Full { .. } => "full",
2472 };
2473
2474 if EditorSettings::jupyter_enabled(cx) {
2475 key_context.add("jupyter");
2476 }
2477
2478 key_context.set("mode", mode);
2479 if self.pending_rename.is_some() {
2480 key_context.add("renaming");
2481 }
2482
2483 match self.context_menu.borrow().as_ref() {
2484 Some(CodeContextMenu::Completions(menu)) => {
2485 if menu.visible() {
2486 key_context.add("menu");
2487 key_context.add("showing_completions");
2488 }
2489 }
2490 Some(CodeContextMenu::CodeActions(menu)) => {
2491 if menu.visible() {
2492 key_context.add("menu");
2493 key_context.add("showing_code_actions")
2494 }
2495 }
2496 None => {}
2497 }
2498
2499 if self.signature_help_state.has_multiple_signatures() {
2500 key_context.add("showing_signature_help");
2501 }
2502
2503 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2504 if !self.focus_handle(cx).contains_focused(window, cx)
2505 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2506 {
2507 for addon in self.addons.values() {
2508 addon.extend_key_context(&mut key_context, cx)
2509 }
2510 }
2511
2512 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2513 if let Some(extension) = singleton_buffer
2514 .read(cx)
2515 .file()
2516 .and_then(|file| file.path().extension())
2517 {
2518 key_context.set("extension", extension.to_string());
2519 }
2520 } else {
2521 key_context.add("multibuffer");
2522 }
2523
2524 if has_active_edit_prediction {
2525 if self.edit_prediction_in_conflict() {
2526 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2527 } else {
2528 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2529 key_context.add("copilot_suggestion");
2530 }
2531 }
2532
2533 if self.selection_mark_mode {
2534 key_context.add("selection_mode");
2535 }
2536
2537 key_context
2538 }
2539
2540 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2541 self.last_bounds.as_ref()
2542 }
2543
2544 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2545 if self.mouse_cursor_hidden {
2546 self.mouse_cursor_hidden = false;
2547 cx.notify();
2548 }
2549 }
2550
2551 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2552 let hide_mouse_cursor = match origin {
2553 HideMouseCursorOrigin::TypingAction => {
2554 matches!(
2555 self.hide_mouse_mode,
2556 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2557 )
2558 }
2559 HideMouseCursorOrigin::MovementAction => {
2560 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2561 }
2562 };
2563 if self.mouse_cursor_hidden != hide_mouse_cursor {
2564 self.mouse_cursor_hidden = hide_mouse_cursor;
2565 cx.notify();
2566 }
2567 }
2568
2569 pub fn edit_prediction_in_conflict(&self) -> bool {
2570 if !self.show_edit_predictions_in_menu() {
2571 return false;
2572 }
2573
2574 let showing_completions = self
2575 .context_menu
2576 .borrow()
2577 .as_ref()
2578 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2579
2580 showing_completions
2581 || self.edit_prediction_requires_modifier()
2582 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2583 // bindings to insert tab characters.
2584 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2585 }
2586
2587 pub fn accept_edit_prediction_keybind(
2588 &self,
2589 accept_partial: bool,
2590 window: &Window,
2591 cx: &App,
2592 ) -> AcceptEditPredictionBinding {
2593 let key_context = self.key_context_internal(true, window, cx);
2594 let in_conflict = self.edit_prediction_in_conflict();
2595
2596 let bindings = if accept_partial {
2597 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2598 } else {
2599 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2600 };
2601
2602 // TODO: if the binding contains multiple keystrokes, display all of them, not
2603 // just the first one.
2604 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2605 !in_conflict
2606 || binding
2607 .keystrokes()
2608 .first()
2609 .is_some_and(|keystroke| keystroke.modifiers().modified())
2610 }))
2611 }
2612
2613 pub fn new_file(
2614 workspace: &mut Workspace,
2615 _: &workspace::NewFile,
2616 window: &mut Window,
2617 cx: &mut Context<Workspace>,
2618 ) {
2619 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2620 "Failed to create buffer",
2621 window,
2622 cx,
2623 |e, _, _| match e.error_code() {
2624 ErrorCode::RemoteUpgradeRequired => Some(format!(
2625 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2626 e.error_tag("required").unwrap_or("the latest version")
2627 )),
2628 _ => None,
2629 },
2630 );
2631 }
2632
2633 pub fn new_in_workspace(
2634 workspace: &mut Workspace,
2635 window: &mut Window,
2636 cx: &mut Context<Workspace>,
2637 ) -> Task<Result<Entity<Editor>>> {
2638 let project = workspace.project().clone();
2639 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2640
2641 cx.spawn_in(window, async move |workspace, cx| {
2642 let buffer = create.await?;
2643 workspace.update_in(cx, |workspace, window, cx| {
2644 let editor =
2645 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2646 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2647 editor
2648 })
2649 })
2650 }
2651
2652 fn new_file_vertical(
2653 workspace: &mut Workspace,
2654 _: &workspace::NewFileSplitVertical,
2655 window: &mut Window,
2656 cx: &mut Context<Workspace>,
2657 ) {
2658 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2659 }
2660
2661 fn new_file_horizontal(
2662 workspace: &mut Workspace,
2663 _: &workspace::NewFileSplitHorizontal,
2664 window: &mut Window,
2665 cx: &mut Context<Workspace>,
2666 ) {
2667 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2668 }
2669
2670 fn new_file_in_direction(
2671 workspace: &mut Workspace,
2672 direction: SplitDirection,
2673 window: &mut Window,
2674 cx: &mut Context<Workspace>,
2675 ) {
2676 let project = workspace.project().clone();
2677 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2678
2679 cx.spawn_in(window, async move |workspace, cx| {
2680 let buffer = create.await?;
2681 workspace.update_in(cx, move |workspace, window, cx| {
2682 workspace.split_item(
2683 direction,
2684 Box::new(
2685 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2686 ),
2687 window,
2688 cx,
2689 )
2690 })?;
2691 anyhow::Ok(())
2692 })
2693 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2694 match e.error_code() {
2695 ErrorCode::RemoteUpgradeRequired => Some(format!(
2696 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2697 e.error_tag("required").unwrap_or("the latest version")
2698 )),
2699 _ => None,
2700 }
2701 });
2702 }
2703
2704 pub fn leader_id(&self) -> Option<CollaboratorId> {
2705 self.leader_id
2706 }
2707
2708 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2709 &self.buffer
2710 }
2711
2712 pub fn project(&self) -> Option<&Entity<Project>> {
2713 self.project.as_ref()
2714 }
2715
2716 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2717 self.workspace.as_ref()?.0.upgrade()
2718 }
2719
2720 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2721 self.buffer().read(cx).title(cx)
2722 }
2723
2724 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2725 let git_blame_gutter_max_author_length = self
2726 .render_git_blame_gutter(cx)
2727 .then(|| {
2728 if let Some(blame) = self.blame.as_ref() {
2729 let max_author_length =
2730 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2731 Some(max_author_length)
2732 } else {
2733 None
2734 }
2735 })
2736 .flatten();
2737
2738 EditorSnapshot {
2739 mode: self.mode.clone(),
2740 show_gutter: self.show_gutter,
2741 show_line_numbers: self.show_line_numbers,
2742 show_git_diff_gutter: self.show_git_diff_gutter,
2743 show_code_actions: self.show_code_actions,
2744 show_runnables: self.show_runnables,
2745 show_breakpoints: self.show_breakpoints,
2746 git_blame_gutter_max_author_length,
2747 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2748 placeholder_display_snapshot: self
2749 .placeholder_display_map
2750 .as_ref()
2751 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2752 scroll_anchor: self.scroll_manager.anchor(),
2753 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2754 is_focused: self.focus_handle.is_focused(window),
2755 current_line_highlight: self
2756 .current_line_highlight
2757 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2758 gutter_hovered: self.gutter_hovered,
2759 }
2760 }
2761
2762 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2763 self.buffer.read(cx).language_at(point, cx)
2764 }
2765
2766 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2767 self.buffer.read(cx).read(cx).file_at(point).cloned()
2768 }
2769
2770 pub fn active_excerpt(
2771 &self,
2772 cx: &App,
2773 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2774 self.buffer
2775 .read(cx)
2776 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2777 }
2778
2779 pub fn mode(&self) -> &EditorMode {
2780 &self.mode
2781 }
2782
2783 pub fn set_mode(&mut self, mode: EditorMode) {
2784 self.mode = mode;
2785 }
2786
2787 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2788 self.collaboration_hub.as_deref()
2789 }
2790
2791 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2792 self.collaboration_hub = Some(hub);
2793 }
2794
2795 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2796 self.in_project_search = in_project_search;
2797 }
2798
2799 pub fn set_custom_context_menu(
2800 &mut self,
2801 f: impl 'static
2802 + Fn(
2803 &mut Self,
2804 DisplayPoint,
2805 &mut Window,
2806 &mut Context<Self>,
2807 ) -> Option<Entity<ui::ContextMenu>>,
2808 ) {
2809 self.custom_context_menu = Some(Box::new(f))
2810 }
2811
2812 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2813 self.completion_provider = provider;
2814 }
2815
2816 #[cfg(any(test, feature = "test-support"))]
2817 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2818 self.completion_provider.clone()
2819 }
2820
2821 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2822 self.semantics_provider.clone()
2823 }
2824
2825 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2826 self.semantics_provider = provider;
2827 }
2828
2829 pub fn set_edit_prediction_provider<T>(
2830 &mut self,
2831 provider: Option<Entity<T>>,
2832 window: &mut Window,
2833 cx: &mut Context<Self>,
2834 ) where
2835 T: EditPredictionProvider,
2836 {
2837 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2838 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2839 if this.focus_handle.is_focused(window) {
2840 this.update_visible_edit_prediction(window, cx);
2841 }
2842 }),
2843 provider: Arc::new(provider),
2844 });
2845 self.update_edit_prediction_settings(cx);
2846 self.refresh_edit_prediction(false, false, window, cx);
2847 }
2848
2849 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2850 self.placeholder_display_map
2851 .as_ref()
2852 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2853 }
2854
2855 pub fn set_placeholder_text(
2856 &mut self,
2857 placeholder_text: &str,
2858 window: &mut Window,
2859 cx: &mut Context<Self>,
2860 ) {
2861 let multibuffer = cx
2862 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2863
2864 let style = window.text_style();
2865
2866 self.placeholder_display_map = Some(cx.new(|cx| {
2867 DisplayMap::new(
2868 multibuffer,
2869 style.font(),
2870 style.font_size.to_pixels(window.rem_size()),
2871 None,
2872 FILE_HEADER_HEIGHT,
2873 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2874 Default::default(),
2875 DiagnosticSeverity::Off,
2876 cx,
2877 )
2878 }));
2879 cx.notify();
2880 }
2881
2882 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2883 self.cursor_shape = cursor_shape;
2884
2885 // Disrupt blink for immediate user feedback that the cursor shape has changed
2886 self.blink_manager.update(cx, BlinkManager::show_cursor);
2887
2888 cx.notify();
2889 }
2890
2891 pub fn set_current_line_highlight(
2892 &mut self,
2893 current_line_highlight: Option<CurrentLineHighlight>,
2894 ) {
2895 self.current_line_highlight = current_line_highlight;
2896 }
2897
2898 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2899 self.collapse_matches = collapse_matches;
2900 }
2901
2902 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2903 let buffers = self.buffer.read(cx).all_buffers();
2904 let Some(project) = self.project.as_ref() else {
2905 return;
2906 };
2907 project.update(cx, |project, cx| {
2908 for buffer in buffers {
2909 self.registered_buffers
2910 .entry(buffer.read(cx).remote_id())
2911 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2912 }
2913 })
2914 }
2915
2916 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2917 if self.collapse_matches {
2918 return range.start..range.start;
2919 }
2920 range.clone()
2921 }
2922
2923 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2924 if self.display_map.read(cx).clip_at_line_ends != clip {
2925 self.display_map
2926 .update(cx, |map, _| map.clip_at_line_ends = clip);
2927 }
2928 }
2929
2930 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2931 self.input_enabled = input_enabled;
2932 }
2933
2934 pub fn set_edit_predictions_hidden_for_vim_mode(
2935 &mut self,
2936 hidden: bool,
2937 window: &mut Window,
2938 cx: &mut Context<Self>,
2939 ) {
2940 if hidden != self.edit_predictions_hidden_for_vim_mode {
2941 self.edit_predictions_hidden_for_vim_mode = hidden;
2942 if hidden {
2943 self.update_visible_edit_prediction(window, cx);
2944 } else {
2945 self.refresh_edit_prediction(true, false, window, cx);
2946 }
2947 }
2948 }
2949
2950 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2951 self.menu_edit_predictions_policy = value;
2952 }
2953
2954 pub fn set_autoindent(&mut self, autoindent: bool) {
2955 if autoindent {
2956 self.autoindent_mode = Some(AutoindentMode::EachLine);
2957 } else {
2958 self.autoindent_mode = None;
2959 }
2960 }
2961
2962 pub fn read_only(&self, cx: &App) -> bool {
2963 self.read_only || self.buffer.read(cx).read_only()
2964 }
2965
2966 pub fn set_read_only(&mut self, read_only: bool) {
2967 self.read_only = read_only;
2968 }
2969
2970 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2971 self.use_autoclose = autoclose;
2972 }
2973
2974 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2975 self.use_auto_surround = auto_surround;
2976 }
2977
2978 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2979 self.auto_replace_emoji_shortcode = auto_replace;
2980 }
2981
2982 pub fn toggle_edit_predictions(
2983 &mut self,
2984 _: &ToggleEditPrediction,
2985 window: &mut Window,
2986 cx: &mut Context<Self>,
2987 ) {
2988 if self.show_edit_predictions_override.is_some() {
2989 self.set_show_edit_predictions(None, window, cx);
2990 } else {
2991 let show_edit_predictions = !self.edit_predictions_enabled();
2992 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2993 }
2994 }
2995
2996 pub fn set_show_edit_predictions(
2997 &mut self,
2998 show_edit_predictions: Option<bool>,
2999 window: &mut Window,
3000 cx: &mut Context<Self>,
3001 ) {
3002 self.show_edit_predictions_override = show_edit_predictions;
3003 self.update_edit_prediction_settings(cx);
3004
3005 if let Some(false) = show_edit_predictions {
3006 self.discard_edit_prediction(false, cx);
3007 } else {
3008 self.refresh_edit_prediction(false, true, window, cx);
3009 }
3010 }
3011
3012 fn edit_predictions_disabled_in_scope(
3013 &self,
3014 buffer: &Entity<Buffer>,
3015 buffer_position: language::Anchor,
3016 cx: &App,
3017 ) -> bool {
3018 let snapshot = buffer.read(cx).snapshot();
3019 let settings = snapshot.settings_at(buffer_position, cx);
3020
3021 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3022 return false;
3023 };
3024
3025 scope.override_name().is_some_and(|scope_name| {
3026 settings
3027 .edit_predictions_disabled_in
3028 .iter()
3029 .any(|s| s == scope_name)
3030 })
3031 }
3032
3033 pub fn set_use_modal_editing(&mut self, to: bool) {
3034 self.use_modal_editing = to;
3035 }
3036
3037 pub fn use_modal_editing(&self) -> bool {
3038 self.use_modal_editing
3039 }
3040
3041 fn selections_did_change(
3042 &mut self,
3043 local: bool,
3044 old_cursor_position: &Anchor,
3045 effects: SelectionEffects,
3046 window: &mut Window,
3047 cx: &mut Context<Self>,
3048 ) {
3049 window.invalidate_character_coordinates();
3050
3051 // Copy selections to primary selection buffer
3052 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3053 if local {
3054 let selections = self.selections.all::<usize>(cx);
3055 let buffer_handle = self.buffer.read(cx).read(cx);
3056
3057 let mut text = String::new();
3058 for (index, selection) in selections.iter().enumerate() {
3059 let text_for_selection = buffer_handle
3060 .text_for_range(selection.start..selection.end)
3061 .collect::<String>();
3062
3063 text.push_str(&text_for_selection);
3064 if index != selections.len() - 1 {
3065 text.push('\n');
3066 }
3067 }
3068
3069 if !text.is_empty() {
3070 cx.write_to_primary(ClipboardItem::new_string(text));
3071 }
3072 }
3073
3074 let selection_anchors = self.selections.disjoint_anchors_arc();
3075
3076 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3077 self.buffer.update(cx, |buffer, cx| {
3078 buffer.set_active_selections(
3079 &selection_anchors,
3080 self.selections.line_mode(),
3081 self.cursor_shape,
3082 cx,
3083 )
3084 });
3085 }
3086 let display_map = self
3087 .display_map
3088 .update(cx, |display_map, cx| display_map.snapshot(cx));
3089 let buffer = display_map.buffer_snapshot();
3090 if self.selections.count() == 1 {
3091 self.add_selections_state = None;
3092 }
3093 self.select_next_state = None;
3094 self.select_prev_state = None;
3095 self.select_syntax_node_history.try_clear();
3096 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3097 self.snippet_stack.invalidate(&selection_anchors, buffer);
3098 self.take_rename(false, window, cx);
3099
3100 let newest_selection = self.selections.newest_anchor();
3101 let new_cursor_position = newest_selection.head();
3102 let selection_start = newest_selection.start;
3103
3104 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3105 self.push_to_nav_history(
3106 *old_cursor_position,
3107 Some(new_cursor_position.to_point(buffer)),
3108 false,
3109 effects.nav_history == Some(true),
3110 cx,
3111 );
3112 }
3113
3114 if local {
3115 if let Some(buffer_id) = new_cursor_position.buffer_id
3116 && !self.registered_buffers.contains_key(&buffer_id)
3117 && let Some(project) = self.project.as_ref()
3118 {
3119 project.update(cx, |project, cx| {
3120 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
3121 return;
3122 };
3123 self.registered_buffers.insert(
3124 buffer_id,
3125 project.register_buffer_with_language_servers(&buffer, cx),
3126 );
3127 })
3128 }
3129
3130 let mut context_menu = self.context_menu.borrow_mut();
3131 let completion_menu = match context_menu.as_ref() {
3132 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3133 Some(CodeContextMenu::CodeActions(_)) => {
3134 *context_menu = None;
3135 None
3136 }
3137 None => None,
3138 };
3139 let completion_position = completion_menu.map(|menu| menu.initial_position);
3140 drop(context_menu);
3141
3142 if effects.completions
3143 && let Some(completion_position) = completion_position
3144 {
3145 let start_offset = selection_start.to_offset(buffer);
3146 let position_matches = start_offset == completion_position.to_offset(buffer);
3147 let continue_showing = if position_matches {
3148 if self.snippet_stack.is_empty() {
3149 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3150 == Some(CharKind::Word)
3151 } else {
3152 // Snippet choices can be shown even when the cursor is in whitespace.
3153 // Dismissing the menu with actions like backspace is handled by
3154 // invalidation regions.
3155 true
3156 }
3157 } else {
3158 false
3159 };
3160
3161 if continue_showing {
3162 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3163 } else {
3164 self.hide_context_menu(window, cx);
3165 }
3166 }
3167
3168 hide_hover(self, cx);
3169
3170 if old_cursor_position.to_display_point(&display_map).row()
3171 != new_cursor_position.to_display_point(&display_map).row()
3172 {
3173 self.available_code_actions.take();
3174 }
3175 self.refresh_code_actions(window, cx);
3176 self.refresh_document_highlights(cx);
3177 self.refresh_selected_text_highlights(false, window, cx);
3178 refresh_matching_bracket_highlights(self, cx);
3179 self.update_visible_edit_prediction(window, cx);
3180 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3181 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3182 self.inline_blame_popover.take();
3183 if self.git_blame_inline_enabled {
3184 self.start_inline_blame_timer(window, cx);
3185 }
3186 }
3187
3188 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3189 cx.emit(EditorEvent::SelectionsChanged { local });
3190
3191 let selections = &self.selections.disjoint_anchors_arc();
3192 if selections.len() == 1 {
3193 cx.emit(SearchEvent::ActiveMatchChanged)
3194 }
3195 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3196 let inmemory_selections = selections
3197 .iter()
3198 .map(|s| {
3199 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3200 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3201 })
3202 .collect();
3203 self.update_restoration_data(cx, |data| {
3204 data.selections = inmemory_selections;
3205 });
3206
3207 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3208 && let Some(workspace_id) =
3209 self.workspace.as_ref().and_then(|workspace| workspace.1)
3210 {
3211 let snapshot = self.buffer().read(cx).snapshot(cx);
3212 let selections = selections.clone();
3213 let background_executor = cx.background_executor().clone();
3214 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3215 self.serialize_selections = cx.background_spawn(async move {
3216 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3217 let db_selections = selections
3218 .iter()
3219 .map(|selection| {
3220 (
3221 selection.start.to_offset(&snapshot),
3222 selection.end.to_offset(&snapshot),
3223 )
3224 })
3225 .collect();
3226
3227 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3228 .await
3229 .with_context(|| {
3230 format!(
3231 "persisting editor selections for editor {editor_id}, \
3232 workspace {workspace_id:?}"
3233 )
3234 })
3235 .log_err();
3236 });
3237 }
3238 }
3239
3240 cx.notify();
3241 }
3242
3243 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3244 use text::ToOffset as _;
3245 use text::ToPoint as _;
3246
3247 if self.mode.is_minimap()
3248 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3249 {
3250 return;
3251 }
3252
3253 if !self.buffer().read(cx).is_singleton() {
3254 return;
3255 }
3256
3257 let display_snapshot = self
3258 .display_map
3259 .update(cx, |display_map, cx| display_map.snapshot(cx));
3260 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3261 return;
3262 };
3263 let inmemory_folds = display_snapshot
3264 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3265 .map(|fold| {
3266 fold.range.start.text_anchor.to_point(&snapshot)
3267 ..fold.range.end.text_anchor.to_point(&snapshot)
3268 })
3269 .collect();
3270 self.update_restoration_data(cx, |data| {
3271 data.folds = inmemory_folds;
3272 });
3273
3274 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3275 return;
3276 };
3277 let background_executor = cx.background_executor().clone();
3278 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3279 let db_folds = display_snapshot
3280 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3281 .map(|fold| {
3282 (
3283 fold.range.start.text_anchor.to_offset(&snapshot),
3284 fold.range.end.text_anchor.to_offset(&snapshot),
3285 )
3286 })
3287 .collect();
3288 self.serialize_folds = cx.background_spawn(async move {
3289 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3290 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3291 .await
3292 .with_context(|| {
3293 format!(
3294 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3295 )
3296 })
3297 .log_err();
3298 });
3299 }
3300
3301 pub fn sync_selections(
3302 &mut self,
3303 other: Entity<Editor>,
3304 cx: &mut Context<Self>,
3305 ) -> gpui::Subscription {
3306 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3307 if !other_selections.is_empty() {
3308 self.selections.change_with(cx, |selections| {
3309 selections.select_anchors(other_selections);
3310 });
3311 }
3312
3313 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3314 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3315 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3316 if other_selections.is_empty() {
3317 return;
3318 }
3319 this.selections.change_with(cx, |selections| {
3320 selections.select_anchors(other_selections);
3321 });
3322 }
3323 });
3324
3325 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3326 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3327 let these_selections = this.selections.disjoint_anchors().to_vec();
3328 if these_selections.is_empty() {
3329 return;
3330 }
3331 other.update(cx, |other_editor, cx| {
3332 other_editor.selections.change_with(cx, |selections| {
3333 selections.select_anchors(these_selections);
3334 })
3335 });
3336 }
3337 });
3338
3339 Subscription::join(other_subscription, this_subscription)
3340 }
3341
3342 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3343 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3344 /// effects of selection change occur at the end of the transaction.
3345 pub fn change_selections<R>(
3346 &mut self,
3347 effects: SelectionEffects,
3348 window: &mut Window,
3349 cx: &mut Context<Self>,
3350 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3351 ) -> R {
3352 if let Some(state) = &mut self.deferred_selection_effects_state {
3353 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3354 state.effects.completions = effects.completions;
3355 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3356 let (changed, result) = self.selections.change_with(cx, change);
3357 state.changed |= changed;
3358 return result;
3359 }
3360 let mut state = DeferredSelectionEffectsState {
3361 changed: false,
3362 effects,
3363 old_cursor_position: self.selections.newest_anchor().head(),
3364 history_entry: SelectionHistoryEntry {
3365 selections: self.selections.disjoint_anchors_arc(),
3366 select_next_state: self.select_next_state.clone(),
3367 select_prev_state: self.select_prev_state.clone(),
3368 add_selections_state: self.add_selections_state.clone(),
3369 },
3370 };
3371 let (changed, result) = self.selections.change_with(cx, change);
3372 state.changed = state.changed || changed;
3373 if self.defer_selection_effects {
3374 self.deferred_selection_effects_state = Some(state);
3375 } else {
3376 self.apply_selection_effects(state, window, cx);
3377 }
3378 result
3379 }
3380
3381 /// Defers the effects of selection change, so that the effects of multiple calls to
3382 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3383 /// to selection history and the state of popovers based on selection position aren't
3384 /// erroneously updated.
3385 pub fn with_selection_effects_deferred<R>(
3386 &mut self,
3387 window: &mut Window,
3388 cx: &mut Context<Self>,
3389 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3390 ) -> R {
3391 let already_deferred = self.defer_selection_effects;
3392 self.defer_selection_effects = true;
3393 let result = update(self, window, cx);
3394 if !already_deferred {
3395 self.defer_selection_effects = false;
3396 if let Some(state) = self.deferred_selection_effects_state.take() {
3397 self.apply_selection_effects(state, window, cx);
3398 }
3399 }
3400 result
3401 }
3402
3403 fn apply_selection_effects(
3404 &mut self,
3405 state: DeferredSelectionEffectsState,
3406 window: &mut Window,
3407 cx: &mut Context<Self>,
3408 ) {
3409 if state.changed {
3410 self.selection_history.push(state.history_entry);
3411
3412 if let Some(autoscroll) = state.effects.scroll {
3413 self.request_autoscroll(autoscroll, cx);
3414 }
3415
3416 let old_cursor_position = &state.old_cursor_position;
3417
3418 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3419
3420 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3421 self.show_signature_help(&ShowSignatureHelp, window, cx);
3422 }
3423 }
3424 }
3425
3426 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3427 where
3428 I: IntoIterator<Item = (Range<S>, T)>,
3429 S: ToOffset,
3430 T: Into<Arc<str>>,
3431 {
3432 if self.read_only(cx) {
3433 return;
3434 }
3435
3436 self.buffer
3437 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3438 }
3439
3440 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3441 where
3442 I: IntoIterator<Item = (Range<S>, T)>,
3443 S: ToOffset,
3444 T: Into<Arc<str>>,
3445 {
3446 if self.read_only(cx) {
3447 return;
3448 }
3449
3450 self.buffer.update(cx, |buffer, cx| {
3451 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3452 });
3453 }
3454
3455 pub fn edit_with_block_indent<I, S, T>(
3456 &mut self,
3457 edits: I,
3458 original_indent_columns: Vec<Option<u32>>,
3459 cx: &mut Context<Self>,
3460 ) where
3461 I: IntoIterator<Item = (Range<S>, T)>,
3462 S: ToOffset,
3463 T: Into<Arc<str>>,
3464 {
3465 if self.read_only(cx) {
3466 return;
3467 }
3468
3469 self.buffer.update(cx, |buffer, cx| {
3470 buffer.edit(
3471 edits,
3472 Some(AutoindentMode::Block {
3473 original_indent_columns,
3474 }),
3475 cx,
3476 )
3477 });
3478 }
3479
3480 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3481 self.hide_context_menu(window, cx);
3482
3483 match phase {
3484 SelectPhase::Begin {
3485 position,
3486 add,
3487 click_count,
3488 } => self.begin_selection(position, add, click_count, window, cx),
3489 SelectPhase::BeginColumnar {
3490 position,
3491 goal_column,
3492 reset,
3493 mode,
3494 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3495 SelectPhase::Extend {
3496 position,
3497 click_count,
3498 } => self.extend_selection(position, click_count, window, cx),
3499 SelectPhase::Update {
3500 position,
3501 goal_column,
3502 scroll_delta,
3503 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3504 SelectPhase::End => self.end_selection(window, cx),
3505 }
3506 }
3507
3508 fn extend_selection(
3509 &mut self,
3510 position: DisplayPoint,
3511 click_count: usize,
3512 window: &mut Window,
3513 cx: &mut Context<Self>,
3514 ) {
3515 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3516 let tail = self.selections.newest::<usize>(cx).tail();
3517 let click_count = click_count.max(match self.selections.select_mode() {
3518 SelectMode::Character => 1,
3519 SelectMode::Word(_) => 2,
3520 SelectMode::Line(_) => 3,
3521 SelectMode::All => 4,
3522 });
3523 self.begin_selection(position, false, click_count, window, cx);
3524
3525 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3526
3527 let current_selection = match self.selections.select_mode() {
3528 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3529 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3530 };
3531
3532 let mut pending_selection = self
3533 .selections
3534 .pending_anchor()
3535 .cloned()
3536 .expect("extend_selection not called with pending selection");
3537
3538 if pending_selection
3539 .start
3540 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3541 == Ordering::Greater
3542 {
3543 pending_selection.start = current_selection.start;
3544 }
3545 if pending_selection
3546 .end
3547 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3548 == Ordering::Less
3549 {
3550 pending_selection.end = current_selection.end;
3551 pending_selection.reversed = true;
3552 }
3553
3554 let mut pending_mode = self.selections.pending_mode().unwrap();
3555 match &mut pending_mode {
3556 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3557 _ => {}
3558 }
3559
3560 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3561 SelectionEffects::scroll(Autoscroll::fit())
3562 } else {
3563 SelectionEffects::no_scroll()
3564 };
3565
3566 self.change_selections(effects, window, cx, |s| {
3567 s.set_pending(pending_selection.clone(), pending_mode);
3568 s.set_is_extending(true);
3569 });
3570 }
3571
3572 fn begin_selection(
3573 &mut self,
3574 position: DisplayPoint,
3575 add: bool,
3576 click_count: usize,
3577 window: &mut Window,
3578 cx: &mut Context<Self>,
3579 ) {
3580 if !self.focus_handle.is_focused(window) {
3581 self.last_focused_descendant = None;
3582 window.focus(&self.focus_handle);
3583 }
3584
3585 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3586 let buffer = display_map.buffer_snapshot();
3587 let position = display_map.clip_point(position, Bias::Left);
3588
3589 let start;
3590 let end;
3591 let mode;
3592 let mut auto_scroll;
3593 match click_count {
3594 1 => {
3595 start = buffer.anchor_before(position.to_point(&display_map));
3596 end = start;
3597 mode = SelectMode::Character;
3598 auto_scroll = true;
3599 }
3600 2 => {
3601 let position = display_map
3602 .clip_point(position, Bias::Left)
3603 .to_offset(&display_map, Bias::Left);
3604 let (range, _) = buffer.surrounding_word(position, None);
3605 start = buffer.anchor_before(range.start);
3606 end = buffer.anchor_before(range.end);
3607 mode = SelectMode::Word(start..end);
3608 auto_scroll = true;
3609 }
3610 3 => {
3611 let position = display_map
3612 .clip_point(position, Bias::Left)
3613 .to_point(&display_map);
3614 let line_start = display_map.prev_line_boundary(position).0;
3615 let next_line_start = buffer.clip_point(
3616 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3617 Bias::Left,
3618 );
3619 start = buffer.anchor_before(line_start);
3620 end = buffer.anchor_before(next_line_start);
3621 mode = SelectMode::Line(start..end);
3622 auto_scroll = true;
3623 }
3624 _ => {
3625 start = buffer.anchor_before(0);
3626 end = buffer.anchor_before(buffer.len());
3627 mode = SelectMode::All;
3628 auto_scroll = false;
3629 }
3630 }
3631 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3632
3633 let point_to_delete: Option<usize> = {
3634 let selected_points: Vec<Selection<Point>> =
3635 self.selections.disjoint_in_range(start..end, cx);
3636
3637 if !add || click_count > 1 {
3638 None
3639 } else if !selected_points.is_empty() {
3640 Some(selected_points[0].id)
3641 } else {
3642 let clicked_point_already_selected =
3643 self.selections.disjoint_anchors().iter().find(|selection| {
3644 selection.start.to_point(buffer) == start.to_point(buffer)
3645 || selection.end.to_point(buffer) == end.to_point(buffer)
3646 });
3647
3648 clicked_point_already_selected.map(|selection| selection.id)
3649 }
3650 };
3651
3652 let selections_count = self.selections.count();
3653 let effects = if auto_scroll {
3654 SelectionEffects::default()
3655 } else {
3656 SelectionEffects::no_scroll()
3657 };
3658
3659 self.change_selections(effects, window, cx, |s| {
3660 if let Some(point_to_delete) = point_to_delete {
3661 s.delete(point_to_delete);
3662
3663 if selections_count == 1 {
3664 s.set_pending_anchor_range(start..end, mode);
3665 }
3666 } else {
3667 if !add {
3668 s.clear_disjoint();
3669 }
3670
3671 s.set_pending_anchor_range(start..end, mode);
3672 }
3673 });
3674 }
3675
3676 fn begin_columnar_selection(
3677 &mut self,
3678 position: DisplayPoint,
3679 goal_column: u32,
3680 reset: bool,
3681 mode: ColumnarMode,
3682 window: &mut Window,
3683 cx: &mut Context<Self>,
3684 ) {
3685 if !self.focus_handle.is_focused(window) {
3686 self.last_focused_descendant = None;
3687 window.focus(&self.focus_handle);
3688 }
3689
3690 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3691
3692 if reset {
3693 let pointer_position = display_map
3694 .buffer_snapshot()
3695 .anchor_before(position.to_point(&display_map));
3696
3697 self.change_selections(
3698 SelectionEffects::scroll(Autoscroll::newest()),
3699 window,
3700 cx,
3701 |s| {
3702 s.clear_disjoint();
3703 s.set_pending_anchor_range(
3704 pointer_position..pointer_position,
3705 SelectMode::Character,
3706 );
3707 },
3708 );
3709 };
3710
3711 let tail = self.selections.newest::<Point>(cx).tail();
3712 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3713 self.columnar_selection_state = match mode {
3714 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3715 selection_tail: selection_anchor,
3716 display_point: if reset {
3717 if position.column() != goal_column {
3718 Some(DisplayPoint::new(position.row(), goal_column))
3719 } else {
3720 None
3721 }
3722 } else {
3723 None
3724 },
3725 }),
3726 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3727 selection_tail: selection_anchor,
3728 }),
3729 };
3730
3731 if !reset {
3732 self.select_columns(position, goal_column, &display_map, window, cx);
3733 }
3734 }
3735
3736 fn update_selection(
3737 &mut self,
3738 position: DisplayPoint,
3739 goal_column: u32,
3740 scroll_delta: gpui::Point<f32>,
3741 window: &mut Window,
3742 cx: &mut Context<Self>,
3743 ) {
3744 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3745
3746 if self.columnar_selection_state.is_some() {
3747 self.select_columns(position, goal_column, &display_map, window, cx);
3748 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3749 let buffer = display_map.buffer_snapshot();
3750 let head;
3751 let tail;
3752 let mode = self.selections.pending_mode().unwrap();
3753 match &mode {
3754 SelectMode::Character => {
3755 head = position.to_point(&display_map);
3756 tail = pending.tail().to_point(buffer);
3757 }
3758 SelectMode::Word(original_range) => {
3759 let offset = display_map
3760 .clip_point(position, Bias::Left)
3761 .to_offset(&display_map, Bias::Left);
3762 let original_range = original_range.to_offset(buffer);
3763
3764 let head_offset = if buffer.is_inside_word(offset, None)
3765 || original_range.contains(&offset)
3766 {
3767 let (word_range, _) = buffer.surrounding_word(offset, None);
3768 if word_range.start < original_range.start {
3769 word_range.start
3770 } else {
3771 word_range.end
3772 }
3773 } else {
3774 offset
3775 };
3776
3777 head = head_offset.to_point(buffer);
3778 if head_offset <= original_range.start {
3779 tail = original_range.end.to_point(buffer);
3780 } else {
3781 tail = original_range.start.to_point(buffer);
3782 }
3783 }
3784 SelectMode::Line(original_range) => {
3785 let original_range = original_range.to_point(display_map.buffer_snapshot());
3786
3787 let position = display_map
3788 .clip_point(position, Bias::Left)
3789 .to_point(&display_map);
3790 let line_start = display_map.prev_line_boundary(position).0;
3791 let next_line_start = buffer.clip_point(
3792 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3793 Bias::Left,
3794 );
3795
3796 if line_start < original_range.start {
3797 head = line_start
3798 } else {
3799 head = next_line_start
3800 }
3801
3802 if head <= original_range.start {
3803 tail = original_range.end;
3804 } else {
3805 tail = original_range.start;
3806 }
3807 }
3808 SelectMode::All => {
3809 return;
3810 }
3811 };
3812
3813 if head < tail {
3814 pending.start = buffer.anchor_before(head);
3815 pending.end = buffer.anchor_before(tail);
3816 pending.reversed = true;
3817 } else {
3818 pending.start = buffer.anchor_before(tail);
3819 pending.end = buffer.anchor_before(head);
3820 pending.reversed = false;
3821 }
3822
3823 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3824 s.set_pending(pending.clone(), mode);
3825 });
3826 } else {
3827 log::error!("update_selection dispatched with no pending selection");
3828 return;
3829 }
3830
3831 self.apply_scroll_delta(scroll_delta, window, cx);
3832 cx.notify();
3833 }
3834
3835 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3836 self.columnar_selection_state.take();
3837 if let Some(pending_mode) = self.selections.pending_mode() {
3838 let selections = self.selections.all::<usize>(cx);
3839 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3840 s.select(selections);
3841 s.clear_pending();
3842 if s.is_extending() {
3843 s.set_is_extending(false);
3844 } else {
3845 s.set_select_mode(pending_mode);
3846 }
3847 });
3848 }
3849 }
3850
3851 fn select_columns(
3852 &mut self,
3853 head: DisplayPoint,
3854 goal_column: u32,
3855 display_map: &DisplaySnapshot,
3856 window: &mut Window,
3857 cx: &mut Context<Self>,
3858 ) {
3859 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3860 return;
3861 };
3862
3863 let tail = match columnar_state {
3864 ColumnarSelectionState::FromMouse {
3865 selection_tail,
3866 display_point,
3867 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3868 ColumnarSelectionState::FromSelection { selection_tail } => {
3869 selection_tail.to_display_point(display_map)
3870 }
3871 };
3872
3873 let start_row = cmp::min(tail.row(), head.row());
3874 let end_row = cmp::max(tail.row(), head.row());
3875 let start_column = cmp::min(tail.column(), goal_column);
3876 let end_column = cmp::max(tail.column(), goal_column);
3877 let reversed = start_column < tail.column();
3878
3879 let selection_ranges = (start_row.0..=end_row.0)
3880 .map(DisplayRow)
3881 .filter_map(|row| {
3882 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3883 || start_column <= display_map.line_len(row))
3884 && !display_map.is_block_line(row)
3885 {
3886 let start = display_map
3887 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3888 .to_point(display_map);
3889 let end = display_map
3890 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3891 .to_point(display_map);
3892 if reversed {
3893 Some(end..start)
3894 } else {
3895 Some(start..end)
3896 }
3897 } else {
3898 None
3899 }
3900 })
3901 .collect::<Vec<_>>();
3902 if selection_ranges.is_empty() {
3903 return;
3904 }
3905
3906 let ranges = match columnar_state {
3907 ColumnarSelectionState::FromMouse { .. } => {
3908 let mut non_empty_ranges = selection_ranges
3909 .iter()
3910 .filter(|selection_range| selection_range.start != selection_range.end)
3911 .peekable();
3912 if non_empty_ranges.peek().is_some() {
3913 non_empty_ranges.cloned().collect()
3914 } else {
3915 selection_ranges
3916 }
3917 }
3918 _ => selection_ranges,
3919 };
3920
3921 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3922 s.select_ranges(ranges);
3923 });
3924 cx.notify();
3925 }
3926
3927 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3928 self.selections
3929 .all_adjusted(cx)
3930 .iter()
3931 .any(|selection| !selection.is_empty())
3932 }
3933
3934 pub fn has_pending_nonempty_selection(&self) -> bool {
3935 let pending_nonempty_selection = match self.selections.pending_anchor() {
3936 Some(Selection { start, end, .. }) => start != end,
3937 None => false,
3938 };
3939
3940 pending_nonempty_selection
3941 || (self.columnar_selection_state.is_some()
3942 && self.selections.disjoint_anchors().len() > 1)
3943 }
3944
3945 pub fn has_pending_selection(&self) -> bool {
3946 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3947 }
3948
3949 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3950 self.selection_mark_mode = false;
3951 self.selection_drag_state = SelectionDragState::None;
3952
3953 if self.clear_expanded_diff_hunks(cx) {
3954 cx.notify();
3955 return;
3956 }
3957 if self.dismiss_menus_and_popups(true, window, cx) {
3958 return;
3959 }
3960
3961 if self.mode.is_full()
3962 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3963 {
3964 return;
3965 }
3966
3967 cx.propagate();
3968 }
3969
3970 pub fn dismiss_menus_and_popups(
3971 &mut self,
3972 is_user_requested: bool,
3973 window: &mut Window,
3974 cx: &mut Context<Self>,
3975 ) -> bool {
3976 if self.take_rename(false, window, cx).is_some() {
3977 return true;
3978 }
3979
3980 if hide_hover(self, cx) {
3981 return true;
3982 }
3983
3984 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3985 return true;
3986 }
3987
3988 if self.hide_context_menu(window, cx).is_some() {
3989 return true;
3990 }
3991
3992 if self.mouse_context_menu.take().is_some() {
3993 return true;
3994 }
3995
3996 if is_user_requested && self.discard_edit_prediction(true, cx) {
3997 return true;
3998 }
3999
4000 if self.snippet_stack.pop().is_some() {
4001 return true;
4002 }
4003
4004 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4005 self.dismiss_diagnostics(cx);
4006 return true;
4007 }
4008
4009 false
4010 }
4011
4012 fn linked_editing_ranges_for(
4013 &self,
4014 selection: Range<text::Anchor>,
4015 cx: &App,
4016 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4017 if self.linked_edit_ranges.is_empty() {
4018 return None;
4019 }
4020 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4021 selection.end.buffer_id.and_then(|end_buffer_id| {
4022 if selection.start.buffer_id != Some(end_buffer_id) {
4023 return None;
4024 }
4025 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4026 let snapshot = buffer.read(cx).snapshot();
4027 self.linked_edit_ranges
4028 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4029 .map(|ranges| (ranges, snapshot, buffer))
4030 })?;
4031 use text::ToOffset as TO;
4032 // find offset from the start of current range to current cursor position
4033 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4034
4035 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4036 let start_difference = start_offset - start_byte_offset;
4037 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4038 let end_difference = end_offset - start_byte_offset;
4039 // Current range has associated linked ranges.
4040 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4041 for range in linked_ranges.iter() {
4042 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4043 let end_offset = start_offset + end_difference;
4044 let start_offset = start_offset + start_difference;
4045 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4046 continue;
4047 }
4048 if self.selections.disjoint_anchor_ranges().any(|s| {
4049 if s.start.buffer_id != selection.start.buffer_id
4050 || s.end.buffer_id != selection.end.buffer_id
4051 {
4052 return false;
4053 }
4054 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4055 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4056 }) {
4057 continue;
4058 }
4059 let start = buffer_snapshot.anchor_after(start_offset);
4060 let end = buffer_snapshot.anchor_after(end_offset);
4061 linked_edits
4062 .entry(buffer.clone())
4063 .or_default()
4064 .push(start..end);
4065 }
4066 Some(linked_edits)
4067 }
4068
4069 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4070 let text: Arc<str> = text.into();
4071
4072 if self.read_only(cx) {
4073 return;
4074 }
4075
4076 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4077
4078 let selections = self.selections.all_adjusted(cx);
4079 let mut bracket_inserted = false;
4080 let mut edits = Vec::new();
4081 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4082 let mut new_selections = Vec::with_capacity(selections.len());
4083 let mut new_autoclose_regions = Vec::new();
4084 let snapshot = self.buffer.read(cx).read(cx);
4085 let mut clear_linked_edit_ranges = false;
4086
4087 for (selection, autoclose_region) in
4088 self.selections_with_autoclose_regions(selections, &snapshot)
4089 {
4090 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4091 // Determine if the inserted text matches the opening or closing
4092 // bracket of any of this language's bracket pairs.
4093 let mut bracket_pair = None;
4094 let mut is_bracket_pair_start = false;
4095 let mut is_bracket_pair_end = false;
4096 if !text.is_empty() {
4097 let mut bracket_pair_matching_end = None;
4098 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4099 // and they are removing the character that triggered IME popup.
4100 for (pair, enabled) in scope.brackets() {
4101 if !pair.close && !pair.surround {
4102 continue;
4103 }
4104
4105 if enabled && pair.start.ends_with(text.as_ref()) {
4106 let prefix_len = pair.start.len() - text.len();
4107 let preceding_text_matches_prefix = prefix_len == 0
4108 || (selection.start.column >= (prefix_len as u32)
4109 && snapshot.contains_str_at(
4110 Point::new(
4111 selection.start.row,
4112 selection.start.column - (prefix_len as u32),
4113 ),
4114 &pair.start[..prefix_len],
4115 ));
4116 if preceding_text_matches_prefix {
4117 bracket_pair = Some(pair.clone());
4118 is_bracket_pair_start = true;
4119 break;
4120 }
4121 }
4122 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4123 {
4124 // take first bracket pair matching end, but don't break in case a later bracket
4125 // pair matches start
4126 bracket_pair_matching_end = Some(pair.clone());
4127 }
4128 }
4129 if let Some(end) = bracket_pair_matching_end
4130 && bracket_pair.is_none()
4131 {
4132 bracket_pair = Some(end);
4133 is_bracket_pair_end = true;
4134 }
4135 }
4136
4137 if let Some(bracket_pair) = bracket_pair {
4138 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4139 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4140 let auto_surround =
4141 self.use_auto_surround && snapshot_settings.use_auto_surround;
4142 if selection.is_empty() {
4143 if is_bracket_pair_start {
4144 // If the inserted text is a suffix of an opening bracket and the
4145 // selection is preceded by the rest of the opening bracket, then
4146 // insert the closing bracket.
4147 let following_text_allows_autoclose = snapshot
4148 .chars_at(selection.start)
4149 .next()
4150 .is_none_or(|c| scope.should_autoclose_before(c));
4151
4152 let preceding_text_allows_autoclose = selection.start.column == 0
4153 || snapshot
4154 .reversed_chars_at(selection.start)
4155 .next()
4156 .is_none_or(|c| {
4157 bracket_pair.start != bracket_pair.end
4158 || !snapshot
4159 .char_classifier_at(selection.start)
4160 .is_word(c)
4161 });
4162
4163 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4164 && bracket_pair.start.len() == 1
4165 {
4166 let target = bracket_pair.start.chars().next().unwrap();
4167 let current_line_count = snapshot
4168 .reversed_chars_at(selection.start)
4169 .take_while(|&c| c != '\n')
4170 .filter(|&c| c == target)
4171 .count();
4172 current_line_count % 2 == 1
4173 } else {
4174 false
4175 };
4176
4177 if autoclose
4178 && bracket_pair.close
4179 && following_text_allows_autoclose
4180 && preceding_text_allows_autoclose
4181 && !is_closing_quote
4182 {
4183 let anchor = snapshot.anchor_before(selection.end);
4184 new_selections.push((selection.map(|_| anchor), text.len()));
4185 new_autoclose_regions.push((
4186 anchor,
4187 text.len(),
4188 selection.id,
4189 bracket_pair.clone(),
4190 ));
4191 edits.push((
4192 selection.range(),
4193 format!("{}{}", text, bracket_pair.end).into(),
4194 ));
4195 bracket_inserted = true;
4196 continue;
4197 }
4198 }
4199
4200 if let Some(region) = autoclose_region {
4201 // If the selection is followed by an auto-inserted closing bracket,
4202 // then don't insert that closing bracket again; just move the selection
4203 // past the closing bracket.
4204 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4205 && text.as_ref() == region.pair.end.as_str()
4206 && snapshot.contains_str_at(region.range.end, text.as_ref());
4207 if should_skip {
4208 let anchor = snapshot.anchor_after(selection.end);
4209 new_selections
4210 .push((selection.map(|_| anchor), region.pair.end.len()));
4211 continue;
4212 }
4213 }
4214
4215 let always_treat_brackets_as_autoclosed = snapshot
4216 .language_settings_at(selection.start, cx)
4217 .always_treat_brackets_as_autoclosed;
4218 if always_treat_brackets_as_autoclosed
4219 && is_bracket_pair_end
4220 && snapshot.contains_str_at(selection.end, text.as_ref())
4221 {
4222 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4223 // and the inserted text is a closing bracket and the selection is followed
4224 // by the closing bracket then move the selection past the closing bracket.
4225 let anchor = snapshot.anchor_after(selection.end);
4226 new_selections.push((selection.map(|_| anchor), text.len()));
4227 continue;
4228 }
4229 }
4230 // If an opening bracket is 1 character long and is typed while
4231 // text is selected, then surround that text with the bracket pair.
4232 else if auto_surround
4233 && bracket_pair.surround
4234 && is_bracket_pair_start
4235 && bracket_pair.start.chars().count() == 1
4236 {
4237 edits.push((selection.start..selection.start, text.clone()));
4238 edits.push((
4239 selection.end..selection.end,
4240 bracket_pair.end.as_str().into(),
4241 ));
4242 bracket_inserted = true;
4243 new_selections.push((
4244 Selection {
4245 id: selection.id,
4246 start: snapshot.anchor_after(selection.start),
4247 end: snapshot.anchor_before(selection.end),
4248 reversed: selection.reversed,
4249 goal: selection.goal,
4250 },
4251 0,
4252 ));
4253 continue;
4254 }
4255 }
4256 }
4257
4258 if self.auto_replace_emoji_shortcode
4259 && selection.is_empty()
4260 && text.as_ref().ends_with(':')
4261 && let Some(possible_emoji_short_code) =
4262 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4263 && !possible_emoji_short_code.is_empty()
4264 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4265 {
4266 let emoji_shortcode_start = Point::new(
4267 selection.start.row,
4268 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4269 );
4270
4271 // Remove shortcode from buffer
4272 edits.push((
4273 emoji_shortcode_start..selection.start,
4274 "".to_string().into(),
4275 ));
4276 new_selections.push((
4277 Selection {
4278 id: selection.id,
4279 start: snapshot.anchor_after(emoji_shortcode_start),
4280 end: snapshot.anchor_before(selection.start),
4281 reversed: selection.reversed,
4282 goal: selection.goal,
4283 },
4284 0,
4285 ));
4286
4287 // Insert emoji
4288 let selection_start_anchor = snapshot.anchor_after(selection.start);
4289 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4290 edits.push((selection.start..selection.end, emoji.to_string().into()));
4291
4292 continue;
4293 }
4294
4295 // If not handling any auto-close operation, then just replace the selected
4296 // text with the given input and move the selection to the end of the
4297 // newly inserted text.
4298 let anchor = snapshot.anchor_after(selection.end);
4299 if !self.linked_edit_ranges.is_empty() {
4300 let start_anchor = snapshot.anchor_before(selection.start);
4301
4302 let is_word_char = text.chars().next().is_none_or(|char| {
4303 let classifier = snapshot
4304 .char_classifier_at(start_anchor.to_offset(&snapshot))
4305 .scope_context(Some(CharScopeContext::LinkedEdit));
4306 classifier.is_word(char)
4307 });
4308
4309 if is_word_char {
4310 if let Some(ranges) = self
4311 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4312 {
4313 for (buffer, edits) in ranges {
4314 linked_edits
4315 .entry(buffer.clone())
4316 .or_default()
4317 .extend(edits.into_iter().map(|range| (range, text.clone())));
4318 }
4319 }
4320 } else {
4321 clear_linked_edit_ranges = true;
4322 }
4323 }
4324
4325 new_selections.push((selection.map(|_| anchor), 0));
4326 edits.push((selection.start..selection.end, text.clone()));
4327 }
4328
4329 drop(snapshot);
4330
4331 self.transact(window, cx, |this, window, cx| {
4332 if clear_linked_edit_ranges {
4333 this.linked_edit_ranges.clear();
4334 }
4335 let initial_buffer_versions =
4336 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4337
4338 this.buffer.update(cx, |buffer, cx| {
4339 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4340 });
4341 for (buffer, edits) in linked_edits {
4342 buffer.update(cx, |buffer, cx| {
4343 let snapshot = buffer.snapshot();
4344 let edits = edits
4345 .into_iter()
4346 .map(|(range, text)| {
4347 use text::ToPoint as TP;
4348 let end_point = TP::to_point(&range.end, &snapshot);
4349 let start_point = TP::to_point(&range.start, &snapshot);
4350 (start_point..end_point, text)
4351 })
4352 .sorted_by_key(|(range, _)| range.start);
4353 buffer.edit(edits, None, cx);
4354 })
4355 }
4356 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4357 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4358 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4359 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4360 .zip(new_selection_deltas)
4361 .map(|(selection, delta)| Selection {
4362 id: selection.id,
4363 start: selection.start + delta,
4364 end: selection.end + delta,
4365 reversed: selection.reversed,
4366 goal: SelectionGoal::None,
4367 })
4368 .collect::<Vec<_>>();
4369
4370 let mut i = 0;
4371 for (position, delta, selection_id, pair) in new_autoclose_regions {
4372 let position = position.to_offset(map.buffer_snapshot()) + delta;
4373 let start = map.buffer_snapshot().anchor_before(position);
4374 let end = map.buffer_snapshot().anchor_after(position);
4375 while let Some(existing_state) = this.autoclose_regions.get(i) {
4376 match existing_state
4377 .range
4378 .start
4379 .cmp(&start, map.buffer_snapshot())
4380 {
4381 Ordering::Less => i += 1,
4382 Ordering::Greater => break,
4383 Ordering::Equal => {
4384 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4385 Ordering::Less => i += 1,
4386 Ordering::Equal => break,
4387 Ordering::Greater => break,
4388 }
4389 }
4390 }
4391 }
4392 this.autoclose_regions.insert(
4393 i,
4394 AutocloseRegion {
4395 selection_id,
4396 range: start..end,
4397 pair,
4398 },
4399 );
4400 }
4401
4402 let had_active_edit_prediction = this.has_active_edit_prediction();
4403 this.change_selections(
4404 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4405 window,
4406 cx,
4407 |s| s.select(new_selections),
4408 );
4409
4410 if !bracket_inserted
4411 && let Some(on_type_format_task) =
4412 this.trigger_on_type_formatting(text.to_string(), window, cx)
4413 {
4414 on_type_format_task.detach_and_log_err(cx);
4415 }
4416
4417 let editor_settings = EditorSettings::get_global(cx);
4418 if bracket_inserted
4419 && (editor_settings.auto_signature_help
4420 || editor_settings.show_signature_help_after_edits)
4421 {
4422 this.show_signature_help(&ShowSignatureHelp, window, cx);
4423 }
4424
4425 let trigger_in_words =
4426 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4427 if this.hard_wrap.is_some() {
4428 let latest: Range<Point> = this.selections.newest(cx).range();
4429 if latest.is_empty()
4430 && this
4431 .buffer()
4432 .read(cx)
4433 .snapshot(cx)
4434 .line_len(MultiBufferRow(latest.start.row))
4435 == latest.start.column
4436 {
4437 this.rewrap_impl(
4438 RewrapOptions {
4439 override_language_settings: true,
4440 preserve_existing_whitespace: true,
4441 },
4442 cx,
4443 )
4444 }
4445 }
4446 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4447 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4448 this.refresh_edit_prediction(true, false, window, cx);
4449 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4450 });
4451 }
4452
4453 fn find_possible_emoji_shortcode_at_position(
4454 snapshot: &MultiBufferSnapshot,
4455 position: Point,
4456 ) -> Option<String> {
4457 let mut chars = Vec::new();
4458 let mut found_colon = false;
4459 for char in snapshot.reversed_chars_at(position).take(100) {
4460 // Found a possible emoji shortcode in the middle of the buffer
4461 if found_colon {
4462 if char.is_whitespace() {
4463 chars.reverse();
4464 return Some(chars.iter().collect());
4465 }
4466 // If the previous character is not a whitespace, we are in the middle of a word
4467 // and we only want to complete the shortcode if the word is made up of other emojis
4468 let mut containing_word = String::new();
4469 for ch in snapshot
4470 .reversed_chars_at(position)
4471 .skip(chars.len() + 1)
4472 .take(100)
4473 {
4474 if ch.is_whitespace() {
4475 break;
4476 }
4477 containing_word.push(ch);
4478 }
4479 let containing_word = containing_word.chars().rev().collect::<String>();
4480 if util::word_consists_of_emojis(containing_word.as_str()) {
4481 chars.reverse();
4482 return Some(chars.iter().collect());
4483 }
4484 }
4485
4486 if char.is_whitespace() || !char.is_ascii() {
4487 return None;
4488 }
4489 if char == ':' {
4490 found_colon = true;
4491 } else {
4492 chars.push(char);
4493 }
4494 }
4495 // Found a possible emoji shortcode at the beginning of the buffer
4496 chars.reverse();
4497 Some(chars.iter().collect())
4498 }
4499
4500 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4501 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4502 self.transact(window, cx, |this, window, cx| {
4503 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4504 let selections = this.selections.all::<usize>(cx);
4505 let multi_buffer = this.buffer.read(cx);
4506 let buffer = multi_buffer.snapshot(cx);
4507 selections
4508 .iter()
4509 .map(|selection| {
4510 let start_point = selection.start.to_point(&buffer);
4511 let mut existing_indent =
4512 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4513 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4514 let start = selection.start;
4515 let end = selection.end;
4516 let selection_is_empty = start == end;
4517 let language_scope = buffer.language_scope_at(start);
4518 let (
4519 comment_delimiter,
4520 doc_delimiter,
4521 insert_extra_newline,
4522 indent_on_newline,
4523 indent_on_extra_newline,
4524 ) = if let Some(language) = &language_scope {
4525 let mut insert_extra_newline =
4526 insert_extra_newline_brackets(&buffer, start..end, language)
4527 || insert_extra_newline_tree_sitter(&buffer, start..end);
4528
4529 // Comment extension on newline is allowed only for cursor selections
4530 let comment_delimiter = maybe!({
4531 if !selection_is_empty {
4532 return None;
4533 }
4534
4535 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4536 return None;
4537 }
4538
4539 let delimiters = language.line_comment_prefixes();
4540 let max_len_of_delimiter =
4541 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4542 let (snapshot, range) =
4543 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4544
4545 let num_of_whitespaces = snapshot
4546 .chars_for_range(range.clone())
4547 .take_while(|c| c.is_whitespace())
4548 .count();
4549 let comment_candidate = snapshot
4550 .chars_for_range(range.clone())
4551 .skip(num_of_whitespaces)
4552 .take(max_len_of_delimiter)
4553 .collect::<String>();
4554 let (delimiter, trimmed_len) = delimiters
4555 .iter()
4556 .filter_map(|delimiter| {
4557 let prefix = delimiter.trim_end();
4558 if comment_candidate.starts_with(prefix) {
4559 Some((delimiter, prefix.len()))
4560 } else {
4561 None
4562 }
4563 })
4564 .max_by_key(|(_, len)| *len)?;
4565
4566 if let Some(BlockCommentConfig {
4567 start: block_start, ..
4568 }) = language.block_comment()
4569 {
4570 let block_start_trimmed = block_start.trim_end();
4571 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4572 let line_content = snapshot
4573 .chars_for_range(range)
4574 .skip(num_of_whitespaces)
4575 .take(block_start_trimmed.len())
4576 .collect::<String>();
4577
4578 if line_content.starts_with(block_start_trimmed) {
4579 return None;
4580 }
4581 }
4582 }
4583
4584 let cursor_is_placed_after_comment_marker =
4585 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4586 if cursor_is_placed_after_comment_marker {
4587 Some(delimiter.clone())
4588 } else {
4589 None
4590 }
4591 });
4592
4593 let mut indent_on_newline = IndentSize::spaces(0);
4594 let mut indent_on_extra_newline = IndentSize::spaces(0);
4595
4596 let doc_delimiter = maybe!({
4597 if !selection_is_empty {
4598 return None;
4599 }
4600
4601 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4602 return None;
4603 }
4604
4605 let BlockCommentConfig {
4606 start: start_tag,
4607 end: end_tag,
4608 prefix: delimiter,
4609 tab_size: len,
4610 } = language.documentation_comment()?;
4611 let is_within_block_comment = buffer
4612 .language_scope_at(start_point)
4613 .is_some_and(|scope| scope.override_name() == Some("comment"));
4614 if !is_within_block_comment {
4615 return None;
4616 }
4617
4618 let (snapshot, range) =
4619 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4620
4621 let num_of_whitespaces = snapshot
4622 .chars_for_range(range.clone())
4623 .take_while(|c| c.is_whitespace())
4624 .count();
4625
4626 // 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.
4627 let column = start_point.column;
4628 let cursor_is_after_start_tag = {
4629 let start_tag_len = start_tag.len();
4630 let start_tag_line = snapshot
4631 .chars_for_range(range.clone())
4632 .skip(num_of_whitespaces)
4633 .take(start_tag_len)
4634 .collect::<String>();
4635 if start_tag_line.starts_with(start_tag.as_ref()) {
4636 num_of_whitespaces + start_tag_len <= column as usize
4637 } else {
4638 false
4639 }
4640 };
4641
4642 let cursor_is_after_delimiter = {
4643 let delimiter_trim = delimiter.trim_end();
4644 let delimiter_line = snapshot
4645 .chars_for_range(range.clone())
4646 .skip(num_of_whitespaces)
4647 .take(delimiter_trim.len())
4648 .collect::<String>();
4649 if delimiter_line.starts_with(delimiter_trim) {
4650 num_of_whitespaces + delimiter_trim.len() <= column as usize
4651 } else {
4652 false
4653 }
4654 };
4655
4656 let cursor_is_before_end_tag_if_exists = {
4657 let mut char_position = 0u32;
4658 let mut end_tag_offset = None;
4659
4660 'outer: for chunk in snapshot.text_for_range(range) {
4661 if let Some(byte_pos) = chunk.find(&**end_tag) {
4662 let chars_before_match =
4663 chunk[..byte_pos].chars().count() as u32;
4664 end_tag_offset =
4665 Some(char_position + chars_before_match);
4666 break 'outer;
4667 }
4668 char_position += chunk.chars().count() as u32;
4669 }
4670
4671 if let Some(end_tag_offset) = end_tag_offset {
4672 let cursor_is_before_end_tag = column <= end_tag_offset;
4673 if cursor_is_after_start_tag {
4674 if cursor_is_before_end_tag {
4675 insert_extra_newline = true;
4676 }
4677 let cursor_is_at_start_of_end_tag =
4678 column == end_tag_offset;
4679 if cursor_is_at_start_of_end_tag {
4680 indent_on_extra_newline.len = *len;
4681 }
4682 }
4683 cursor_is_before_end_tag
4684 } else {
4685 true
4686 }
4687 };
4688
4689 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4690 && cursor_is_before_end_tag_if_exists
4691 {
4692 if cursor_is_after_start_tag {
4693 indent_on_newline.len = *len;
4694 }
4695 Some(delimiter.clone())
4696 } else {
4697 None
4698 }
4699 });
4700
4701 (
4702 comment_delimiter,
4703 doc_delimiter,
4704 insert_extra_newline,
4705 indent_on_newline,
4706 indent_on_extra_newline,
4707 )
4708 } else {
4709 (
4710 None,
4711 None,
4712 false,
4713 IndentSize::default(),
4714 IndentSize::default(),
4715 )
4716 };
4717
4718 let prevent_auto_indent = doc_delimiter.is_some();
4719 let delimiter = comment_delimiter.or(doc_delimiter);
4720
4721 let capacity_for_delimiter =
4722 delimiter.as_deref().map(str::len).unwrap_or_default();
4723 let mut new_text = String::with_capacity(
4724 1 + capacity_for_delimiter
4725 + existing_indent.len as usize
4726 + indent_on_newline.len as usize
4727 + indent_on_extra_newline.len as usize,
4728 );
4729 new_text.push('\n');
4730 new_text.extend(existing_indent.chars());
4731 new_text.extend(indent_on_newline.chars());
4732
4733 if let Some(delimiter) = &delimiter {
4734 new_text.push_str(delimiter);
4735 }
4736
4737 if insert_extra_newline {
4738 new_text.push('\n');
4739 new_text.extend(existing_indent.chars());
4740 new_text.extend(indent_on_extra_newline.chars());
4741 }
4742
4743 let anchor = buffer.anchor_after(end);
4744 let new_selection = selection.map(|_| anchor);
4745 (
4746 ((start..end, new_text), prevent_auto_indent),
4747 (insert_extra_newline, new_selection),
4748 )
4749 })
4750 .unzip()
4751 };
4752
4753 let mut auto_indent_edits = Vec::new();
4754 let mut edits = Vec::new();
4755 for (edit, prevent_auto_indent) in edits_with_flags {
4756 if prevent_auto_indent {
4757 edits.push(edit);
4758 } else {
4759 auto_indent_edits.push(edit);
4760 }
4761 }
4762 if !edits.is_empty() {
4763 this.edit(edits, cx);
4764 }
4765 if !auto_indent_edits.is_empty() {
4766 this.edit_with_autoindent(auto_indent_edits, cx);
4767 }
4768
4769 let buffer = this.buffer.read(cx).snapshot(cx);
4770 let new_selections = selection_info
4771 .into_iter()
4772 .map(|(extra_newline_inserted, new_selection)| {
4773 let mut cursor = new_selection.end.to_point(&buffer);
4774 if extra_newline_inserted {
4775 cursor.row -= 1;
4776 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4777 }
4778 new_selection.map(|_| cursor)
4779 })
4780 .collect();
4781
4782 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4783 this.refresh_edit_prediction(true, false, window, cx);
4784 });
4785 }
4786
4787 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4788 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4789
4790 let buffer = self.buffer.read(cx);
4791 let snapshot = buffer.snapshot(cx);
4792
4793 let mut edits = Vec::new();
4794 let mut rows = Vec::new();
4795
4796 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4797 let cursor = selection.head();
4798 let row = cursor.row;
4799
4800 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4801
4802 let newline = "\n".to_string();
4803 edits.push((start_of_line..start_of_line, newline));
4804
4805 rows.push(row + rows_inserted as u32);
4806 }
4807
4808 self.transact(window, cx, |editor, window, cx| {
4809 editor.edit(edits, cx);
4810
4811 editor.change_selections(Default::default(), window, cx, |s| {
4812 let mut index = 0;
4813 s.move_cursors_with(|map, _, _| {
4814 let row = rows[index];
4815 index += 1;
4816
4817 let point = Point::new(row, 0);
4818 let boundary = map.next_line_boundary(point).1;
4819 let clipped = map.clip_point(boundary, Bias::Left);
4820
4821 (clipped, SelectionGoal::None)
4822 });
4823 });
4824
4825 let mut indent_edits = Vec::new();
4826 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4827 for row in rows {
4828 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4829 for (row, indent) in indents {
4830 if indent.len == 0 {
4831 continue;
4832 }
4833
4834 let text = match indent.kind {
4835 IndentKind::Space => " ".repeat(indent.len as usize),
4836 IndentKind::Tab => "\t".repeat(indent.len as usize),
4837 };
4838 let point = Point::new(row.0, 0);
4839 indent_edits.push((point..point, text));
4840 }
4841 }
4842 editor.edit(indent_edits, cx);
4843 });
4844 }
4845
4846 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4847 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4848
4849 let buffer = self.buffer.read(cx);
4850 let snapshot = buffer.snapshot(cx);
4851
4852 let mut edits = Vec::new();
4853 let mut rows = Vec::new();
4854 let mut rows_inserted = 0;
4855
4856 for selection in self.selections.all_adjusted(cx) {
4857 let cursor = selection.head();
4858 let row = cursor.row;
4859
4860 let point = Point::new(row + 1, 0);
4861 let start_of_line = snapshot.clip_point(point, Bias::Left);
4862
4863 let newline = "\n".to_string();
4864 edits.push((start_of_line..start_of_line, newline));
4865
4866 rows_inserted += 1;
4867 rows.push(row + rows_inserted);
4868 }
4869
4870 self.transact(window, cx, |editor, window, cx| {
4871 editor.edit(edits, cx);
4872
4873 editor.change_selections(Default::default(), window, cx, |s| {
4874 let mut index = 0;
4875 s.move_cursors_with(|map, _, _| {
4876 let row = rows[index];
4877 index += 1;
4878
4879 let point = Point::new(row, 0);
4880 let boundary = map.next_line_boundary(point).1;
4881 let clipped = map.clip_point(boundary, Bias::Left);
4882
4883 (clipped, SelectionGoal::None)
4884 });
4885 });
4886
4887 let mut indent_edits = Vec::new();
4888 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4889 for row in rows {
4890 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4891 for (row, indent) in indents {
4892 if indent.len == 0 {
4893 continue;
4894 }
4895
4896 let text = match indent.kind {
4897 IndentKind::Space => " ".repeat(indent.len as usize),
4898 IndentKind::Tab => "\t".repeat(indent.len as usize),
4899 };
4900 let point = Point::new(row.0, 0);
4901 indent_edits.push((point..point, text));
4902 }
4903 }
4904 editor.edit(indent_edits, cx);
4905 });
4906 }
4907
4908 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4909 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4910 original_indent_columns: Vec::new(),
4911 });
4912 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4913 }
4914
4915 fn insert_with_autoindent_mode(
4916 &mut self,
4917 text: &str,
4918 autoindent_mode: Option<AutoindentMode>,
4919 window: &mut Window,
4920 cx: &mut Context<Self>,
4921 ) {
4922 if self.read_only(cx) {
4923 return;
4924 }
4925
4926 let text: Arc<str> = text.into();
4927 self.transact(window, cx, |this, window, cx| {
4928 let old_selections = this.selections.all_adjusted(cx);
4929 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4930 let anchors = {
4931 let snapshot = buffer.read(cx);
4932 old_selections
4933 .iter()
4934 .map(|s| {
4935 let anchor = snapshot.anchor_after(s.head());
4936 s.map(|_| anchor)
4937 })
4938 .collect::<Vec<_>>()
4939 };
4940 buffer.edit(
4941 old_selections
4942 .iter()
4943 .map(|s| (s.start..s.end, text.clone())),
4944 autoindent_mode,
4945 cx,
4946 );
4947 anchors
4948 });
4949
4950 this.change_selections(Default::default(), window, cx, |s| {
4951 s.select_anchors(selection_anchors);
4952 });
4953
4954 cx.notify();
4955 });
4956 }
4957
4958 fn trigger_completion_on_input(
4959 &mut self,
4960 text: &str,
4961 trigger_in_words: bool,
4962 window: &mut Window,
4963 cx: &mut Context<Self>,
4964 ) {
4965 let completions_source = self
4966 .context_menu
4967 .borrow()
4968 .as_ref()
4969 .and_then(|menu| match menu {
4970 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4971 CodeContextMenu::CodeActions(_) => None,
4972 });
4973
4974 match completions_source {
4975 Some(CompletionsMenuSource::Words { .. }) => {
4976 self.open_or_update_completions_menu(
4977 Some(CompletionsMenuSource::Words {
4978 ignore_threshold: false,
4979 }),
4980 None,
4981 window,
4982 cx,
4983 );
4984 }
4985 Some(CompletionsMenuSource::Normal)
4986 | Some(CompletionsMenuSource::SnippetChoices)
4987 | None
4988 if self.is_completion_trigger(
4989 text,
4990 trigger_in_words,
4991 completions_source.is_some(),
4992 cx,
4993 ) =>
4994 {
4995 self.show_completions(
4996 &ShowCompletions {
4997 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4998 },
4999 window,
5000 cx,
5001 )
5002 }
5003 _ => {
5004 self.hide_context_menu(window, cx);
5005 }
5006 }
5007 }
5008
5009 fn is_completion_trigger(
5010 &self,
5011 text: &str,
5012 trigger_in_words: bool,
5013 menu_is_open: bool,
5014 cx: &mut Context<Self>,
5015 ) -> bool {
5016 let position = self.selections.newest_anchor().head();
5017 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
5018 return false;
5019 };
5020
5021 if let Some(completion_provider) = &self.completion_provider {
5022 completion_provider.is_completion_trigger(
5023 &buffer,
5024 position.text_anchor,
5025 text,
5026 trigger_in_words,
5027 menu_is_open,
5028 cx,
5029 )
5030 } else {
5031 false
5032 }
5033 }
5034
5035 /// If any empty selections is touching the start of its innermost containing autoclose
5036 /// region, expand it to select the brackets.
5037 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5038 let selections = self.selections.all::<usize>(cx);
5039 let buffer = self.buffer.read(cx).read(cx);
5040 let new_selections = self
5041 .selections_with_autoclose_regions(selections, &buffer)
5042 .map(|(mut selection, region)| {
5043 if !selection.is_empty() {
5044 return selection;
5045 }
5046
5047 if let Some(region) = region {
5048 let mut range = region.range.to_offset(&buffer);
5049 if selection.start == range.start && range.start >= region.pair.start.len() {
5050 range.start -= region.pair.start.len();
5051 if buffer.contains_str_at(range.start, ®ion.pair.start)
5052 && buffer.contains_str_at(range.end, ®ion.pair.end)
5053 {
5054 range.end += region.pair.end.len();
5055 selection.start = range.start;
5056 selection.end = range.end;
5057
5058 return selection;
5059 }
5060 }
5061 }
5062
5063 let always_treat_brackets_as_autoclosed = buffer
5064 .language_settings_at(selection.start, cx)
5065 .always_treat_brackets_as_autoclosed;
5066
5067 if !always_treat_brackets_as_autoclosed {
5068 return selection;
5069 }
5070
5071 if let Some(scope) = buffer.language_scope_at(selection.start) {
5072 for (pair, enabled) in scope.brackets() {
5073 if !enabled || !pair.close {
5074 continue;
5075 }
5076
5077 if buffer.contains_str_at(selection.start, &pair.end) {
5078 let pair_start_len = pair.start.len();
5079 if buffer.contains_str_at(
5080 selection.start.saturating_sub(pair_start_len),
5081 &pair.start,
5082 ) {
5083 selection.start -= pair_start_len;
5084 selection.end += pair.end.len();
5085
5086 return selection;
5087 }
5088 }
5089 }
5090 }
5091
5092 selection
5093 })
5094 .collect();
5095
5096 drop(buffer);
5097 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5098 selections.select(new_selections)
5099 });
5100 }
5101
5102 /// Iterate the given selections, and for each one, find the smallest surrounding
5103 /// autoclose region. This uses the ordering of the selections and the autoclose
5104 /// regions to avoid repeated comparisons.
5105 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5106 &'a self,
5107 selections: impl IntoIterator<Item = Selection<D>>,
5108 buffer: &'a MultiBufferSnapshot,
5109 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5110 let mut i = 0;
5111 let mut regions = self.autoclose_regions.as_slice();
5112 selections.into_iter().map(move |selection| {
5113 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5114
5115 let mut enclosing = None;
5116 while let Some(pair_state) = regions.get(i) {
5117 if pair_state.range.end.to_offset(buffer) < range.start {
5118 regions = ®ions[i + 1..];
5119 i = 0;
5120 } else if pair_state.range.start.to_offset(buffer) > range.end {
5121 break;
5122 } else {
5123 if pair_state.selection_id == selection.id {
5124 enclosing = Some(pair_state);
5125 }
5126 i += 1;
5127 }
5128 }
5129
5130 (selection, enclosing)
5131 })
5132 }
5133
5134 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5135 fn invalidate_autoclose_regions(
5136 &mut self,
5137 mut selections: &[Selection<Anchor>],
5138 buffer: &MultiBufferSnapshot,
5139 ) {
5140 self.autoclose_regions.retain(|state| {
5141 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5142 return false;
5143 }
5144
5145 let mut i = 0;
5146 while let Some(selection) = selections.get(i) {
5147 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5148 selections = &selections[1..];
5149 continue;
5150 }
5151 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5152 break;
5153 }
5154 if selection.id == state.selection_id {
5155 return true;
5156 } else {
5157 i += 1;
5158 }
5159 }
5160 false
5161 });
5162 }
5163
5164 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5165 let offset = position.to_offset(buffer);
5166 let (word_range, kind) =
5167 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5168 if offset > word_range.start && kind == Some(CharKind::Word) {
5169 Some(
5170 buffer
5171 .text_for_range(word_range.start..offset)
5172 .collect::<String>(),
5173 )
5174 } else {
5175 None
5176 }
5177 }
5178
5179 pub fn toggle_inline_values(
5180 &mut self,
5181 _: &ToggleInlineValues,
5182 _: &mut Window,
5183 cx: &mut Context<Self>,
5184 ) {
5185 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5186
5187 self.refresh_inline_values(cx);
5188 }
5189
5190 pub fn toggle_inlay_hints(
5191 &mut self,
5192 _: &ToggleInlayHints,
5193 _: &mut Window,
5194 cx: &mut Context<Self>,
5195 ) {
5196 self.refresh_inlay_hints(
5197 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5198 cx,
5199 );
5200 }
5201
5202 pub fn inlay_hints_enabled(&self) -> bool {
5203 self.inlay_hint_cache.enabled
5204 }
5205
5206 pub fn inline_values_enabled(&self) -> bool {
5207 self.inline_value_cache.enabled
5208 }
5209
5210 #[cfg(any(test, feature = "test-support"))]
5211 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5212 self.display_map
5213 .read(cx)
5214 .current_inlays()
5215 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5216 .cloned()
5217 .collect()
5218 }
5219
5220 #[cfg(any(test, feature = "test-support"))]
5221 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5222 self.display_map
5223 .read(cx)
5224 .current_inlays()
5225 .cloned()
5226 .collect()
5227 }
5228
5229 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5230 if self.semantics_provider.is_none() || !self.mode.is_full() {
5231 return;
5232 }
5233
5234 let reason_description = reason.description();
5235 let ignore_debounce = matches!(
5236 reason,
5237 InlayHintRefreshReason::SettingsChange(_)
5238 | InlayHintRefreshReason::Toggle(_)
5239 | InlayHintRefreshReason::ExcerptsRemoved(_)
5240 | InlayHintRefreshReason::ModifiersChanged(_)
5241 );
5242 let (invalidate_cache, required_languages) = match reason {
5243 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5244 match self.inlay_hint_cache.modifiers_override(enabled) {
5245 Some(enabled) => {
5246 if enabled {
5247 (InvalidationStrategy::RefreshRequested, None)
5248 } else {
5249 self.clear_inlay_hints(cx);
5250 return;
5251 }
5252 }
5253 None => return,
5254 }
5255 }
5256 InlayHintRefreshReason::Toggle(enabled) => {
5257 if self.inlay_hint_cache.toggle(enabled) {
5258 if enabled {
5259 (InvalidationStrategy::RefreshRequested, None)
5260 } else {
5261 self.clear_inlay_hints(cx);
5262 return;
5263 }
5264 } else {
5265 return;
5266 }
5267 }
5268 InlayHintRefreshReason::SettingsChange(new_settings) => {
5269 match self.inlay_hint_cache.update_settings(
5270 &self.buffer,
5271 new_settings,
5272 self.visible_inlay_hints(cx).cloned().collect::<Vec<_>>(),
5273 cx,
5274 ) {
5275 ControlFlow::Break(Some(InlaySplice {
5276 to_remove,
5277 to_insert,
5278 })) => {
5279 self.splice_inlays(&to_remove, to_insert, cx);
5280 return;
5281 }
5282 ControlFlow::Break(None) => return,
5283 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5284 }
5285 }
5286 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5287 if let Some(InlaySplice {
5288 to_remove,
5289 to_insert,
5290 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5291 {
5292 self.splice_inlays(&to_remove, to_insert, cx);
5293 }
5294 self.display_map.update(cx, |display_map, _| {
5295 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5296 });
5297 return;
5298 }
5299 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5300 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5301 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5302 }
5303 InlayHintRefreshReason::RefreshRequested => {
5304 (InvalidationStrategy::RefreshRequested, None)
5305 }
5306 };
5307
5308 if let Some(InlaySplice {
5309 to_remove,
5310 to_insert,
5311 }) = self.inlay_hint_cache.spawn_hint_refresh(
5312 reason_description,
5313 self.visible_excerpts(required_languages.as_ref(), cx),
5314 invalidate_cache,
5315 ignore_debounce,
5316 cx,
5317 ) {
5318 self.splice_inlays(&to_remove, to_insert, cx);
5319 }
5320 }
5321
5322 pub fn clear_inlay_hints(&self, cx: &mut Context<Editor>) {
5323 self.splice_inlays(
5324 &self
5325 .visible_inlay_hints(cx)
5326 .map(|inlay| inlay.id)
5327 .collect::<Vec<_>>(),
5328 Vec::new(),
5329 cx,
5330 );
5331 }
5332
5333 fn visible_inlay_hints<'a>(
5334 &'a self,
5335 cx: &'a Context<Editor>,
5336 ) -> impl Iterator<Item = &'a Inlay> {
5337 self.display_map
5338 .read(cx)
5339 .current_inlays()
5340 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5341 }
5342
5343 pub fn visible_excerpts(
5344 &self,
5345 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5346 cx: &mut Context<Editor>,
5347 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5348 let Some(project) = self.project() else {
5349 return HashMap::default();
5350 };
5351 let project = project.read(cx);
5352 let multi_buffer = self.buffer().read(cx);
5353 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5354 let multi_buffer_visible_start = self
5355 .scroll_manager
5356 .anchor()
5357 .anchor
5358 .to_point(&multi_buffer_snapshot);
5359 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5360 multi_buffer_visible_start
5361 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5362 Bias::Left,
5363 );
5364 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5365 multi_buffer_snapshot
5366 .range_to_buffer_ranges(multi_buffer_visible_range)
5367 .into_iter()
5368 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5369 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5370 let buffer_file = project::File::from_dyn(buffer.file())?;
5371 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5372 let worktree_entry = buffer_worktree
5373 .read(cx)
5374 .entry_for_id(buffer_file.project_entry_id()?)?;
5375 if worktree_entry.is_ignored {
5376 return None;
5377 }
5378
5379 let language = buffer.language()?;
5380 if let Some(restrict_to_languages) = restrict_to_languages
5381 && !restrict_to_languages.contains(language)
5382 {
5383 return None;
5384 }
5385 Some((
5386 excerpt_id,
5387 (
5388 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5389 buffer.version().clone(),
5390 excerpt_visible_range,
5391 ),
5392 ))
5393 })
5394 .collect()
5395 }
5396
5397 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5398 TextLayoutDetails {
5399 text_system: window.text_system().clone(),
5400 editor_style: self.style.clone().unwrap(),
5401 rem_size: window.rem_size(),
5402 scroll_anchor: self.scroll_manager.anchor(),
5403 visible_rows: self.visible_line_count(),
5404 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5405 }
5406 }
5407
5408 pub fn splice_inlays(
5409 &self,
5410 to_remove: &[InlayId],
5411 to_insert: Vec<Inlay>,
5412 cx: &mut Context<Self>,
5413 ) {
5414 self.display_map.update(cx, |display_map, cx| {
5415 display_map.splice_inlays(to_remove, to_insert, cx)
5416 });
5417 cx.notify();
5418 }
5419
5420 fn trigger_on_type_formatting(
5421 &self,
5422 input: String,
5423 window: &mut Window,
5424 cx: &mut Context<Self>,
5425 ) -> Option<Task<Result<()>>> {
5426 if input.len() != 1 {
5427 return None;
5428 }
5429
5430 let project = self.project()?;
5431 let position = self.selections.newest_anchor().head();
5432 let (buffer, buffer_position) = self
5433 .buffer
5434 .read(cx)
5435 .text_anchor_for_position(position, cx)?;
5436
5437 let settings = language_settings::language_settings(
5438 buffer
5439 .read(cx)
5440 .language_at(buffer_position)
5441 .map(|l| l.name()),
5442 buffer.read(cx).file(),
5443 cx,
5444 );
5445 if !settings.use_on_type_format {
5446 return None;
5447 }
5448
5449 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5450 // hence we do LSP request & edit on host side only — add formats to host's history.
5451 let push_to_lsp_host_history = true;
5452 // If this is not the host, append its history with new edits.
5453 let push_to_client_history = project.read(cx).is_via_collab();
5454
5455 let on_type_formatting = project.update(cx, |project, cx| {
5456 project.on_type_format(
5457 buffer.clone(),
5458 buffer_position,
5459 input,
5460 push_to_lsp_host_history,
5461 cx,
5462 )
5463 });
5464 Some(cx.spawn_in(window, async move |editor, cx| {
5465 if let Some(transaction) = on_type_formatting.await? {
5466 if push_to_client_history {
5467 buffer
5468 .update(cx, |buffer, _| {
5469 buffer.push_transaction(transaction, Instant::now());
5470 buffer.finalize_last_transaction();
5471 })
5472 .ok();
5473 }
5474 editor.update(cx, |editor, cx| {
5475 editor.refresh_document_highlights(cx);
5476 })?;
5477 }
5478 Ok(())
5479 }))
5480 }
5481
5482 pub fn show_word_completions(
5483 &mut self,
5484 _: &ShowWordCompletions,
5485 window: &mut Window,
5486 cx: &mut Context<Self>,
5487 ) {
5488 self.open_or_update_completions_menu(
5489 Some(CompletionsMenuSource::Words {
5490 ignore_threshold: true,
5491 }),
5492 None,
5493 window,
5494 cx,
5495 );
5496 }
5497
5498 pub fn show_completions(
5499 &mut self,
5500 options: &ShowCompletions,
5501 window: &mut Window,
5502 cx: &mut Context<Self>,
5503 ) {
5504 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5505 }
5506
5507 fn open_or_update_completions_menu(
5508 &mut self,
5509 requested_source: Option<CompletionsMenuSource>,
5510 trigger: Option<&str>,
5511 window: &mut Window,
5512 cx: &mut Context<Self>,
5513 ) {
5514 if self.pending_rename.is_some() {
5515 return;
5516 }
5517
5518 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5519
5520 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5521 // inserted and selected. To handle that case, the start of the selection is used so that
5522 // the menu starts with all choices.
5523 let position = self
5524 .selections
5525 .newest_anchor()
5526 .start
5527 .bias_right(&multibuffer_snapshot);
5528 if position.diff_base_anchor.is_some() {
5529 return;
5530 }
5531 let buffer_position = multibuffer_snapshot.anchor_before(position);
5532 let Some(buffer) = buffer_position
5533 .buffer_id
5534 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5535 else {
5536 return;
5537 };
5538 let buffer_snapshot = buffer.read(cx).snapshot();
5539
5540 let query: Option<Arc<String>> =
5541 Self::completion_query(&multibuffer_snapshot, buffer_position)
5542 .map(|query| query.into());
5543
5544 drop(multibuffer_snapshot);
5545
5546 // Hide the current completions menu when query is empty. Without this, cached
5547 // completions from before the trigger char may be reused (#32774).
5548 if query.is_none() {
5549 let menu_is_open = matches!(
5550 self.context_menu.borrow().as_ref(),
5551 Some(CodeContextMenu::Completions(_))
5552 );
5553 if menu_is_open {
5554 self.hide_context_menu(window, cx);
5555 }
5556 }
5557
5558 let mut ignore_word_threshold = false;
5559 let provider = match requested_source {
5560 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5561 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5562 ignore_word_threshold = ignore_threshold;
5563 None
5564 }
5565 Some(CompletionsMenuSource::SnippetChoices) => {
5566 log::error!("bug: SnippetChoices requested_source is not handled");
5567 None
5568 }
5569 };
5570
5571 let sort_completions = provider
5572 .as_ref()
5573 .is_some_and(|provider| provider.sort_completions());
5574
5575 let filter_completions = provider
5576 .as_ref()
5577 .is_none_or(|provider| provider.filter_completions());
5578
5579 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5580 if filter_completions {
5581 menu.filter(query.clone(), provider.clone(), window, cx);
5582 }
5583 // When `is_incomplete` is false, no need to re-query completions when the current query
5584 // is a suffix of the initial query.
5585 if !menu.is_incomplete {
5586 // If the new query is a suffix of the old query (typing more characters) and
5587 // the previous result was complete, the existing completions can be filtered.
5588 //
5589 // Note that this is always true for snippet completions.
5590 let query_matches = match (&menu.initial_query, &query) {
5591 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5592 (None, _) => true,
5593 _ => false,
5594 };
5595 if query_matches {
5596 let position_matches = if menu.initial_position == position {
5597 true
5598 } else {
5599 let snapshot = self.buffer.read(cx).read(cx);
5600 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5601 };
5602 if position_matches {
5603 return;
5604 }
5605 }
5606 }
5607 };
5608
5609 let trigger_kind = match trigger {
5610 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5611 CompletionTriggerKind::TRIGGER_CHARACTER
5612 }
5613 _ => CompletionTriggerKind::INVOKED,
5614 };
5615 let completion_context = CompletionContext {
5616 trigger_character: trigger.and_then(|trigger| {
5617 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5618 Some(String::from(trigger))
5619 } else {
5620 None
5621 }
5622 }),
5623 trigger_kind,
5624 };
5625
5626 let Anchor {
5627 excerpt_id: buffer_excerpt_id,
5628 text_anchor: buffer_position,
5629 ..
5630 } = buffer_position;
5631
5632 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5633 buffer_snapshot.surrounding_word(buffer_position, None)
5634 {
5635 let word_to_exclude = buffer_snapshot
5636 .text_for_range(word_range.clone())
5637 .collect::<String>();
5638 (
5639 buffer_snapshot.anchor_before(word_range.start)
5640 ..buffer_snapshot.anchor_after(buffer_position),
5641 Some(word_to_exclude),
5642 )
5643 } else {
5644 (buffer_position..buffer_position, None)
5645 };
5646
5647 let language = buffer_snapshot
5648 .language_at(buffer_position)
5649 .map(|language| language.name());
5650
5651 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5652 .completions
5653 .clone();
5654
5655 let show_completion_documentation = buffer_snapshot
5656 .settings_at(buffer_position, cx)
5657 .show_completion_documentation;
5658
5659 // The document can be large, so stay in reasonable bounds when searching for words,
5660 // otherwise completion pop-up might be slow to appear.
5661 const WORD_LOOKUP_ROWS: u32 = 5_000;
5662 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5663 let min_word_search = buffer_snapshot.clip_point(
5664 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5665 Bias::Left,
5666 );
5667 let max_word_search = buffer_snapshot.clip_point(
5668 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5669 Bias::Right,
5670 );
5671 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5672 ..buffer_snapshot.point_to_offset(max_word_search);
5673
5674 let skip_digits = query
5675 .as_ref()
5676 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5677
5678 let omit_word_completions = !self.word_completions_enabled
5679 || (!ignore_word_threshold
5680 && match &query {
5681 Some(query) => query.chars().count() < completion_settings.words_min_length,
5682 None => completion_settings.words_min_length != 0,
5683 });
5684
5685 let (mut words, provider_responses) = match &provider {
5686 Some(provider) => {
5687 let provider_responses = provider.completions(
5688 buffer_excerpt_id,
5689 &buffer,
5690 buffer_position,
5691 completion_context,
5692 window,
5693 cx,
5694 );
5695
5696 let words = match (omit_word_completions, completion_settings.words) {
5697 (true, _) | (_, WordsCompletionMode::Disabled) => {
5698 Task::ready(BTreeMap::default())
5699 }
5700 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5701 .background_spawn(async move {
5702 buffer_snapshot.words_in_range(WordsQuery {
5703 fuzzy_contents: None,
5704 range: word_search_range,
5705 skip_digits,
5706 })
5707 }),
5708 };
5709
5710 (words, provider_responses)
5711 }
5712 None => {
5713 let words = if omit_word_completions {
5714 Task::ready(BTreeMap::default())
5715 } else {
5716 cx.background_spawn(async move {
5717 buffer_snapshot.words_in_range(WordsQuery {
5718 fuzzy_contents: None,
5719 range: word_search_range,
5720 skip_digits,
5721 })
5722 })
5723 };
5724 (words, Task::ready(Ok(Vec::new())))
5725 }
5726 };
5727
5728 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5729
5730 let id = post_inc(&mut self.next_completion_id);
5731 let task = cx.spawn_in(window, async move |editor, cx| {
5732 let Ok(()) = editor.update(cx, |this, _| {
5733 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5734 }) else {
5735 return;
5736 };
5737
5738 // TODO: Ideally completions from different sources would be selectively re-queried, so
5739 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5740 let mut completions = Vec::new();
5741 let mut is_incomplete = false;
5742 let mut display_options: Option<CompletionDisplayOptions> = None;
5743 if let Some(provider_responses) = provider_responses.await.log_err()
5744 && !provider_responses.is_empty()
5745 {
5746 for response in provider_responses {
5747 completions.extend(response.completions);
5748 is_incomplete = is_incomplete || response.is_incomplete;
5749 match display_options.as_mut() {
5750 None => {
5751 display_options = Some(response.display_options);
5752 }
5753 Some(options) => options.merge(&response.display_options),
5754 }
5755 }
5756 if completion_settings.words == WordsCompletionMode::Fallback {
5757 words = Task::ready(BTreeMap::default());
5758 }
5759 }
5760 let display_options = display_options.unwrap_or_default();
5761
5762 let mut words = words.await;
5763 if let Some(word_to_exclude) = &word_to_exclude {
5764 words.remove(word_to_exclude);
5765 }
5766 for lsp_completion in &completions {
5767 words.remove(&lsp_completion.new_text);
5768 }
5769 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5770 replace_range: word_replace_range.clone(),
5771 new_text: word.clone(),
5772 label: CodeLabel::plain(word, None),
5773 icon_path: None,
5774 documentation: None,
5775 source: CompletionSource::BufferWord {
5776 word_range,
5777 resolved: false,
5778 },
5779 insert_text_mode: Some(InsertTextMode::AS_IS),
5780 confirm: None,
5781 }));
5782
5783 let menu = if completions.is_empty() {
5784 None
5785 } else {
5786 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5787 let languages = editor
5788 .workspace
5789 .as_ref()
5790 .and_then(|(workspace, _)| workspace.upgrade())
5791 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5792 let menu = CompletionsMenu::new(
5793 id,
5794 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5795 sort_completions,
5796 show_completion_documentation,
5797 position,
5798 query.clone(),
5799 is_incomplete,
5800 buffer.clone(),
5801 completions.into(),
5802 display_options,
5803 snippet_sort_order,
5804 languages,
5805 language,
5806 cx,
5807 );
5808
5809 let query = if filter_completions { query } else { None };
5810 let matches_task = if let Some(query) = query {
5811 menu.do_async_filtering(query, cx)
5812 } else {
5813 Task::ready(menu.unfiltered_matches())
5814 };
5815 (menu, matches_task)
5816 }) else {
5817 return;
5818 };
5819
5820 let matches = matches_task.await;
5821
5822 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5823 // Newer menu already set, so exit.
5824 if let Some(CodeContextMenu::Completions(prev_menu)) =
5825 editor.context_menu.borrow().as_ref()
5826 && prev_menu.id > id
5827 {
5828 return;
5829 };
5830
5831 // Only valid to take prev_menu because it the new menu is immediately set
5832 // below, or the menu is hidden.
5833 if let Some(CodeContextMenu::Completions(prev_menu)) =
5834 editor.context_menu.borrow_mut().take()
5835 {
5836 let position_matches =
5837 if prev_menu.initial_position == menu.initial_position {
5838 true
5839 } else {
5840 let snapshot = editor.buffer.read(cx).read(cx);
5841 prev_menu.initial_position.to_offset(&snapshot)
5842 == menu.initial_position.to_offset(&snapshot)
5843 };
5844 if position_matches {
5845 // Preserve markdown cache before `set_filter_results` because it will
5846 // try to populate the documentation cache.
5847 menu.preserve_markdown_cache(prev_menu);
5848 }
5849 };
5850
5851 menu.set_filter_results(matches, provider, window, cx);
5852 }) else {
5853 return;
5854 };
5855
5856 menu.visible().then_some(menu)
5857 };
5858
5859 editor
5860 .update_in(cx, |editor, window, cx| {
5861 if editor.focus_handle.is_focused(window)
5862 && let Some(menu) = menu
5863 {
5864 *editor.context_menu.borrow_mut() =
5865 Some(CodeContextMenu::Completions(menu));
5866
5867 crate::hover_popover::hide_hover(editor, cx);
5868 if editor.show_edit_predictions_in_menu() {
5869 editor.update_visible_edit_prediction(window, cx);
5870 } else {
5871 editor.discard_edit_prediction(false, cx);
5872 }
5873
5874 cx.notify();
5875 return;
5876 }
5877
5878 if editor.completion_tasks.len() <= 1 {
5879 // If there are no more completion tasks and the last menu was empty, we should hide it.
5880 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5881 // If it was already hidden and we don't show edit predictions in the menu,
5882 // we should also show the edit prediction when available.
5883 if was_hidden && editor.show_edit_predictions_in_menu() {
5884 editor.update_visible_edit_prediction(window, cx);
5885 }
5886 }
5887 })
5888 .ok();
5889 });
5890
5891 self.completion_tasks.push((id, task));
5892 }
5893
5894 #[cfg(feature = "test-support")]
5895 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5896 let menu = self.context_menu.borrow();
5897 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5898 let completions = menu.completions.borrow();
5899 Some(completions.to_vec())
5900 } else {
5901 None
5902 }
5903 }
5904
5905 pub fn with_completions_menu_matching_id<R>(
5906 &self,
5907 id: CompletionId,
5908 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5909 ) -> R {
5910 let mut context_menu = self.context_menu.borrow_mut();
5911 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5912 return f(None);
5913 };
5914 if completions_menu.id != id {
5915 return f(None);
5916 }
5917 f(Some(completions_menu))
5918 }
5919
5920 pub fn confirm_completion(
5921 &mut self,
5922 action: &ConfirmCompletion,
5923 window: &mut Window,
5924 cx: &mut Context<Self>,
5925 ) -> Option<Task<Result<()>>> {
5926 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5927 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5928 }
5929
5930 pub fn confirm_completion_insert(
5931 &mut self,
5932 _: &ConfirmCompletionInsert,
5933 window: &mut Window,
5934 cx: &mut Context<Self>,
5935 ) -> Option<Task<Result<()>>> {
5936 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5937 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5938 }
5939
5940 pub fn confirm_completion_replace(
5941 &mut self,
5942 _: &ConfirmCompletionReplace,
5943 window: &mut Window,
5944 cx: &mut Context<Self>,
5945 ) -> Option<Task<Result<()>>> {
5946 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5947 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5948 }
5949
5950 pub fn compose_completion(
5951 &mut self,
5952 action: &ComposeCompletion,
5953 window: &mut Window,
5954 cx: &mut Context<Self>,
5955 ) -> Option<Task<Result<()>>> {
5956 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5957 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5958 }
5959
5960 fn do_completion(
5961 &mut self,
5962 item_ix: Option<usize>,
5963 intent: CompletionIntent,
5964 window: &mut Window,
5965 cx: &mut Context<Editor>,
5966 ) -> Option<Task<Result<()>>> {
5967 use language::ToOffset as _;
5968
5969 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5970 else {
5971 return None;
5972 };
5973
5974 let candidate_id = {
5975 let entries = completions_menu.entries.borrow();
5976 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5977 if self.show_edit_predictions_in_menu() {
5978 self.discard_edit_prediction(true, cx);
5979 }
5980 mat.candidate_id
5981 };
5982
5983 let completion = completions_menu
5984 .completions
5985 .borrow()
5986 .get(candidate_id)?
5987 .clone();
5988 cx.stop_propagation();
5989
5990 let buffer_handle = completions_menu.buffer.clone();
5991
5992 let CompletionEdit {
5993 new_text,
5994 snippet,
5995 replace_range,
5996 } = process_completion_for_edit(
5997 &completion,
5998 intent,
5999 &buffer_handle,
6000 &completions_menu.initial_position.text_anchor,
6001 cx,
6002 );
6003
6004 let buffer = buffer_handle.read(cx);
6005 let snapshot = self.buffer.read(cx).snapshot(cx);
6006 let newest_anchor = self.selections.newest_anchor();
6007 let replace_range_multibuffer = {
6008 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6009 let multibuffer_anchor = snapshot
6010 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
6011 .unwrap()
6012 ..snapshot
6013 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
6014 .unwrap();
6015 multibuffer_anchor.start.to_offset(&snapshot)
6016 ..multibuffer_anchor.end.to_offset(&snapshot)
6017 };
6018 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6019 return None;
6020 }
6021
6022 let old_text = buffer
6023 .text_for_range(replace_range.clone())
6024 .collect::<String>();
6025 let lookbehind = newest_anchor
6026 .start
6027 .text_anchor
6028 .to_offset(buffer)
6029 .saturating_sub(replace_range.start);
6030 let lookahead = replace_range
6031 .end
6032 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6033 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6034 let suffix = &old_text[lookbehind.min(old_text.len())..];
6035
6036 let selections = self.selections.all::<usize>(cx);
6037 let mut ranges = Vec::new();
6038 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6039
6040 for selection in &selections {
6041 let range = if selection.id == newest_anchor.id {
6042 replace_range_multibuffer.clone()
6043 } else {
6044 let mut range = selection.range();
6045
6046 // if prefix is present, don't duplicate it
6047 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
6048 range.start = range.start.saturating_sub(lookbehind);
6049
6050 // if suffix is also present, mimic the newest cursor and replace it
6051 if selection.id != newest_anchor.id
6052 && snapshot.contains_str_at(range.end, suffix)
6053 {
6054 range.end += lookahead;
6055 }
6056 }
6057 range
6058 };
6059
6060 ranges.push(range.clone());
6061
6062 if !self.linked_edit_ranges.is_empty() {
6063 let start_anchor = snapshot.anchor_before(range.start);
6064 let end_anchor = snapshot.anchor_after(range.end);
6065 if let Some(ranges) = self
6066 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6067 {
6068 for (buffer, edits) in ranges {
6069 linked_edits
6070 .entry(buffer.clone())
6071 .or_default()
6072 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6073 }
6074 }
6075 }
6076 }
6077
6078 let common_prefix_len = old_text
6079 .chars()
6080 .zip(new_text.chars())
6081 .take_while(|(a, b)| a == b)
6082 .map(|(a, _)| a.len_utf8())
6083 .sum::<usize>();
6084
6085 cx.emit(EditorEvent::InputHandled {
6086 utf16_range_to_replace: None,
6087 text: new_text[common_prefix_len..].into(),
6088 });
6089
6090 self.transact(window, cx, |editor, window, cx| {
6091 if let Some(mut snippet) = snippet {
6092 snippet.text = new_text.to_string();
6093 editor
6094 .insert_snippet(&ranges, snippet, window, cx)
6095 .log_err();
6096 } else {
6097 editor.buffer.update(cx, |multi_buffer, cx| {
6098 let auto_indent = match completion.insert_text_mode {
6099 Some(InsertTextMode::AS_IS) => None,
6100 _ => editor.autoindent_mode.clone(),
6101 };
6102 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6103 multi_buffer.edit(edits, auto_indent, cx);
6104 });
6105 }
6106 for (buffer, edits) in linked_edits {
6107 buffer.update(cx, |buffer, cx| {
6108 let snapshot = buffer.snapshot();
6109 let edits = edits
6110 .into_iter()
6111 .map(|(range, text)| {
6112 use text::ToPoint as TP;
6113 let end_point = TP::to_point(&range.end, &snapshot);
6114 let start_point = TP::to_point(&range.start, &snapshot);
6115 (start_point..end_point, text)
6116 })
6117 .sorted_by_key(|(range, _)| range.start);
6118 buffer.edit(edits, None, cx);
6119 })
6120 }
6121
6122 editor.refresh_edit_prediction(true, false, window, cx);
6123 });
6124 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6125
6126 let show_new_completions_on_confirm = completion
6127 .confirm
6128 .as_ref()
6129 .is_some_and(|confirm| confirm(intent, window, cx));
6130 if show_new_completions_on_confirm {
6131 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6132 }
6133
6134 let provider = self.completion_provider.as_ref()?;
6135 drop(completion);
6136 let apply_edits = provider.apply_additional_edits_for_completion(
6137 buffer_handle,
6138 completions_menu.completions.clone(),
6139 candidate_id,
6140 true,
6141 cx,
6142 );
6143
6144 let editor_settings = EditorSettings::get_global(cx);
6145 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6146 // After the code completion is finished, users often want to know what signatures are needed.
6147 // so we should automatically call signature_help
6148 self.show_signature_help(&ShowSignatureHelp, window, cx);
6149 }
6150
6151 Some(cx.foreground_executor().spawn(async move {
6152 apply_edits.await?;
6153 Ok(())
6154 }))
6155 }
6156
6157 pub fn toggle_code_actions(
6158 &mut self,
6159 action: &ToggleCodeActions,
6160 window: &mut Window,
6161 cx: &mut Context<Self>,
6162 ) {
6163 let quick_launch = action.quick_launch;
6164 let mut context_menu = self.context_menu.borrow_mut();
6165 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6166 if code_actions.deployed_from == action.deployed_from {
6167 // Toggle if we're selecting the same one
6168 *context_menu = None;
6169 cx.notify();
6170 return;
6171 } else {
6172 // Otherwise, clear it and start a new one
6173 *context_menu = None;
6174 cx.notify();
6175 }
6176 }
6177 drop(context_menu);
6178 let snapshot = self.snapshot(window, cx);
6179 let deployed_from = action.deployed_from.clone();
6180 let action = action.clone();
6181 self.completion_tasks.clear();
6182 self.discard_edit_prediction(false, cx);
6183
6184 let multibuffer_point = match &action.deployed_from {
6185 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6186 DisplayPoint::new(*row, 0).to_point(&snapshot)
6187 }
6188 _ => self.selections.newest::<Point>(cx).head(),
6189 };
6190 let Some((buffer, buffer_row)) = snapshot
6191 .buffer_snapshot()
6192 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6193 .and_then(|(buffer_snapshot, range)| {
6194 self.buffer()
6195 .read(cx)
6196 .buffer(buffer_snapshot.remote_id())
6197 .map(|buffer| (buffer, range.start.row))
6198 })
6199 else {
6200 return;
6201 };
6202 let buffer_id = buffer.read(cx).remote_id();
6203 let tasks = self
6204 .tasks
6205 .get(&(buffer_id, buffer_row))
6206 .map(|t| Arc::new(t.to_owned()));
6207
6208 if !self.focus_handle.is_focused(window) {
6209 return;
6210 }
6211 let project = self.project.clone();
6212
6213 let code_actions_task = match deployed_from {
6214 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6215 _ => self.code_actions(buffer_row, window, cx),
6216 };
6217
6218 let runnable_task = match deployed_from {
6219 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6220 _ => {
6221 let mut task_context_task = Task::ready(None);
6222 if let Some(tasks) = &tasks
6223 && let Some(project) = project
6224 {
6225 task_context_task =
6226 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6227 }
6228
6229 cx.spawn_in(window, {
6230 let buffer = buffer.clone();
6231 async move |editor, cx| {
6232 let task_context = task_context_task.await;
6233
6234 let resolved_tasks =
6235 tasks
6236 .zip(task_context.clone())
6237 .map(|(tasks, task_context)| ResolvedTasks {
6238 templates: tasks.resolve(&task_context).collect(),
6239 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6240 multibuffer_point.row,
6241 tasks.column,
6242 )),
6243 });
6244 let debug_scenarios = editor
6245 .update(cx, |editor, cx| {
6246 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6247 })?
6248 .await;
6249 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6250 }
6251 })
6252 }
6253 };
6254
6255 cx.spawn_in(window, async move |editor, cx| {
6256 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6257 let code_actions = code_actions_task.await;
6258 let spawn_straight_away = quick_launch
6259 && resolved_tasks
6260 .as_ref()
6261 .is_some_and(|tasks| tasks.templates.len() == 1)
6262 && code_actions
6263 .as_ref()
6264 .is_none_or(|actions| actions.is_empty())
6265 && debug_scenarios.is_empty();
6266
6267 editor.update_in(cx, |editor, window, cx| {
6268 crate::hover_popover::hide_hover(editor, cx);
6269 let actions = CodeActionContents::new(
6270 resolved_tasks,
6271 code_actions,
6272 debug_scenarios,
6273 task_context.unwrap_or_default(),
6274 );
6275
6276 // Don't show the menu if there are no actions available
6277 if actions.is_empty() {
6278 cx.notify();
6279 return Task::ready(Ok(()));
6280 }
6281
6282 *editor.context_menu.borrow_mut() =
6283 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6284 buffer,
6285 actions,
6286 selected_item: Default::default(),
6287 scroll_handle: UniformListScrollHandle::default(),
6288 deployed_from,
6289 }));
6290 cx.notify();
6291 if spawn_straight_away
6292 && let Some(task) = editor.confirm_code_action(
6293 &ConfirmCodeAction { item_ix: Some(0) },
6294 window,
6295 cx,
6296 )
6297 {
6298 return task;
6299 }
6300
6301 Task::ready(Ok(()))
6302 })
6303 })
6304 .detach_and_log_err(cx);
6305 }
6306
6307 fn debug_scenarios(
6308 &mut self,
6309 resolved_tasks: &Option<ResolvedTasks>,
6310 buffer: &Entity<Buffer>,
6311 cx: &mut App,
6312 ) -> Task<Vec<task::DebugScenario>> {
6313 maybe!({
6314 let project = self.project()?;
6315 let dap_store = project.read(cx).dap_store();
6316 let mut scenarios = vec![];
6317 let resolved_tasks = resolved_tasks.as_ref()?;
6318 let buffer = buffer.read(cx);
6319 let language = buffer.language()?;
6320 let file = buffer.file();
6321 let debug_adapter = language_settings(language.name().into(), file, cx)
6322 .debuggers
6323 .first()
6324 .map(SharedString::from)
6325 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6326
6327 dap_store.update(cx, |dap_store, cx| {
6328 for (_, task) in &resolved_tasks.templates {
6329 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6330 task.original_task().clone(),
6331 debug_adapter.clone().into(),
6332 task.display_label().to_owned().into(),
6333 cx,
6334 );
6335 scenarios.push(maybe_scenario);
6336 }
6337 });
6338 Some(cx.background_spawn(async move {
6339 futures::future::join_all(scenarios)
6340 .await
6341 .into_iter()
6342 .flatten()
6343 .collect::<Vec<_>>()
6344 }))
6345 })
6346 .unwrap_or_else(|| Task::ready(vec![]))
6347 }
6348
6349 fn code_actions(
6350 &mut self,
6351 buffer_row: u32,
6352 window: &mut Window,
6353 cx: &mut Context<Self>,
6354 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6355 let mut task = self.code_actions_task.take();
6356 cx.spawn_in(window, async move |editor, cx| {
6357 while let Some(prev_task) = task {
6358 prev_task.await.log_err();
6359 task = editor
6360 .update(cx, |this, _| this.code_actions_task.take())
6361 .ok()?;
6362 }
6363
6364 editor
6365 .update(cx, |editor, cx| {
6366 editor
6367 .available_code_actions
6368 .clone()
6369 .and_then(|(location, code_actions)| {
6370 let snapshot = location.buffer.read(cx).snapshot();
6371 let point_range = location.range.to_point(&snapshot);
6372 let point_range = point_range.start.row..=point_range.end.row;
6373 if point_range.contains(&buffer_row) {
6374 Some(code_actions)
6375 } else {
6376 None
6377 }
6378 })
6379 })
6380 .ok()
6381 .flatten()
6382 })
6383 }
6384
6385 pub fn confirm_code_action(
6386 &mut self,
6387 action: &ConfirmCodeAction,
6388 window: &mut Window,
6389 cx: &mut Context<Self>,
6390 ) -> Option<Task<Result<()>>> {
6391 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6392
6393 let actions_menu =
6394 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6395 menu
6396 } else {
6397 return None;
6398 };
6399
6400 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6401 let action = actions_menu.actions.get(action_ix)?;
6402 let title = action.label();
6403 let buffer = actions_menu.buffer;
6404 let workspace = self.workspace()?;
6405
6406 match action {
6407 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6408 workspace.update(cx, |workspace, cx| {
6409 workspace.schedule_resolved_task(
6410 task_source_kind,
6411 resolved_task,
6412 false,
6413 window,
6414 cx,
6415 );
6416
6417 Some(Task::ready(Ok(())))
6418 })
6419 }
6420 CodeActionsItem::CodeAction {
6421 excerpt_id,
6422 action,
6423 provider,
6424 } => {
6425 let apply_code_action =
6426 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6427 let workspace = workspace.downgrade();
6428 Some(cx.spawn_in(window, async move |editor, cx| {
6429 let project_transaction = apply_code_action.await?;
6430 Self::open_project_transaction(
6431 &editor,
6432 workspace,
6433 project_transaction,
6434 title,
6435 cx,
6436 )
6437 .await
6438 }))
6439 }
6440 CodeActionsItem::DebugScenario(scenario) => {
6441 let context = actions_menu.actions.context;
6442
6443 workspace.update(cx, |workspace, cx| {
6444 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6445 workspace.start_debug_session(
6446 scenario,
6447 context,
6448 Some(buffer),
6449 None,
6450 window,
6451 cx,
6452 );
6453 });
6454 Some(Task::ready(Ok(())))
6455 }
6456 }
6457 }
6458
6459 pub async fn open_project_transaction(
6460 editor: &WeakEntity<Editor>,
6461 workspace: WeakEntity<Workspace>,
6462 transaction: ProjectTransaction,
6463 title: String,
6464 cx: &mut AsyncWindowContext,
6465 ) -> Result<()> {
6466 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6467 cx.update(|_, cx| {
6468 entries.sort_unstable_by_key(|(buffer, _)| {
6469 buffer.read(cx).file().map(|f| f.path().clone())
6470 });
6471 })?;
6472 if entries.is_empty() {
6473 return Ok(());
6474 }
6475
6476 // If the project transaction's edits are all contained within this editor, then
6477 // avoid opening a new editor to display them.
6478
6479 if let [(buffer, transaction)] = &*entries {
6480 let excerpt = editor.update(cx, |editor, cx| {
6481 editor
6482 .buffer()
6483 .read(cx)
6484 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6485 })?;
6486 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6487 && excerpted_buffer == *buffer
6488 {
6489 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6490 let excerpt_range = excerpt_range.to_offset(buffer);
6491 buffer
6492 .edited_ranges_for_transaction::<usize>(transaction)
6493 .all(|range| {
6494 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6495 })
6496 })?;
6497
6498 if all_edits_within_excerpt {
6499 return Ok(());
6500 }
6501 }
6502 }
6503
6504 let mut ranges_to_highlight = Vec::new();
6505 let excerpt_buffer = cx.new(|cx| {
6506 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6507 for (buffer_handle, transaction) in &entries {
6508 let edited_ranges = buffer_handle
6509 .read(cx)
6510 .edited_ranges_for_transaction::<Point>(transaction)
6511 .collect::<Vec<_>>();
6512 let (ranges, _) = multibuffer.set_excerpts_for_path(
6513 PathKey::for_buffer(buffer_handle, cx),
6514 buffer_handle.clone(),
6515 edited_ranges,
6516 multibuffer_context_lines(cx),
6517 cx,
6518 );
6519
6520 ranges_to_highlight.extend(ranges);
6521 }
6522 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6523 multibuffer
6524 })?;
6525
6526 workspace.update_in(cx, |workspace, window, cx| {
6527 let project = workspace.project().clone();
6528 let editor =
6529 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6530 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6531 editor.update(cx, |editor, cx| {
6532 editor.highlight_background::<Self>(
6533 &ranges_to_highlight,
6534 |theme| theme.colors().editor_highlighted_line_background,
6535 cx,
6536 );
6537 });
6538 })?;
6539
6540 Ok(())
6541 }
6542
6543 pub fn clear_code_action_providers(&mut self) {
6544 self.code_action_providers.clear();
6545 self.available_code_actions.take();
6546 }
6547
6548 pub fn add_code_action_provider(
6549 &mut self,
6550 provider: Rc<dyn CodeActionProvider>,
6551 window: &mut Window,
6552 cx: &mut Context<Self>,
6553 ) {
6554 if self
6555 .code_action_providers
6556 .iter()
6557 .any(|existing_provider| existing_provider.id() == provider.id())
6558 {
6559 return;
6560 }
6561
6562 self.code_action_providers.push(provider);
6563 self.refresh_code_actions(window, cx);
6564 }
6565
6566 pub fn remove_code_action_provider(
6567 &mut self,
6568 id: Arc<str>,
6569 window: &mut Window,
6570 cx: &mut Context<Self>,
6571 ) {
6572 self.code_action_providers
6573 .retain(|provider| provider.id() != id);
6574 self.refresh_code_actions(window, cx);
6575 }
6576
6577 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6578 !self.code_action_providers.is_empty()
6579 && EditorSettings::get_global(cx).toolbar.code_actions
6580 }
6581
6582 pub fn has_available_code_actions(&self) -> bool {
6583 self.available_code_actions
6584 .as_ref()
6585 .is_some_and(|(_, actions)| !actions.is_empty())
6586 }
6587
6588 fn render_inline_code_actions(
6589 &self,
6590 icon_size: ui::IconSize,
6591 display_row: DisplayRow,
6592 is_active: bool,
6593 cx: &mut Context<Self>,
6594 ) -> AnyElement {
6595 let show_tooltip = !self.context_menu_visible();
6596 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6597 .icon_size(icon_size)
6598 .shape(ui::IconButtonShape::Square)
6599 .icon_color(ui::Color::Hidden)
6600 .toggle_state(is_active)
6601 .when(show_tooltip, |this| {
6602 this.tooltip({
6603 let focus_handle = self.focus_handle.clone();
6604 move |window, cx| {
6605 Tooltip::for_action_in(
6606 "Toggle Code Actions",
6607 &ToggleCodeActions {
6608 deployed_from: None,
6609 quick_launch: false,
6610 },
6611 &focus_handle,
6612 window,
6613 cx,
6614 )
6615 }
6616 })
6617 })
6618 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6619 window.focus(&editor.focus_handle(cx));
6620 editor.toggle_code_actions(
6621 &crate::actions::ToggleCodeActions {
6622 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6623 display_row,
6624 )),
6625 quick_launch: false,
6626 },
6627 window,
6628 cx,
6629 );
6630 }))
6631 .into_any_element()
6632 }
6633
6634 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6635 &self.context_menu
6636 }
6637
6638 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6639 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6640 cx.background_executor()
6641 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6642 .await;
6643
6644 let (start_buffer, start, _, end, newest_selection) = this
6645 .update(cx, |this, cx| {
6646 let newest_selection = this.selections.newest_anchor().clone();
6647 if newest_selection.head().diff_base_anchor.is_some() {
6648 return None;
6649 }
6650 let newest_selection_adjusted = this.selections.newest_adjusted(cx);
6651 let buffer = this.buffer.read(cx);
6652
6653 let (start_buffer, start) =
6654 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6655 let (end_buffer, end) =
6656 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6657
6658 Some((start_buffer, start, end_buffer, end, newest_selection))
6659 })?
6660 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6661 .context(
6662 "Expected selection to lie in a single buffer when refreshing code actions",
6663 )?;
6664 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6665 let providers = this.code_action_providers.clone();
6666 let tasks = this
6667 .code_action_providers
6668 .iter()
6669 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6670 .collect::<Vec<_>>();
6671 (providers, tasks)
6672 })?;
6673
6674 let mut actions = Vec::new();
6675 for (provider, provider_actions) in
6676 providers.into_iter().zip(future::join_all(tasks).await)
6677 {
6678 if let Some(provider_actions) = provider_actions.log_err() {
6679 actions.extend(provider_actions.into_iter().map(|action| {
6680 AvailableCodeAction {
6681 excerpt_id: newest_selection.start.excerpt_id,
6682 action,
6683 provider: provider.clone(),
6684 }
6685 }));
6686 }
6687 }
6688
6689 this.update(cx, |this, cx| {
6690 this.available_code_actions = if actions.is_empty() {
6691 None
6692 } else {
6693 Some((
6694 Location {
6695 buffer: start_buffer,
6696 range: start..end,
6697 },
6698 actions.into(),
6699 ))
6700 };
6701 cx.notify();
6702 })
6703 }));
6704 }
6705
6706 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6707 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6708 self.show_git_blame_inline = false;
6709
6710 self.show_git_blame_inline_delay_task =
6711 Some(cx.spawn_in(window, async move |this, cx| {
6712 cx.background_executor().timer(delay).await;
6713
6714 this.update(cx, |this, cx| {
6715 this.show_git_blame_inline = true;
6716 cx.notify();
6717 })
6718 .log_err();
6719 }));
6720 }
6721 }
6722
6723 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6724 let snapshot = self.snapshot(window, cx);
6725 let cursor = self.selections.newest::<Point>(cx).head();
6726 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6727 else {
6728 return;
6729 };
6730
6731 let Some(blame) = self.blame.as_ref() else {
6732 return;
6733 };
6734
6735 let row_info = RowInfo {
6736 buffer_id: Some(buffer.remote_id()),
6737 buffer_row: Some(point.row),
6738 ..Default::default()
6739 };
6740 let Some((buffer, blame_entry)) = blame
6741 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6742 .flatten()
6743 else {
6744 return;
6745 };
6746
6747 let anchor = self.selections.newest_anchor().head();
6748 let position = self.to_pixel_point(anchor, &snapshot, window);
6749 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6750 self.show_blame_popover(
6751 buffer,
6752 &blame_entry,
6753 position + last_bounds.origin,
6754 true,
6755 cx,
6756 );
6757 };
6758 }
6759
6760 fn show_blame_popover(
6761 &mut self,
6762 buffer: BufferId,
6763 blame_entry: &BlameEntry,
6764 position: gpui::Point<Pixels>,
6765 ignore_timeout: bool,
6766 cx: &mut Context<Self>,
6767 ) {
6768 if let Some(state) = &mut self.inline_blame_popover {
6769 state.hide_task.take();
6770 } else {
6771 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6772 let blame_entry = blame_entry.clone();
6773 let show_task = cx.spawn(async move |editor, cx| {
6774 if !ignore_timeout {
6775 cx.background_executor()
6776 .timer(std::time::Duration::from_millis(blame_popover_delay))
6777 .await;
6778 }
6779 editor
6780 .update(cx, |editor, cx| {
6781 editor.inline_blame_popover_show_task.take();
6782 let Some(blame) = editor.blame.as_ref() else {
6783 return;
6784 };
6785 let blame = blame.read(cx);
6786 let details = blame.details_for_entry(buffer, &blame_entry);
6787 let markdown = cx.new(|cx| {
6788 Markdown::new(
6789 details
6790 .as_ref()
6791 .map(|message| message.message.clone())
6792 .unwrap_or_default(),
6793 None,
6794 None,
6795 cx,
6796 )
6797 });
6798 editor.inline_blame_popover = Some(InlineBlamePopover {
6799 position,
6800 hide_task: None,
6801 popover_bounds: None,
6802 popover_state: InlineBlamePopoverState {
6803 scroll_handle: ScrollHandle::new(),
6804 commit_message: details,
6805 markdown,
6806 },
6807 keyboard_grace: ignore_timeout,
6808 });
6809 cx.notify();
6810 })
6811 .ok();
6812 });
6813 self.inline_blame_popover_show_task = Some(show_task);
6814 }
6815 }
6816
6817 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6818 self.inline_blame_popover_show_task.take();
6819 if let Some(state) = &mut self.inline_blame_popover {
6820 let hide_task = cx.spawn(async move |editor, cx| {
6821 cx.background_executor()
6822 .timer(std::time::Duration::from_millis(100))
6823 .await;
6824 editor
6825 .update(cx, |editor, cx| {
6826 editor.inline_blame_popover.take();
6827 cx.notify();
6828 })
6829 .ok();
6830 });
6831 state.hide_task = Some(hide_task);
6832 }
6833 }
6834
6835 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6836 if self.pending_rename.is_some() {
6837 return None;
6838 }
6839
6840 let provider = self.semantics_provider.clone()?;
6841 let buffer = self.buffer.read(cx);
6842 let newest_selection = self.selections.newest_anchor().clone();
6843 let cursor_position = newest_selection.head();
6844 let (cursor_buffer, cursor_buffer_position) =
6845 buffer.text_anchor_for_position(cursor_position, cx)?;
6846 let (tail_buffer, tail_buffer_position) =
6847 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6848 if cursor_buffer != tail_buffer {
6849 return None;
6850 }
6851
6852 let snapshot = cursor_buffer.read(cx).snapshot();
6853 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6854 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6855 if start_word_range != end_word_range {
6856 self.document_highlights_task.take();
6857 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6858 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6859 return None;
6860 }
6861
6862 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6863 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6864 cx.background_executor()
6865 .timer(Duration::from_millis(debounce))
6866 .await;
6867
6868 let highlights = if let Some(highlights) = cx
6869 .update(|cx| {
6870 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6871 })
6872 .ok()
6873 .flatten()
6874 {
6875 highlights.await.log_err()
6876 } else {
6877 None
6878 };
6879
6880 if let Some(highlights) = highlights {
6881 this.update(cx, |this, cx| {
6882 if this.pending_rename.is_some() {
6883 return;
6884 }
6885
6886 let buffer = this.buffer.read(cx);
6887 if buffer
6888 .text_anchor_for_position(cursor_position, cx)
6889 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6890 {
6891 return;
6892 }
6893
6894 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6895 let mut write_ranges = Vec::new();
6896 let mut read_ranges = Vec::new();
6897 for highlight in highlights {
6898 let buffer_id = cursor_buffer.read(cx).remote_id();
6899 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6900 {
6901 let start = highlight
6902 .range
6903 .start
6904 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6905 let end = highlight
6906 .range
6907 .end
6908 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6909 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6910 continue;
6911 }
6912
6913 let range = Anchor::range_in_buffer(excerpt_id, buffer_id, start..end);
6914 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6915 write_ranges.push(range);
6916 } else {
6917 read_ranges.push(range);
6918 }
6919 }
6920 }
6921
6922 this.highlight_background::<DocumentHighlightRead>(
6923 &read_ranges,
6924 |theme| theme.colors().editor_document_highlight_read_background,
6925 cx,
6926 );
6927 this.highlight_background::<DocumentHighlightWrite>(
6928 &write_ranges,
6929 |theme| theme.colors().editor_document_highlight_write_background,
6930 cx,
6931 );
6932 cx.notify();
6933 })
6934 .log_err();
6935 }
6936 }));
6937 None
6938 }
6939
6940 fn prepare_highlight_query_from_selection(
6941 &mut self,
6942 cx: &mut Context<Editor>,
6943 ) -> Option<(String, Range<Anchor>)> {
6944 if matches!(self.mode, EditorMode::SingleLine) {
6945 return None;
6946 }
6947 if !EditorSettings::get_global(cx).selection_highlight {
6948 return None;
6949 }
6950 if self.selections.count() != 1 || self.selections.line_mode() {
6951 return None;
6952 }
6953 let selection = self.selections.newest_anchor();
6954 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6955 let selection_point_range = selection.start.to_point(&multi_buffer_snapshot)
6956 ..selection.end.to_point(&multi_buffer_snapshot);
6957 // If the selection spans multiple rows OR it is empty
6958 if selection_point_range.start.row != selection_point_range.end.row
6959 || selection_point_range.start.column == selection_point_range.end.column
6960 {
6961 return None;
6962 }
6963
6964 let query = multi_buffer_snapshot
6965 .text_for_range(selection.range())
6966 .collect::<String>();
6967 if query.trim().is_empty() {
6968 return None;
6969 }
6970 Some((query, selection.range()))
6971 }
6972
6973 fn update_selection_occurrence_highlights(
6974 &mut self,
6975 query_text: String,
6976 query_range: Range<Anchor>,
6977 multi_buffer_range_to_query: Range<Point>,
6978 use_debounce: bool,
6979 window: &mut Window,
6980 cx: &mut Context<Editor>,
6981 ) -> Task<()> {
6982 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6983 cx.spawn_in(window, async move |editor, cx| {
6984 if use_debounce {
6985 cx.background_executor()
6986 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6987 .await;
6988 }
6989 let match_task = cx.background_spawn(async move {
6990 let buffer_ranges = multi_buffer_snapshot
6991 .range_to_buffer_ranges(multi_buffer_range_to_query)
6992 .into_iter()
6993 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6994 let mut match_ranges = Vec::new();
6995 let Ok(regex) = project::search::SearchQuery::text(
6996 query_text.clone(),
6997 false,
6998 false,
6999 false,
7000 Default::default(),
7001 Default::default(),
7002 false,
7003 None,
7004 ) else {
7005 return Vec::default();
7006 };
7007 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7008 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7009 match_ranges.extend(
7010 regex
7011 .search(buffer_snapshot, Some(search_range.clone()))
7012 .await
7013 .into_iter()
7014 .filter_map(|match_range| {
7015 let match_start = buffer_snapshot
7016 .anchor_after(search_range.start + match_range.start);
7017 let match_end = buffer_snapshot
7018 .anchor_before(search_range.start + match_range.end);
7019 let match_anchor_range = Anchor::range_in_buffer(
7020 excerpt_id,
7021 buffer_snapshot.remote_id(),
7022 match_start..match_end,
7023 );
7024 (match_anchor_range != query_range).then_some(match_anchor_range)
7025 }),
7026 );
7027 }
7028 match_ranges
7029 });
7030 let match_ranges = match_task.await;
7031 editor
7032 .update_in(cx, |editor, _, cx| {
7033 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7034 if !match_ranges.is_empty() {
7035 editor.highlight_background::<SelectedTextHighlight>(
7036 &match_ranges,
7037 |theme| theme.colors().editor_document_highlight_bracket_background,
7038 cx,
7039 )
7040 }
7041 })
7042 .log_err();
7043 })
7044 }
7045
7046 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7047 struct NewlineFold;
7048 let type_id = std::any::TypeId::of::<NewlineFold>();
7049 if !self.mode.is_single_line() {
7050 return;
7051 }
7052 let snapshot = self.snapshot(window, cx);
7053 if snapshot.buffer_snapshot().max_point().row == 0 {
7054 return;
7055 }
7056 let task = cx.background_spawn(async move {
7057 let new_newlines = snapshot
7058 .buffer_chars_at(0)
7059 .filter_map(|(c, i)| {
7060 if c == '\n' {
7061 Some(
7062 snapshot.buffer_snapshot().anchor_after(i)
7063 ..snapshot.buffer_snapshot().anchor_before(i + 1),
7064 )
7065 } else {
7066 None
7067 }
7068 })
7069 .collect::<Vec<_>>();
7070 let existing_newlines = snapshot
7071 .folds_in_range(0..snapshot.buffer_snapshot().len())
7072 .filter_map(|fold| {
7073 if fold.placeholder.type_tag == Some(type_id) {
7074 Some(fold.range.start..fold.range.end)
7075 } else {
7076 None
7077 }
7078 })
7079 .collect::<Vec<_>>();
7080
7081 (new_newlines, existing_newlines)
7082 });
7083 self.folding_newlines = cx.spawn(async move |this, cx| {
7084 let (new_newlines, existing_newlines) = task.await;
7085 if new_newlines == existing_newlines {
7086 return;
7087 }
7088 let placeholder = FoldPlaceholder {
7089 render: Arc::new(move |_, _, cx| {
7090 div()
7091 .bg(cx.theme().status().hint_background)
7092 .border_b_1()
7093 .size_full()
7094 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7095 .border_color(cx.theme().status().hint)
7096 .child("\\n")
7097 .into_any()
7098 }),
7099 constrain_width: false,
7100 merge_adjacent: false,
7101 type_tag: Some(type_id),
7102 };
7103 let creases = new_newlines
7104 .into_iter()
7105 .map(|range| Crease::simple(range, placeholder.clone()))
7106 .collect();
7107 this.update(cx, |this, cx| {
7108 this.display_map.update(cx, |display_map, cx| {
7109 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7110 display_map.fold(creases, cx);
7111 });
7112 })
7113 .ok();
7114 });
7115 }
7116
7117 fn refresh_selected_text_highlights(
7118 &mut self,
7119 on_buffer_edit: bool,
7120 window: &mut Window,
7121 cx: &mut Context<Editor>,
7122 ) {
7123 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
7124 else {
7125 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7126 self.quick_selection_highlight_task.take();
7127 self.debounced_selection_highlight_task.take();
7128 return;
7129 };
7130 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7131 if on_buffer_edit
7132 || self
7133 .quick_selection_highlight_task
7134 .as_ref()
7135 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7136 {
7137 let multi_buffer_visible_start = self
7138 .scroll_manager
7139 .anchor()
7140 .anchor
7141 .to_point(&multi_buffer_snapshot);
7142 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7143 multi_buffer_visible_start
7144 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7145 Bias::Left,
7146 );
7147 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7148 self.quick_selection_highlight_task = Some((
7149 query_range.clone(),
7150 self.update_selection_occurrence_highlights(
7151 query_text.clone(),
7152 query_range.clone(),
7153 multi_buffer_visible_range,
7154 false,
7155 window,
7156 cx,
7157 ),
7158 ));
7159 }
7160 if on_buffer_edit
7161 || self
7162 .debounced_selection_highlight_task
7163 .as_ref()
7164 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7165 {
7166 let multi_buffer_start = multi_buffer_snapshot
7167 .anchor_before(0)
7168 .to_point(&multi_buffer_snapshot);
7169 let multi_buffer_end = multi_buffer_snapshot
7170 .anchor_after(multi_buffer_snapshot.len())
7171 .to_point(&multi_buffer_snapshot);
7172 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7173 self.debounced_selection_highlight_task = Some((
7174 query_range.clone(),
7175 self.update_selection_occurrence_highlights(
7176 query_text,
7177 query_range,
7178 multi_buffer_full_range,
7179 true,
7180 window,
7181 cx,
7182 ),
7183 ));
7184 }
7185 }
7186
7187 pub fn refresh_edit_prediction(
7188 &mut self,
7189 debounce: bool,
7190 user_requested: bool,
7191 window: &mut Window,
7192 cx: &mut Context<Self>,
7193 ) -> Option<()> {
7194 if DisableAiSettings::get_global(cx).disable_ai {
7195 return None;
7196 }
7197
7198 let provider = self.edit_prediction_provider()?;
7199 let cursor = self.selections.newest_anchor().head();
7200 let (buffer, cursor_buffer_position) =
7201 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7202
7203 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7204 self.discard_edit_prediction(false, cx);
7205 return None;
7206 }
7207
7208 self.update_visible_edit_prediction(window, cx);
7209
7210 if !user_requested
7211 && (!self.should_show_edit_predictions()
7212 || !self.is_focused(window)
7213 || buffer.read(cx).is_empty())
7214 {
7215 self.discard_edit_prediction(false, cx);
7216 return None;
7217 }
7218
7219 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7220 Some(())
7221 }
7222
7223 fn show_edit_predictions_in_menu(&self) -> bool {
7224 match self.edit_prediction_settings {
7225 EditPredictionSettings::Disabled => false,
7226 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7227 }
7228 }
7229
7230 pub fn edit_predictions_enabled(&self) -> bool {
7231 match self.edit_prediction_settings {
7232 EditPredictionSettings::Disabled => false,
7233 EditPredictionSettings::Enabled { .. } => true,
7234 }
7235 }
7236
7237 fn edit_prediction_requires_modifier(&self) -> bool {
7238 match self.edit_prediction_settings {
7239 EditPredictionSettings::Disabled => false,
7240 EditPredictionSettings::Enabled {
7241 preview_requires_modifier,
7242 ..
7243 } => preview_requires_modifier,
7244 }
7245 }
7246
7247 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7248 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7249 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7250 self.discard_edit_prediction(false, cx);
7251 } else {
7252 let selection = self.selections.newest_anchor();
7253 let cursor = selection.head();
7254
7255 if let Some((buffer, cursor_buffer_position)) =
7256 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7257 {
7258 self.edit_prediction_settings =
7259 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7260 }
7261 }
7262 }
7263
7264 fn edit_prediction_settings_at_position(
7265 &self,
7266 buffer: &Entity<Buffer>,
7267 buffer_position: language::Anchor,
7268 cx: &App,
7269 ) -> EditPredictionSettings {
7270 if !self.mode.is_full()
7271 || !self.show_edit_predictions_override.unwrap_or(true)
7272 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7273 {
7274 return EditPredictionSettings::Disabled;
7275 }
7276
7277 let buffer = buffer.read(cx);
7278
7279 let file = buffer.file();
7280
7281 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7282 return EditPredictionSettings::Disabled;
7283 };
7284
7285 let by_provider = matches!(
7286 self.menu_edit_predictions_policy,
7287 MenuEditPredictionsPolicy::ByProvider
7288 );
7289
7290 let show_in_menu = by_provider
7291 && self
7292 .edit_prediction_provider
7293 .as_ref()
7294 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7295
7296 let preview_requires_modifier =
7297 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7298
7299 EditPredictionSettings::Enabled {
7300 show_in_menu,
7301 preview_requires_modifier,
7302 }
7303 }
7304
7305 fn should_show_edit_predictions(&self) -> bool {
7306 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7307 }
7308
7309 pub fn edit_prediction_preview_is_active(&self) -> bool {
7310 matches!(
7311 self.edit_prediction_preview,
7312 EditPredictionPreview::Active { .. }
7313 )
7314 }
7315
7316 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7317 let cursor = self.selections.newest_anchor().head();
7318 if let Some((buffer, cursor_position)) =
7319 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7320 {
7321 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7322 } else {
7323 false
7324 }
7325 }
7326
7327 pub fn supports_minimap(&self, cx: &App) -> bool {
7328 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7329 }
7330
7331 fn edit_predictions_enabled_in_buffer(
7332 &self,
7333 buffer: &Entity<Buffer>,
7334 buffer_position: language::Anchor,
7335 cx: &App,
7336 ) -> bool {
7337 maybe!({
7338 if self.read_only(cx) {
7339 return Some(false);
7340 }
7341 let provider = self.edit_prediction_provider()?;
7342 if !provider.is_enabled(buffer, buffer_position, cx) {
7343 return Some(false);
7344 }
7345 let buffer = buffer.read(cx);
7346 let Some(file) = buffer.file() else {
7347 return Some(true);
7348 };
7349 let settings = all_language_settings(Some(file), cx);
7350 Some(settings.edit_predictions_enabled_for_file(file, cx))
7351 })
7352 .unwrap_or(false)
7353 }
7354
7355 fn cycle_edit_prediction(
7356 &mut self,
7357 direction: Direction,
7358 window: &mut Window,
7359 cx: &mut Context<Self>,
7360 ) -> Option<()> {
7361 let provider = self.edit_prediction_provider()?;
7362 let cursor = self.selections.newest_anchor().head();
7363 let (buffer, cursor_buffer_position) =
7364 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7365 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7366 return None;
7367 }
7368
7369 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7370 self.update_visible_edit_prediction(window, cx);
7371
7372 Some(())
7373 }
7374
7375 pub fn show_edit_prediction(
7376 &mut self,
7377 _: &ShowEditPrediction,
7378 window: &mut Window,
7379 cx: &mut Context<Self>,
7380 ) {
7381 if !self.has_active_edit_prediction() {
7382 self.refresh_edit_prediction(false, true, window, cx);
7383 return;
7384 }
7385
7386 self.update_visible_edit_prediction(window, cx);
7387 }
7388
7389 pub fn display_cursor_names(
7390 &mut self,
7391 _: &DisplayCursorNames,
7392 window: &mut Window,
7393 cx: &mut Context<Self>,
7394 ) {
7395 self.show_cursor_names(window, cx);
7396 }
7397
7398 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7399 self.show_cursor_names = true;
7400 cx.notify();
7401 cx.spawn_in(window, async move |this, cx| {
7402 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7403 this.update(cx, |this, cx| {
7404 this.show_cursor_names = false;
7405 cx.notify()
7406 })
7407 .ok()
7408 })
7409 .detach();
7410 }
7411
7412 pub fn next_edit_prediction(
7413 &mut self,
7414 _: &NextEditPrediction,
7415 window: &mut Window,
7416 cx: &mut Context<Self>,
7417 ) {
7418 if self.has_active_edit_prediction() {
7419 self.cycle_edit_prediction(Direction::Next, window, cx);
7420 } else {
7421 let is_copilot_disabled = self
7422 .refresh_edit_prediction(false, true, window, cx)
7423 .is_none();
7424 if is_copilot_disabled {
7425 cx.propagate();
7426 }
7427 }
7428 }
7429
7430 pub fn previous_edit_prediction(
7431 &mut self,
7432 _: &PreviousEditPrediction,
7433 window: &mut Window,
7434 cx: &mut Context<Self>,
7435 ) {
7436 if self.has_active_edit_prediction() {
7437 self.cycle_edit_prediction(Direction::Prev, window, cx);
7438 } else {
7439 let is_copilot_disabled = self
7440 .refresh_edit_prediction(false, true, window, cx)
7441 .is_none();
7442 if is_copilot_disabled {
7443 cx.propagate();
7444 }
7445 }
7446 }
7447
7448 pub fn accept_edit_prediction(
7449 &mut self,
7450 _: &AcceptEditPrediction,
7451 window: &mut Window,
7452 cx: &mut Context<Self>,
7453 ) {
7454 if self.show_edit_predictions_in_menu() {
7455 self.hide_context_menu(window, cx);
7456 }
7457
7458 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7459 return;
7460 };
7461
7462 match &active_edit_prediction.completion {
7463 EditPrediction::MoveWithin { target, .. } => {
7464 let target = *target;
7465
7466 if let Some(position_map) = &self.last_position_map {
7467 if position_map
7468 .visible_row_range
7469 .contains(&target.to_display_point(&position_map.snapshot).row())
7470 || !self.edit_prediction_requires_modifier()
7471 {
7472 self.unfold_ranges(&[target..target], true, false, cx);
7473 // Note that this is also done in vim's handler of the Tab action.
7474 self.change_selections(
7475 SelectionEffects::scroll(Autoscroll::newest()),
7476 window,
7477 cx,
7478 |selections| {
7479 selections.select_anchor_ranges([target..target]);
7480 },
7481 );
7482 self.clear_row_highlights::<EditPredictionPreview>();
7483
7484 self.edit_prediction_preview
7485 .set_previous_scroll_position(None);
7486 } else {
7487 self.edit_prediction_preview
7488 .set_previous_scroll_position(Some(
7489 position_map.snapshot.scroll_anchor,
7490 ));
7491
7492 self.highlight_rows::<EditPredictionPreview>(
7493 target..target,
7494 cx.theme().colors().editor_highlighted_line_background,
7495 RowHighlightOptions {
7496 autoscroll: true,
7497 ..Default::default()
7498 },
7499 cx,
7500 );
7501 self.request_autoscroll(Autoscroll::fit(), cx);
7502 }
7503 }
7504 }
7505 EditPrediction::MoveOutside { snapshot, target } => {
7506 if let Some(workspace) = self.workspace() {
7507 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7508 .detach_and_log_err(cx);
7509 }
7510 }
7511 EditPrediction::Edit { edits, .. } => {
7512 self.report_edit_prediction_event(
7513 active_edit_prediction.completion_id.clone(),
7514 true,
7515 cx,
7516 );
7517
7518 if let Some(provider) = self.edit_prediction_provider() {
7519 provider.accept(cx);
7520 }
7521
7522 // Store the transaction ID and selections before applying the edit
7523 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7524
7525 let snapshot = self.buffer.read(cx).snapshot(cx);
7526 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7527
7528 self.buffer.update(cx, |buffer, cx| {
7529 buffer.edit(edits.iter().cloned(), None, cx)
7530 });
7531
7532 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7533 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7534 });
7535
7536 let selections = self.selections.disjoint_anchors_arc();
7537 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7538 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7539 if has_new_transaction {
7540 self.selection_history
7541 .insert_transaction(transaction_id_now, selections);
7542 }
7543 }
7544
7545 self.update_visible_edit_prediction(window, cx);
7546 if self.active_edit_prediction.is_none() {
7547 self.refresh_edit_prediction(true, true, window, cx);
7548 }
7549
7550 cx.notify();
7551 }
7552 }
7553
7554 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7555 }
7556
7557 pub fn accept_partial_edit_prediction(
7558 &mut self,
7559 _: &AcceptPartialEditPrediction,
7560 window: &mut Window,
7561 cx: &mut Context<Self>,
7562 ) {
7563 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7564 return;
7565 };
7566 if self.selections.count() != 1 {
7567 return;
7568 }
7569
7570 match &active_edit_prediction.completion {
7571 EditPrediction::MoveWithin { target, .. } => {
7572 let target = *target;
7573 self.change_selections(
7574 SelectionEffects::scroll(Autoscroll::newest()),
7575 window,
7576 cx,
7577 |selections| {
7578 selections.select_anchor_ranges([target..target]);
7579 },
7580 );
7581 }
7582 EditPrediction::MoveOutside { snapshot, target } => {
7583 if let Some(workspace) = self.workspace() {
7584 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7585 .detach_and_log_err(cx);
7586 }
7587 }
7588 EditPrediction::Edit { edits, .. } => {
7589 self.report_edit_prediction_event(
7590 active_edit_prediction.completion_id.clone(),
7591 true,
7592 cx,
7593 );
7594
7595 // Find an insertion that starts at the cursor position.
7596 let snapshot = self.buffer.read(cx).snapshot(cx);
7597 let cursor_offset = self.selections.newest::<usize>(cx).head();
7598 let insertion = edits.iter().find_map(|(range, text)| {
7599 let range = range.to_offset(&snapshot);
7600 if range.is_empty() && range.start == cursor_offset {
7601 Some(text)
7602 } else {
7603 None
7604 }
7605 });
7606
7607 if let Some(text) = insertion {
7608 let mut partial_completion = text
7609 .chars()
7610 .by_ref()
7611 .take_while(|c| c.is_alphabetic())
7612 .collect::<String>();
7613 if partial_completion.is_empty() {
7614 partial_completion = text
7615 .chars()
7616 .by_ref()
7617 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7618 .collect::<String>();
7619 }
7620
7621 cx.emit(EditorEvent::InputHandled {
7622 utf16_range_to_replace: None,
7623 text: partial_completion.clone().into(),
7624 });
7625
7626 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7627
7628 self.refresh_edit_prediction(true, true, window, cx);
7629 cx.notify();
7630 } else {
7631 self.accept_edit_prediction(&Default::default(), window, cx);
7632 }
7633 }
7634 }
7635 }
7636
7637 fn discard_edit_prediction(
7638 &mut self,
7639 should_report_edit_prediction_event: bool,
7640 cx: &mut Context<Self>,
7641 ) -> bool {
7642 if should_report_edit_prediction_event {
7643 let completion_id = self
7644 .active_edit_prediction
7645 .as_ref()
7646 .and_then(|active_completion| active_completion.completion_id.clone());
7647
7648 self.report_edit_prediction_event(completion_id, false, cx);
7649 }
7650
7651 if let Some(provider) = self.edit_prediction_provider() {
7652 provider.discard(cx);
7653 }
7654
7655 self.take_active_edit_prediction(cx)
7656 }
7657
7658 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7659 let Some(provider) = self.edit_prediction_provider() else {
7660 return;
7661 };
7662
7663 let Some((_, buffer, _)) = self
7664 .buffer
7665 .read(cx)
7666 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7667 else {
7668 return;
7669 };
7670
7671 let extension = buffer
7672 .read(cx)
7673 .file()
7674 .and_then(|file| Some(file.path().extension()?.to_string()));
7675
7676 let event_type = match accepted {
7677 true => "Edit Prediction Accepted",
7678 false => "Edit Prediction Discarded",
7679 };
7680 telemetry::event!(
7681 event_type,
7682 provider = provider.name(),
7683 prediction_id = id,
7684 suggestion_accepted = accepted,
7685 file_extension = extension,
7686 );
7687 }
7688
7689 fn open_editor_at_anchor(
7690 snapshot: &language::BufferSnapshot,
7691 target: language::Anchor,
7692 workspace: &Entity<Workspace>,
7693 window: &mut Window,
7694 cx: &mut App,
7695 ) -> Task<Result<()>> {
7696 workspace.update(cx, |workspace, cx| {
7697 let path = snapshot.file().map(|file| file.full_path(cx));
7698 let Some(path) =
7699 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7700 else {
7701 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7702 };
7703 let target = text::ToPoint::to_point(&target, snapshot);
7704 let item = workspace.open_path(path, None, true, window, cx);
7705 window.spawn(cx, async move |cx| {
7706 let Some(editor) = item.await?.downcast::<Editor>() else {
7707 return Ok(());
7708 };
7709 editor
7710 .update_in(cx, |editor, window, cx| {
7711 editor.go_to_singleton_buffer_point(target, window, cx);
7712 })
7713 .ok();
7714 anyhow::Ok(())
7715 })
7716 })
7717 }
7718
7719 pub fn has_active_edit_prediction(&self) -> bool {
7720 self.active_edit_prediction.is_some()
7721 }
7722
7723 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7724 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7725 return false;
7726 };
7727
7728 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7729 self.clear_highlights::<EditPredictionHighlight>(cx);
7730 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7731 true
7732 }
7733
7734 /// Returns true when we're displaying the edit prediction popover below the cursor
7735 /// like we are not previewing and the LSP autocomplete menu is visible
7736 /// or we are in `when_holding_modifier` mode.
7737 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7738 if self.edit_prediction_preview_is_active()
7739 || !self.show_edit_predictions_in_menu()
7740 || !self.edit_predictions_enabled()
7741 {
7742 return false;
7743 }
7744
7745 if self.has_visible_completions_menu() {
7746 return true;
7747 }
7748
7749 has_completion && self.edit_prediction_requires_modifier()
7750 }
7751
7752 fn handle_modifiers_changed(
7753 &mut self,
7754 modifiers: Modifiers,
7755 position_map: &PositionMap,
7756 window: &mut Window,
7757 cx: &mut Context<Self>,
7758 ) {
7759 if self.show_edit_predictions_in_menu() {
7760 self.update_edit_prediction_preview(&modifiers, window, cx);
7761 }
7762
7763 self.update_selection_mode(&modifiers, position_map, window, cx);
7764
7765 let mouse_position = window.mouse_position();
7766 if !position_map.text_hitbox.is_hovered(window) {
7767 return;
7768 }
7769
7770 self.update_hovered_link(
7771 position_map.point_for_position(mouse_position),
7772 &position_map.snapshot,
7773 modifiers,
7774 window,
7775 cx,
7776 )
7777 }
7778
7779 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7780 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7781 if invert {
7782 match multi_cursor_setting {
7783 MultiCursorModifier::Alt => modifiers.alt,
7784 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7785 }
7786 } else {
7787 match multi_cursor_setting {
7788 MultiCursorModifier::Alt => modifiers.secondary(),
7789 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7790 }
7791 }
7792 }
7793
7794 fn columnar_selection_mode(
7795 modifiers: &Modifiers,
7796 cx: &mut Context<Self>,
7797 ) -> Option<ColumnarMode> {
7798 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7799 if Self::multi_cursor_modifier(false, modifiers, cx) {
7800 Some(ColumnarMode::FromMouse)
7801 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7802 Some(ColumnarMode::FromSelection)
7803 } else {
7804 None
7805 }
7806 } else {
7807 None
7808 }
7809 }
7810
7811 fn update_selection_mode(
7812 &mut self,
7813 modifiers: &Modifiers,
7814 position_map: &PositionMap,
7815 window: &mut Window,
7816 cx: &mut Context<Self>,
7817 ) {
7818 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7819 return;
7820 };
7821 if self.selections.pending_anchor().is_none() {
7822 return;
7823 }
7824
7825 let mouse_position = window.mouse_position();
7826 let point_for_position = position_map.point_for_position(mouse_position);
7827 let position = point_for_position.previous_valid;
7828
7829 self.select(
7830 SelectPhase::BeginColumnar {
7831 position,
7832 reset: false,
7833 mode,
7834 goal_column: point_for_position.exact_unclipped.column(),
7835 },
7836 window,
7837 cx,
7838 );
7839 }
7840
7841 fn update_edit_prediction_preview(
7842 &mut self,
7843 modifiers: &Modifiers,
7844 window: &mut Window,
7845 cx: &mut Context<Self>,
7846 ) {
7847 let mut modifiers_held = false;
7848 if let Some(accept_keystroke) = self
7849 .accept_edit_prediction_keybind(false, window, cx)
7850 .keystroke()
7851 {
7852 modifiers_held = modifiers_held
7853 || (accept_keystroke.modifiers() == modifiers
7854 && accept_keystroke.modifiers().modified());
7855 };
7856 if let Some(accept_partial_keystroke) = self
7857 .accept_edit_prediction_keybind(true, window, cx)
7858 .keystroke()
7859 {
7860 modifiers_held = modifiers_held
7861 || (accept_partial_keystroke.modifiers() == modifiers
7862 && accept_partial_keystroke.modifiers().modified());
7863 }
7864
7865 if modifiers_held {
7866 if matches!(
7867 self.edit_prediction_preview,
7868 EditPredictionPreview::Inactive { .. }
7869 ) {
7870 self.edit_prediction_preview = EditPredictionPreview::Active {
7871 previous_scroll_position: None,
7872 since: Instant::now(),
7873 };
7874
7875 self.update_visible_edit_prediction(window, cx);
7876 cx.notify();
7877 }
7878 } else if let EditPredictionPreview::Active {
7879 previous_scroll_position,
7880 since,
7881 } = self.edit_prediction_preview
7882 {
7883 if let (Some(previous_scroll_position), Some(position_map)) =
7884 (previous_scroll_position, self.last_position_map.as_ref())
7885 {
7886 self.set_scroll_position(
7887 previous_scroll_position
7888 .scroll_position(&position_map.snapshot.display_snapshot),
7889 window,
7890 cx,
7891 );
7892 }
7893
7894 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7895 released_too_fast: since.elapsed() < Duration::from_millis(200),
7896 };
7897 self.clear_row_highlights::<EditPredictionPreview>();
7898 self.update_visible_edit_prediction(window, cx);
7899 cx.notify();
7900 }
7901 }
7902
7903 fn update_visible_edit_prediction(
7904 &mut self,
7905 _window: &mut Window,
7906 cx: &mut Context<Self>,
7907 ) -> Option<()> {
7908 if DisableAiSettings::get_global(cx).disable_ai {
7909 return None;
7910 }
7911
7912 if self.ime_transaction.is_some() {
7913 self.discard_edit_prediction(false, cx);
7914 return None;
7915 }
7916
7917 let selection = self.selections.newest_anchor();
7918 let cursor = selection.head();
7919 let multibuffer = self.buffer.read(cx).snapshot(cx);
7920 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7921 let excerpt_id = cursor.excerpt_id;
7922
7923 let show_in_menu = self.show_edit_predictions_in_menu();
7924 let completions_menu_has_precedence = !show_in_menu
7925 && (self.context_menu.borrow().is_some()
7926 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7927
7928 if completions_menu_has_precedence
7929 || !offset_selection.is_empty()
7930 || self
7931 .active_edit_prediction
7932 .as_ref()
7933 .is_some_and(|completion| {
7934 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7935 return false;
7936 };
7937 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7938 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7939 !invalidation_range.contains(&offset_selection.head())
7940 })
7941 {
7942 self.discard_edit_prediction(false, cx);
7943 return None;
7944 }
7945
7946 self.take_active_edit_prediction(cx);
7947 let Some(provider) = self.edit_prediction_provider() else {
7948 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7949 return None;
7950 };
7951
7952 let (buffer, cursor_buffer_position) =
7953 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7954
7955 self.edit_prediction_settings =
7956 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7957
7958 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7959
7960 if self.edit_prediction_indent_conflict {
7961 let cursor_point = cursor.to_point(&multibuffer);
7962
7963 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7964
7965 if let Some((_, indent)) = indents.iter().next()
7966 && indent.len == cursor_point.column
7967 {
7968 self.edit_prediction_indent_conflict = false;
7969 }
7970 }
7971
7972 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7973
7974 let (completion_id, edits, edit_preview) = match edit_prediction {
7975 edit_prediction::EditPrediction::Local {
7976 id,
7977 edits,
7978 edit_preview,
7979 } => (id, edits, edit_preview),
7980 edit_prediction::EditPrediction::Jump {
7981 id,
7982 snapshot,
7983 target,
7984 } => {
7985 self.stale_edit_prediction_in_menu = None;
7986 self.active_edit_prediction = Some(EditPredictionState {
7987 inlay_ids: vec![],
7988 completion: EditPrediction::MoveOutside { snapshot, target },
7989 completion_id: id,
7990 invalidation_range: None,
7991 });
7992 cx.notify();
7993 return Some(());
7994 }
7995 };
7996
7997 let edits = edits
7998 .into_iter()
7999 .flat_map(|(range, new_text)| {
8000 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
8001 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
8002 Some((start..end, new_text))
8003 })
8004 .collect::<Vec<_>>();
8005 if edits.is_empty() {
8006 return None;
8007 }
8008
8009 let first_edit_start = edits.first().unwrap().0.start;
8010 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8011 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8012
8013 let last_edit_end = edits.last().unwrap().0.end;
8014 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8015 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8016
8017 let cursor_row = cursor.to_point(&multibuffer).row;
8018
8019 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8020
8021 let mut inlay_ids = Vec::new();
8022 let invalidation_row_range;
8023 let move_invalidation_row_range = if cursor_row < edit_start_row {
8024 Some(cursor_row..edit_end_row)
8025 } else if cursor_row > edit_end_row {
8026 Some(edit_start_row..cursor_row)
8027 } else {
8028 None
8029 };
8030 let supports_jump = self
8031 .edit_prediction_provider
8032 .as_ref()
8033 .map(|provider| provider.provider.supports_jump_to_edit())
8034 .unwrap_or(true);
8035
8036 let is_move = supports_jump
8037 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8038 let completion = if is_move {
8039 invalidation_row_range =
8040 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8041 let target = first_edit_start;
8042 EditPrediction::MoveWithin { target, snapshot }
8043 } else {
8044 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8045 && !self.edit_predictions_hidden_for_vim_mode;
8046
8047 if show_completions_in_buffer {
8048 if edits
8049 .iter()
8050 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8051 {
8052 let mut inlays = Vec::new();
8053 for (range, new_text) in &edits {
8054 let inlay = Inlay::edit_prediction(
8055 post_inc(&mut self.next_inlay_id),
8056 range.start,
8057 new_text.as_str(),
8058 );
8059 inlay_ids.push(inlay.id);
8060 inlays.push(inlay);
8061 }
8062
8063 self.splice_inlays(&[], inlays, cx);
8064 } else {
8065 let background_color = cx.theme().status().deleted_background;
8066 self.highlight_text::<EditPredictionHighlight>(
8067 edits.iter().map(|(range, _)| range.clone()).collect(),
8068 HighlightStyle {
8069 background_color: Some(background_color),
8070 ..Default::default()
8071 },
8072 cx,
8073 );
8074 }
8075 }
8076
8077 invalidation_row_range = edit_start_row..edit_end_row;
8078
8079 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8080 if provider.show_tab_accept_marker() {
8081 EditDisplayMode::TabAccept
8082 } else {
8083 EditDisplayMode::Inline
8084 }
8085 } else {
8086 EditDisplayMode::DiffPopover
8087 };
8088
8089 EditPrediction::Edit {
8090 edits,
8091 edit_preview,
8092 display_mode,
8093 snapshot,
8094 }
8095 };
8096
8097 let invalidation_range = multibuffer
8098 .anchor_before(Point::new(invalidation_row_range.start, 0))
8099 ..multibuffer.anchor_after(Point::new(
8100 invalidation_row_range.end,
8101 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8102 ));
8103
8104 self.stale_edit_prediction_in_menu = None;
8105 self.active_edit_prediction = Some(EditPredictionState {
8106 inlay_ids,
8107 completion,
8108 completion_id,
8109 invalidation_range: Some(invalidation_range),
8110 });
8111
8112 cx.notify();
8113
8114 Some(())
8115 }
8116
8117 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8118 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8119 }
8120
8121 fn clear_tasks(&mut self) {
8122 self.tasks.clear()
8123 }
8124
8125 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8126 if self.tasks.insert(key, value).is_some() {
8127 // This case should hopefully be rare, but just in case...
8128 log::error!(
8129 "multiple different run targets found on a single line, only the last target will be rendered"
8130 )
8131 }
8132 }
8133
8134 /// Get all display points of breakpoints that will be rendered within editor
8135 ///
8136 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8137 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8138 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8139 fn active_breakpoints(
8140 &self,
8141 range: Range<DisplayRow>,
8142 window: &mut Window,
8143 cx: &mut Context<Self>,
8144 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8145 let mut breakpoint_display_points = HashMap::default();
8146
8147 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8148 return breakpoint_display_points;
8149 };
8150
8151 let snapshot = self.snapshot(window, cx);
8152
8153 let multi_buffer_snapshot = snapshot.display_snapshot.buffer_snapshot();
8154 let Some(project) = self.project() else {
8155 return breakpoint_display_points;
8156 };
8157
8158 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8159 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8160
8161 for (buffer_snapshot, range, excerpt_id) in
8162 multi_buffer_snapshot.range_to_buffer_ranges(range)
8163 {
8164 let Some(buffer) = project
8165 .read(cx)
8166 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8167 else {
8168 continue;
8169 };
8170 let breakpoints = breakpoint_store.read(cx).breakpoints(
8171 &buffer,
8172 Some(
8173 buffer_snapshot.anchor_before(range.start)
8174 ..buffer_snapshot.anchor_after(range.end),
8175 ),
8176 buffer_snapshot,
8177 cx,
8178 );
8179 for (breakpoint, state) in breakpoints {
8180 let multi_buffer_anchor =
8181 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8182 let position = multi_buffer_anchor
8183 .to_point(multi_buffer_snapshot)
8184 .to_display_point(&snapshot);
8185
8186 breakpoint_display_points.insert(
8187 position.row(),
8188 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8189 );
8190 }
8191 }
8192
8193 breakpoint_display_points
8194 }
8195
8196 fn breakpoint_context_menu(
8197 &self,
8198 anchor: Anchor,
8199 window: &mut Window,
8200 cx: &mut Context<Self>,
8201 ) -> Entity<ui::ContextMenu> {
8202 let weak_editor = cx.weak_entity();
8203 let focus_handle = self.focus_handle(cx);
8204
8205 let row = self
8206 .buffer
8207 .read(cx)
8208 .snapshot(cx)
8209 .summary_for_anchor::<Point>(&anchor)
8210 .row;
8211
8212 let breakpoint = self
8213 .breakpoint_at_row(row, window, cx)
8214 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8215
8216 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8217 "Edit Log Breakpoint"
8218 } else {
8219 "Set Log Breakpoint"
8220 };
8221
8222 let condition_breakpoint_msg = if breakpoint
8223 .as_ref()
8224 .is_some_and(|bp| bp.1.condition.is_some())
8225 {
8226 "Edit Condition Breakpoint"
8227 } else {
8228 "Set Condition Breakpoint"
8229 };
8230
8231 let hit_condition_breakpoint_msg = if breakpoint
8232 .as_ref()
8233 .is_some_and(|bp| bp.1.hit_condition.is_some())
8234 {
8235 "Edit Hit Condition Breakpoint"
8236 } else {
8237 "Set Hit Condition Breakpoint"
8238 };
8239
8240 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8241 "Unset Breakpoint"
8242 } else {
8243 "Set Breakpoint"
8244 };
8245
8246 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8247
8248 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8249 BreakpointState::Enabled => Some("Disable"),
8250 BreakpointState::Disabled => Some("Enable"),
8251 });
8252
8253 let (anchor, breakpoint) =
8254 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8255
8256 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8257 menu.on_blur_subscription(Subscription::new(|| {}))
8258 .context(focus_handle)
8259 .when(run_to_cursor, |this| {
8260 let weak_editor = weak_editor.clone();
8261 this.entry("Run to cursor", None, move |window, cx| {
8262 weak_editor
8263 .update(cx, |editor, cx| {
8264 editor.change_selections(
8265 SelectionEffects::no_scroll(),
8266 window,
8267 cx,
8268 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8269 );
8270 })
8271 .ok();
8272
8273 window.dispatch_action(Box::new(RunToCursor), cx);
8274 })
8275 .separator()
8276 })
8277 .when_some(toggle_state_msg, |this, msg| {
8278 this.entry(msg, None, {
8279 let weak_editor = weak_editor.clone();
8280 let breakpoint = breakpoint.clone();
8281 move |_window, cx| {
8282 weak_editor
8283 .update(cx, |this, cx| {
8284 this.edit_breakpoint_at_anchor(
8285 anchor,
8286 breakpoint.as_ref().clone(),
8287 BreakpointEditAction::InvertState,
8288 cx,
8289 );
8290 })
8291 .log_err();
8292 }
8293 })
8294 })
8295 .entry(set_breakpoint_msg, None, {
8296 let weak_editor = weak_editor.clone();
8297 let breakpoint = breakpoint.clone();
8298 move |_window, cx| {
8299 weak_editor
8300 .update(cx, |this, cx| {
8301 this.edit_breakpoint_at_anchor(
8302 anchor,
8303 breakpoint.as_ref().clone(),
8304 BreakpointEditAction::Toggle,
8305 cx,
8306 );
8307 })
8308 .log_err();
8309 }
8310 })
8311 .entry(log_breakpoint_msg, None, {
8312 let breakpoint = breakpoint.clone();
8313 let weak_editor = weak_editor.clone();
8314 move |window, cx| {
8315 weak_editor
8316 .update(cx, |this, cx| {
8317 this.add_edit_breakpoint_block(
8318 anchor,
8319 breakpoint.as_ref(),
8320 BreakpointPromptEditAction::Log,
8321 window,
8322 cx,
8323 );
8324 })
8325 .log_err();
8326 }
8327 })
8328 .entry(condition_breakpoint_msg, None, {
8329 let breakpoint = breakpoint.clone();
8330 let weak_editor = weak_editor.clone();
8331 move |window, cx| {
8332 weak_editor
8333 .update(cx, |this, cx| {
8334 this.add_edit_breakpoint_block(
8335 anchor,
8336 breakpoint.as_ref(),
8337 BreakpointPromptEditAction::Condition,
8338 window,
8339 cx,
8340 );
8341 })
8342 .log_err();
8343 }
8344 })
8345 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8346 weak_editor
8347 .update(cx, |this, cx| {
8348 this.add_edit_breakpoint_block(
8349 anchor,
8350 breakpoint.as_ref(),
8351 BreakpointPromptEditAction::HitCondition,
8352 window,
8353 cx,
8354 );
8355 })
8356 .log_err();
8357 })
8358 })
8359 }
8360
8361 fn render_breakpoint(
8362 &self,
8363 position: Anchor,
8364 row: DisplayRow,
8365 breakpoint: &Breakpoint,
8366 state: Option<BreakpointSessionState>,
8367 cx: &mut Context<Self>,
8368 ) -> IconButton {
8369 let is_rejected = state.is_some_and(|s| !s.verified);
8370 // Is it a breakpoint that shows up when hovering over gutter?
8371 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8372 (false, false),
8373 |PhantomBreakpointIndicator {
8374 is_active,
8375 display_row,
8376 collides_with_existing_breakpoint,
8377 }| {
8378 (
8379 is_active && display_row == row,
8380 collides_with_existing_breakpoint,
8381 )
8382 },
8383 );
8384
8385 let (color, icon) = {
8386 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8387 (false, false) => ui::IconName::DebugBreakpoint,
8388 (true, false) => ui::IconName::DebugLogBreakpoint,
8389 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8390 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8391 };
8392
8393 let color = if is_phantom {
8394 Color::Hint
8395 } else if is_rejected {
8396 Color::Disabled
8397 } else {
8398 Color::Debugger
8399 };
8400
8401 (color, icon)
8402 };
8403
8404 let breakpoint = Arc::from(breakpoint.clone());
8405
8406 let alt_as_text = gpui::Keystroke {
8407 modifiers: Modifiers::secondary_key(),
8408 ..Default::default()
8409 };
8410 let primary_action_text = if breakpoint.is_disabled() {
8411 "Enable breakpoint"
8412 } else if is_phantom && !collides_with_existing {
8413 "Set breakpoint"
8414 } else {
8415 "Unset breakpoint"
8416 };
8417 let focus_handle = self.focus_handle.clone();
8418
8419 let meta = if is_rejected {
8420 SharedString::from("No executable code is associated with this line.")
8421 } else if collides_with_existing && !breakpoint.is_disabled() {
8422 SharedString::from(format!(
8423 "{alt_as_text}-click to disable,\nright-click for more options."
8424 ))
8425 } else {
8426 SharedString::from("Right-click for more options.")
8427 };
8428 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8429 .icon_size(IconSize::XSmall)
8430 .size(ui::ButtonSize::None)
8431 .when(is_rejected, |this| {
8432 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8433 })
8434 .icon_color(color)
8435 .style(ButtonStyle::Transparent)
8436 .on_click(cx.listener({
8437 move |editor, event: &ClickEvent, window, cx| {
8438 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8439 BreakpointEditAction::InvertState
8440 } else {
8441 BreakpointEditAction::Toggle
8442 };
8443
8444 window.focus(&editor.focus_handle(cx));
8445 editor.edit_breakpoint_at_anchor(
8446 position,
8447 breakpoint.as_ref().clone(),
8448 edit_action,
8449 cx,
8450 );
8451 }
8452 }))
8453 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8454 editor.set_breakpoint_context_menu(
8455 row,
8456 Some(position),
8457 event.position(),
8458 window,
8459 cx,
8460 );
8461 }))
8462 .tooltip(move |window, cx| {
8463 Tooltip::with_meta_in(
8464 primary_action_text,
8465 Some(&ToggleBreakpoint),
8466 meta.clone(),
8467 &focus_handle,
8468 window,
8469 cx,
8470 )
8471 })
8472 }
8473
8474 fn build_tasks_context(
8475 project: &Entity<Project>,
8476 buffer: &Entity<Buffer>,
8477 buffer_row: u32,
8478 tasks: &Arc<RunnableTasks>,
8479 cx: &mut Context<Self>,
8480 ) -> Task<Option<task::TaskContext>> {
8481 let position = Point::new(buffer_row, tasks.column);
8482 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8483 let location = Location {
8484 buffer: buffer.clone(),
8485 range: range_start..range_start,
8486 };
8487 // Fill in the environmental variables from the tree-sitter captures
8488 let mut captured_task_variables = TaskVariables::default();
8489 for (capture_name, value) in tasks.extra_variables.clone() {
8490 captured_task_variables.insert(
8491 task::VariableName::Custom(capture_name.into()),
8492 value.clone(),
8493 );
8494 }
8495 project.update(cx, |project, cx| {
8496 project.task_store().update(cx, |task_store, cx| {
8497 task_store.task_context_for_location(captured_task_variables, location, cx)
8498 })
8499 })
8500 }
8501
8502 pub fn spawn_nearest_task(
8503 &mut self,
8504 action: &SpawnNearestTask,
8505 window: &mut Window,
8506 cx: &mut Context<Self>,
8507 ) {
8508 let Some((workspace, _)) = self.workspace.clone() else {
8509 return;
8510 };
8511 let Some(project) = self.project.clone() else {
8512 return;
8513 };
8514
8515 // Try to find a closest, enclosing node using tree-sitter that has a task
8516 let Some((buffer, buffer_row, tasks)) = self
8517 .find_enclosing_node_task(cx)
8518 // Or find the task that's closest in row-distance.
8519 .or_else(|| self.find_closest_task(cx))
8520 else {
8521 return;
8522 };
8523
8524 let reveal_strategy = action.reveal;
8525 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8526 cx.spawn_in(window, async move |_, cx| {
8527 let context = task_context.await?;
8528 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8529
8530 let resolved = &mut resolved_task.resolved;
8531 resolved.reveal = reveal_strategy;
8532
8533 workspace
8534 .update_in(cx, |workspace, window, cx| {
8535 workspace.schedule_resolved_task(
8536 task_source_kind,
8537 resolved_task,
8538 false,
8539 window,
8540 cx,
8541 );
8542 })
8543 .ok()
8544 })
8545 .detach();
8546 }
8547
8548 fn find_closest_task(
8549 &mut self,
8550 cx: &mut Context<Self>,
8551 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8552 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8553
8554 let ((buffer_id, row), tasks) = self
8555 .tasks
8556 .iter()
8557 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8558
8559 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8560 let tasks = Arc::new(tasks.to_owned());
8561 Some((buffer, *row, tasks))
8562 }
8563
8564 fn find_enclosing_node_task(
8565 &mut self,
8566 cx: &mut Context<Self>,
8567 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8568 let snapshot = self.buffer.read(cx).snapshot(cx);
8569 let offset = self.selections.newest::<usize>(cx).head();
8570 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8571 let buffer_id = excerpt.buffer().remote_id();
8572
8573 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8574 let mut cursor = layer.node().walk();
8575
8576 while cursor.goto_first_child_for_byte(offset).is_some() {
8577 if cursor.node().end_byte() == offset {
8578 cursor.goto_next_sibling();
8579 }
8580 }
8581
8582 // Ascend to the smallest ancestor that contains the range and has a task.
8583 loop {
8584 let node = cursor.node();
8585 let node_range = node.byte_range();
8586 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8587
8588 // Check if this node contains our offset
8589 if node_range.start <= offset && node_range.end >= offset {
8590 // If it contains offset, check for task
8591 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8592 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8593 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8594 }
8595 }
8596
8597 if !cursor.goto_parent() {
8598 break;
8599 }
8600 }
8601 None
8602 }
8603
8604 fn render_run_indicator(
8605 &self,
8606 _style: &EditorStyle,
8607 is_active: bool,
8608 row: DisplayRow,
8609 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8610 cx: &mut Context<Self>,
8611 ) -> IconButton {
8612 let color = Color::Muted;
8613 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8614
8615 IconButton::new(
8616 ("run_indicator", row.0 as usize),
8617 ui::IconName::PlayOutlined,
8618 )
8619 .shape(ui::IconButtonShape::Square)
8620 .icon_size(IconSize::XSmall)
8621 .icon_color(color)
8622 .toggle_state(is_active)
8623 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8624 let quick_launch = match e {
8625 ClickEvent::Keyboard(_) => true,
8626 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8627 };
8628
8629 window.focus(&editor.focus_handle(cx));
8630 editor.toggle_code_actions(
8631 &ToggleCodeActions {
8632 deployed_from: Some(CodeActionSource::RunMenu(row)),
8633 quick_launch,
8634 },
8635 window,
8636 cx,
8637 );
8638 }))
8639 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8640 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8641 }))
8642 }
8643
8644 pub fn context_menu_visible(&self) -> bool {
8645 !self.edit_prediction_preview_is_active()
8646 && self
8647 .context_menu
8648 .borrow()
8649 .as_ref()
8650 .is_some_and(|menu| menu.visible())
8651 }
8652
8653 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8654 self.context_menu
8655 .borrow()
8656 .as_ref()
8657 .map(|menu| menu.origin())
8658 }
8659
8660 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8661 self.context_menu_options = Some(options);
8662 }
8663
8664 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8665 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8666
8667 fn render_edit_prediction_popover(
8668 &mut self,
8669 text_bounds: &Bounds<Pixels>,
8670 content_origin: gpui::Point<Pixels>,
8671 right_margin: Pixels,
8672 editor_snapshot: &EditorSnapshot,
8673 visible_row_range: Range<DisplayRow>,
8674 scroll_top: ScrollOffset,
8675 scroll_bottom: ScrollOffset,
8676 line_layouts: &[LineWithInvisibles],
8677 line_height: Pixels,
8678 scroll_position: gpui::Point<ScrollOffset>,
8679 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8680 newest_selection_head: Option<DisplayPoint>,
8681 editor_width: Pixels,
8682 style: &EditorStyle,
8683 window: &mut Window,
8684 cx: &mut App,
8685 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8686 if self.mode().is_minimap() {
8687 return None;
8688 }
8689 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8690
8691 if self.edit_prediction_visible_in_cursor_popover(true) {
8692 return None;
8693 }
8694
8695 match &active_edit_prediction.completion {
8696 EditPrediction::MoveWithin { target, .. } => {
8697 let target_display_point = target.to_display_point(editor_snapshot);
8698
8699 if self.edit_prediction_requires_modifier() {
8700 if !self.edit_prediction_preview_is_active() {
8701 return None;
8702 }
8703
8704 self.render_edit_prediction_modifier_jump_popover(
8705 text_bounds,
8706 content_origin,
8707 visible_row_range,
8708 line_layouts,
8709 line_height,
8710 scroll_pixel_position,
8711 newest_selection_head,
8712 target_display_point,
8713 window,
8714 cx,
8715 )
8716 } else {
8717 self.render_edit_prediction_eager_jump_popover(
8718 text_bounds,
8719 content_origin,
8720 editor_snapshot,
8721 visible_row_range,
8722 scroll_top,
8723 scroll_bottom,
8724 line_height,
8725 scroll_pixel_position,
8726 target_display_point,
8727 editor_width,
8728 window,
8729 cx,
8730 )
8731 }
8732 }
8733 EditPrediction::Edit {
8734 display_mode: EditDisplayMode::Inline,
8735 ..
8736 } => None,
8737 EditPrediction::Edit {
8738 display_mode: EditDisplayMode::TabAccept,
8739 edits,
8740 ..
8741 } => {
8742 let range = &edits.first()?.0;
8743 let target_display_point = range.end.to_display_point(editor_snapshot);
8744
8745 self.render_edit_prediction_end_of_line_popover(
8746 "Accept",
8747 editor_snapshot,
8748 visible_row_range,
8749 target_display_point,
8750 line_height,
8751 scroll_pixel_position,
8752 content_origin,
8753 editor_width,
8754 window,
8755 cx,
8756 )
8757 }
8758 EditPrediction::Edit {
8759 edits,
8760 edit_preview,
8761 display_mode: EditDisplayMode::DiffPopover,
8762 snapshot,
8763 } => self.render_edit_prediction_diff_popover(
8764 text_bounds,
8765 content_origin,
8766 right_margin,
8767 editor_snapshot,
8768 visible_row_range,
8769 line_layouts,
8770 line_height,
8771 scroll_position,
8772 scroll_pixel_position,
8773 newest_selection_head,
8774 editor_width,
8775 style,
8776 edits,
8777 edit_preview,
8778 snapshot,
8779 window,
8780 cx,
8781 ),
8782 EditPrediction::MoveOutside { snapshot, .. } => {
8783 let file_name = snapshot
8784 .file()
8785 .map(|file| file.file_name(cx))
8786 .unwrap_or("untitled");
8787 let mut element = self
8788 .render_edit_prediction_line_popover(
8789 format!("Jump to {file_name}"),
8790 Some(IconName::ZedPredict),
8791 window,
8792 cx,
8793 )
8794 .into_any();
8795
8796 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8797 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8798 let origin_y = text_bounds.size.height - size.height - px(30.);
8799 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8800 element.prepaint_at(origin, window, cx);
8801
8802 Some((element, origin))
8803 }
8804 }
8805 }
8806
8807 fn render_edit_prediction_modifier_jump_popover(
8808 &mut self,
8809 text_bounds: &Bounds<Pixels>,
8810 content_origin: gpui::Point<Pixels>,
8811 visible_row_range: Range<DisplayRow>,
8812 line_layouts: &[LineWithInvisibles],
8813 line_height: Pixels,
8814 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8815 newest_selection_head: Option<DisplayPoint>,
8816 target_display_point: DisplayPoint,
8817 window: &mut Window,
8818 cx: &mut App,
8819 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8820 let scrolled_content_origin =
8821 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8822
8823 const SCROLL_PADDING_Y: Pixels = px(12.);
8824
8825 if target_display_point.row() < visible_row_range.start {
8826 return self.render_edit_prediction_scroll_popover(
8827 |_| SCROLL_PADDING_Y,
8828 IconName::ArrowUp,
8829 visible_row_range,
8830 line_layouts,
8831 newest_selection_head,
8832 scrolled_content_origin,
8833 window,
8834 cx,
8835 );
8836 } else if target_display_point.row() >= visible_row_range.end {
8837 return self.render_edit_prediction_scroll_popover(
8838 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8839 IconName::ArrowDown,
8840 visible_row_range,
8841 line_layouts,
8842 newest_selection_head,
8843 scrolled_content_origin,
8844 window,
8845 cx,
8846 );
8847 }
8848
8849 const POLE_WIDTH: Pixels = px(2.);
8850
8851 let line_layout =
8852 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8853 let target_column = target_display_point.column() as usize;
8854
8855 let target_x = line_layout.x_for_index(target_column);
8856 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8857 - scroll_pixel_position.y;
8858
8859 let flag_on_right = target_x < text_bounds.size.width / 2.;
8860
8861 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8862 border_color.l += 0.001;
8863
8864 let mut element = v_flex()
8865 .items_end()
8866 .when(flag_on_right, |el| el.items_start())
8867 .child(if flag_on_right {
8868 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8869 .rounded_bl(px(0.))
8870 .rounded_tl(px(0.))
8871 .border_l_2()
8872 .border_color(border_color)
8873 } else {
8874 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8875 .rounded_br(px(0.))
8876 .rounded_tr(px(0.))
8877 .border_r_2()
8878 .border_color(border_color)
8879 })
8880 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8881 .into_any();
8882
8883 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8884
8885 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8886 - point(
8887 if flag_on_right {
8888 POLE_WIDTH
8889 } else {
8890 size.width - POLE_WIDTH
8891 },
8892 size.height - line_height,
8893 );
8894
8895 origin.x = origin.x.max(content_origin.x);
8896
8897 element.prepaint_at(origin, window, cx);
8898
8899 Some((element, origin))
8900 }
8901
8902 fn render_edit_prediction_scroll_popover(
8903 &mut self,
8904 to_y: impl Fn(Size<Pixels>) -> Pixels,
8905 scroll_icon: IconName,
8906 visible_row_range: Range<DisplayRow>,
8907 line_layouts: &[LineWithInvisibles],
8908 newest_selection_head: Option<DisplayPoint>,
8909 scrolled_content_origin: gpui::Point<Pixels>,
8910 window: &mut Window,
8911 cx: &mut App,
8912 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8913 let mut element = self
8914 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8915 .into_any();
8916
8917 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8918
8919 let cursor = newest_selection_head?;
8920 let cursor_row_layout =
8921 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8922 let cursor_column = cursor.column() as usize;
8923
8924 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8925
8926 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8927
8928 element.prepaint_at(origin, window, cx);
8929 Some((element, origin))
8930 }
8931
8932 fn render_edit_prediction_eager_jump_popover(
8933 &mut self,
8934 text_bounds: &Bounds<Pixels>,
8935 content_origin: gpui::Point<Pixels>,
8936 editor_snapshot: &EditorSnapshot,
8937 visible_row_range: Range<DisplayRow>,
8938 scroll_top: ScrollOffset,
8939 scroll_bottom: ScrollOffset,
8940 line_height: Pixels,
8941 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8942 target_display_point: DisplayPoint,
8943 editor_width: Pixels,
8944 window: &mut Window,
8945 cx: &mut App,
8946 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8947 if target_display_point.row().as_f64() < scroll_top {
8948 let mut element = self
8949 .render_edit_prediction_line_popover(
8950 "Jump to Edit",
8951 Some(IconName::ArrowUp),
8952 window,
8953 cx,
8954 )
8955 .into_any();
8956
8957 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8958 let offset = point(
8959 (text_bounds.size.width - size.width) / 2.,
8960 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8961 );
8962
8963 let origin = text_bounds.origin + offset;
8964 element.prepaint_at(origin, window, cx);
8965 Some((element, origin))
8966 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8967 let mut element = self
8968 .render_edit_prediction_line_popover(
8969 "Jump to Edit",
8970 Some(IconName::ArrowDown),
8971 window,
8972 cx,
8973 )
8974 .into_any();
8975
8976 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8977 let offset = point(
8978 (text_bounds.size.width - size.width) / 2.,
8979 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8980 );
8981
8982 let origin = text_bounds.origin + offset;
8983 element.prepaint_at(origin, window, cx);
8984 Some((element, origin))
8985 } else {
8986 self.render_edit_prediction_end_of_line_popover(
8987 "Jump to Edit",
8988 editor_snapshot,
8989 visible_row_range,
8990 target_display_point,
8991 line_height,
8992 scroll_pixel_position,
8993 content_origin,
8994 editor_width,
8995 window,
8996 cx,
8997 )
8998 }
8999 }
9000
9001 fn render_edit_prediction_end_of_line_popover(
9002 self: &mut Editor,
9003 label: &'static str,
9004 editor_snapshot: &EditorSnapshot,
9005 visible_row_range: Range<DisplayRow>,
9006 target_display_point: DisplayPoint,
9007 line_height: Pixels,
9008 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9009 content_origin: gpui::Point<Pixels>,
9010 editor_width: Pixels,
9011 window: &mut Window,
9012 cx: &mut App,
9013 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9014 let target_line_end = DisplayPoint::new(
9015 target_display_point.row(),
9016 editor_snapshot.line_len(target_display_point.row()),
9017 );
9018
9019 let mut element = self
9020 .render_edit_prediction_line_popover(label, None, window, cx)
9021 .into_any();
9022
9023 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9024
9025 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
9026
9027 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9028 let mut origin = start_point
9029 + line_origin
9030 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9031 origin.x = origin.x.max(content_origin.x);
9032
9033 let max_x = content_origin.x + editor_width - size.width;
9034
9035 if origin.x > max_x {
9036 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9037
9038 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9039 origin.y += offset;
9040 IconName::ArrowUp
9041 } else {
9042 origin.y -= offset;
9043 IconName::ArrowDown
9044 };
9045
9046 element = self
9047 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9048 .into_any();
9049
9050 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9051
9052 origin.x = content_origin.x + editor_width - size.width - px(2.);
9053 }
9054
9055 element.prepaint_at(origin, window, cx);
9056 Some((element, origin))
9057 }
9058
9059 fn render_edit_prediction_diff_popover(
9060 self: &Editor,
9061 text_bounds: &Bounds<Pixels>,
9062 content_origin: gpui::Point<Pixels>,
9063 right_margin: Pixels,
9064 editor_snapshot: &EditorSnapshot,
9065 visible_row_range: Range<DisplayRow>,
9066 line_layouts: &[LineWithInvisibles],
9067 line_height: Pixels,
9068 scroll_position: gpui::Point<ScrollOffset>,
9069 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9070 newest_selection_head: Option<DisplayPoint>,
9071 editor_width: Pixels,
9072 style: &EditorStyle,
9073 edits: &Vec<(Range<Anchor>, String)>,
9074 edit_preview: &Option<language::EditPreview>,
9075 snapshot: &language::BufferSnapshot,
9076 window: &mut Window,
9077 cx: &mut App,
9078 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9079 let edit_start = edits
9080 .first()
9081 .unwrap()
9082 .0
9083 .start
9084 .to_display_point(editor_snapshot);
9085 let edit_end = edits
9086 .last()
9087 .unwrap()
9088 .0
9089 .end
9090 .to_display_point(editor_snapshot);
9091
9092 let is_visible = visible_row_range.contains(&edit_start.row())
9093 || visible_row_range.contains(&edit_end.row());
9094 if !is_visible {
9095 return None;
9096 }
9097
9098 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9099 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9100 } else {
9101 // Fallback for providers without edit_preview
9102 crate::edit_prediction_fallback_text(edits, cx)
9103 };
9104
9105 let styled_text = highlighted_edits.to_styled_text(&style.text);
9106 let line_count = highlighted_edits.text.lines().count();
9107
9108 const BORDER_WIDTH: Pixels = px(1.);
9109
9110 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9111 let has_keybind = keybind.is_some();
9112
9113 let mut element = h_flex()
9114 .items_start()
9115 .child(
9116 h_flex()
9117 .bg(cx.theme().colors().editor_background)
9118 .border(BORDER_WIDTH)
9119 .shadow_xs()
9120 .border_color(cx.theme().colors().border)
9121 .rounded_l_lg()
9122 .when(line_count > 1, |el| el.rounded_br_lg())
9123 .pr_1()
9124 .child(styled_text),
9125 )
9126 .child(
9127 h_flex()
9128 .h(line_height + BORDER_WIDTH * 2.)
9129 .px_1p5()
9130 .gap_1()
9131 // Workaround: For some reason, there's a gap if we don't do this
9132 .ml(-BORDER_WIDTH)
9133 .shadow(vec![gpui::BoxShadow {
9134 color: gpui::black().opacity(0.05),
9135 offset: point(px(1.), px(1.)),
9136 blur_radius: px(2.),
9137 spread_radius: px(0.),
9138 }])
9139 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9140 .border(BORDER_WIDTH)
9141 .border_color(cx.theme().colors().border)
9142 .rounded_r_lg()
9143 .id("edit_prediction_diff_popover_keybind")
9144 .when(!has_keybind, |el| {
9145 let status_colors = cx.theme().status();
9146
9147 el.bg(status_colors.error_background)
9148 .border_color(status_colors.error.opacity(0.6))
9149 .child(Icon::new(IconName::Info).color(Color::Error))
9150 .cursor_default()
9151 .hoverable_tooltip(move |_window, cx| {
9152 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9153 })
9154 })
9155 .children(keybind),
9156 )
9157 .into_any();
9158
9159 let longest_row =
9160 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9161 let longest_line_width = if visible_row_range.contains(&longest_row) {
9162 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9163 } else {
9164 layout_line(
9165 longest_row,
9166 editor_snapshot,
9167 style,
9168 editor_width,
9169 |_| false,
9170 window,
9171 cx,
9172 )
9173 .width
9174 };
9175
9176 let viewport_bounds =
9177 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9178 right: -right_margin,
9179 ..Default::default()
9180 });
9181
9182 let x_after_longest = Pixels::from(
9183 ScrollPixelOffset::from(
9184 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9185 ) - scroll_pixel_position.x,
9186 );
9187
9188 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9189
9190 // Fully visible if it can be displayed within the window (allow overlapping other
9191 // panes). However, this is only allowed if the popover starts within text_bounds.
9192 let can_position_to_the_right = x_after_longest < text_bounds.right()
9193 && x_after_longest + element_bounds.width < viewport_bounds.right();
9194
9195 let mut origin = if can_position_to_the_right {
9196 point(
9197 x_after_longest,
9198 text_bounds.origin.y
9199 + Pixels::from(
9200 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9201 - scroll_pixel_position.y,
9202 ),
9203 )
9204 } else {
9205 let cursor_row = newest_selection_head.map(|head| head.row());
9206 let above_edit = edit_start
9207 .row()
9208 .0
9209 .checked_sub(line_count as u32)
9210 .map(DisplayRow);
9211 let below_edit = Some(edit_end.row() + 1);
9212 let above_cursor =
9213 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9214 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9215
9216 // Place the edit popover adjacent to the edit if there is a location
9217 // available that is onscreen and does not obscure the cursor. Otherwise,
9218 // place it adjacent to the cursor.
9219 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9220 .into_iter()
9221 .flatten()
9222 .find(|&start_row| {
9223 let end_row = start_row + line_count as u32;
9224 visible_row_range.contains(&start_row)
9225 && visible_row_range.contains(&end_row)
9226 && cursor_row
9227 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9228 })?;
9229
9230 content_origin
9231 + point(
9232 Pixels::from(-scroll_pixel_position.x),
9233 Pixels::from(
9234 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9235 ),
9236 )
9237 };
9238
9239 origin.x -= BORDER_WIDTH;
9240
9241 window.defer_draw(element, origin, 1);
9242
9243 // Do not return an element, since it will already be drawn due to defer_draw.
9244 None
9245 }
9246
9247 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9248 px(30.)
9249 }
9250
9251 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9252 if self.read_only(cx) {
9253 cx.theme().players().read_only()
9254 } else {
9255 self.style.as_ref().unwrap().local_player
9256 }
9257 }
9258
9259 fn render_edit_prediction_accept_keybind(
9260 &self,
9261 window: &mut Window,
9262 cx: &App,
9263 ) -> Option<AnyElement> {
9264 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9265 let accept_keystroke = accept_binding.keystroke()?;
9266
9267 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9268
9269 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9270 Color::Accent
9271 } else {
9272 Color::Muted
9273 };
9274
9275 h_flex()
9276 .px_0p5()
9277 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9278 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9279 .text_size(TextSize::XSmall.rems(cx))
9280 .child(h_flex().children(ui::render_modifiers(
9281 accept_keystroke.modifiers(),
9282 PlatformStyle::platform(),
9283 Some(modifiers_color),
9284 Some(IconSize::XSmall.rems().into()),
9285 true,
9286 )))
9287 .when(is_platform_style_mac, |parent| {
9288 parent.child(accept_keystroke.key().to_string())
9289 })
9290 .when(!is_platform_style_mac, |parent| {
9291 parent.child(
9292 Key::new(
9293 util::capitalize(accept_keystroke.key()),
9294 Some(Color::Default),
9295 )
9296 .size(Some(IconSize::XSmall.rems().into())),
9297 )
9298 })
9299 .into_any()
9300 .into()
9301 }
9302
9303 fn render_edit_prediction_line_popover(
9304 &self,
9305 label: impl Into<SharedString>,
9306 icon: Option<IconName>,
9307 window: &mut Window,
9308 cx: &App,
9309 ) -> Stateful<Div> {
9310 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9311
9312 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9313 let has_keybind = keybind.is_some();
9314
9315 h_flex()
9316 .id("ep-line-popover")
9317 .py_0p5()
9318 .pl_1()
9319 .pr(padding_right)
9320 .gap_1()
9321 .rounded_md()
9322 .border_1()
9323 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9324 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9325 .shadow_xs()
9326 .when(!has_keybind, |el| {
9327 let status_colors = cx.theme().status();
9328
9329 el.bg(status_colors.error_background)
9330 .border_color(status_colors.error.opacity(0.6))
9331 .pl_2()
9332 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9333 .cursor_default()
9334 .hoverable_tooltip(move |_window, cx| {
9335 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9336 })
9337 })
9338 .children(keybind)
9339 .child(
9340 Label::new(label)
9341 .size(LabelSize::Small)
9342 .when(!has_keybind, |el| {
9343 el.color(cx.theme().status().error.into()).strikethrough()
9344 }),
9345 )
9346 .when(!has_keybind, |el| {
9347 el.child(
9348 h_flex().ml_1().child(
9349 Icon::new(IconName::Info)
9350 .size(IconSize::Small)
9351 .color(cx.theme().status().error.into()),
9352 ),
9353 )
9354 })
9355 .when_some(icon, |element, icon| {
9356 element.child(
9357 div()
9358 .mt(px(1.5))
9359 .child(Icon::new(icon).size(IconSize::Small)),
9360 )
9361 })
9362 }
9363
9364 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9365 let accent_color = cx.theme().colors().text_accent;
9366 let editor_bg_color = cx.theme().colors().editor_background;
9367 editor_bg_color.blend(accent_color.opacity(0.1))
9368 }
9369
9370 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9371 let accent_color = cx.theme().colors().text_accent;
9372 let editor_bg_color = cx.theme().colors().editor_background;
9373 editor_bg_color.blend(accent_color.opacity(0.6))
9374 }
9375 fn get_prediction_provider_icon_name(
9376 provider: &Option<RegisteredEditPredictionProvider>,
9377 ) -> IconName {
9378 match provider {
9379 Some(provider) => match provider.provider.name() {
9380 "copilot" => IconName::Copilot,
9381 "supermaven" => IconName::Supermaven,
9382 _ => IconName::ZedPredict,
9383 },
9384 None => IconName::ZedPredict,
9385 }
9386 }
9387
9388 fn render_edit_prediction_cursor_popover(
9389 &self,
9390 min_width: Pixels,
9391 max_width: Pixels,
9392 cursor_point: Point,
9393 style: &EditorStyle,
9394 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9395 _window: &Window,
9396 cx: &mut Context<Editor>,
9397 ) -> Option<AnyElement> {
9398 let provider = self.edit_prediction_provider.as_ref()?;
9399 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9400
9401 let is_refreshing = provider.provider.is_refreshing(cx);
9402
9403 fn pending_completion_container(icon: IconName) -> Div {
9404 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9405 }
9406
9407 let completion = match &self.active_edit_prediction {
9408 Some(prediction) => {
9409 if !self.has_visible_completions_menu() {
9410 const RADIUS: Pixels = px(6.);
9411 const BORDER_WIDTH: Pixels = px(1.);
9412
9413 return Some(
9414 h_flex()
9415 .elevation_2(cx)
9416 .border(BORDER_WIDTH)
9417 .border_color(cx.theme().colors().border)
9418 .when(accept_keystroke.is_none(), |el| {
9419 el.border_color(cx.theme().status().error)
9420 })
9421 .rounded(RADIUS)
9422 .rounded_tl(px(0.))
9423 .overflow_hidden()
9424 .child(div().px_1p5().child(match &prediction.completion {
9425 EditPrediction::MoveWithin { target, snapshot } => {
9426 use text::ToPoint as _;
9427 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9428 {
9429 Icon::new(IconName::ZedPredictDown)
9430 } else {
9431 Icon::new(IconName::ZedPredictUp)
9432 }
9433 }
9434 EditPrediction::MoveOutside { .. } => {
9435 // TODO [zeta2] custom icon for external jump?
9436 Icon::new(provider_icon)
9437 }
9438 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9439 }))
9440 .child(
9441 h_flex()
9442 .gap_1()
9443 .py_1()
9444 .px_2()
9445 .rounded_r(RADIUS - BORDER_WIDTH)
9446 .border_l_1()
9447 .border_color(cx.theme().colors().border)
9448 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9449 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9450 el.child(
9451 Label::new("Hold")
9452 .size(LabelSize::Small)
9453 .when(accept_keystroke.is_none(), |el| {
9454 el.strikethrough()
9455 })
9456 .line_height_style(LineHeightStyle::UiLabel),
9457 )
9458 })
9459 .id("edit_prediction_cursor_popover_keybind")
9460 .when(accept_keystroke.is_none(), |el| {
9461 let status_colors = cx.theme().status();
9462
9463 el.bg(status_colors.error_background)
9464 .border_color(status_colors.error.opacity(0.6))
9465 .child(Icon::new(IconName::Info).color(Color::Error))
9466 .cursor_default()
9467 .hoverable_tooltip(move |_window, cx| {
9468 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9469 .into()
9470 })
9471 })
9472 .when_some(
9473 accept_keystroke.as_ref(),
9474 |el, accept_keystroke| {
9475 el.child(h_flex().children(ui::render_modifiers(
9476 accept_keystroke.modifiers(),
9477 PlatformStyle::platform(),
9478 Some(Color::Default),
9479 Some(IconSize::XSmall.rems().into()),
9480 false,
9481 )))
9482 },
9483 ),
9484 )
9485 .into_any(),
9486 );
9487 }
9488
9489 self.render_edit_prediction_cursor_popover_preview(
9490 prediction,
9491 cursor_point,
9492 style,
9493 cx,
9494 )?
9495 }
9496
9497 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9498 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9499 stale_completion,
9500 cursor_point,
9501 style,
9502 cx,
9503 )?,
9504
9505 None => pending_completion_container(provider_icon)
9506 .child(Label::new("...").size(LabelSize::Small)),
9507 },
9508
9509 None => pending_completion_container(provider_icon)
9510 .child(Label::new("...").size(LabelSize::Small)),
9511 };
9512
9513 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9514 completion
9515 .with_animation(
9516 "loading-completion",
9517 Animation::new(Duration::from_secs(2))
9518 .repeat()
9519 .with_easing(pulsating_between(0.4, 0.8)),
9520 |label, delta| label.opacity(delta),
9521 )
9522 .into_any_element()
9523 } else {
9524 completion.into_any_element()
9525 };
9526
9527 let has_completion = self.active_edit_prediction.is_some();
9528
9529 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9530 Some(
9531 h_flex()
9532 .min_w(min_width)
9533 .max_w(max_width)
9534 .flex_1()
9535 .elevation_2(cx)
9536 .border_color(cx.theme().colors().border)
9537 .child(
9538 div()
9539 .flex_1()
9540 .py_1()
9541 .px_2()
9542 .overflow_hidden()
9543 .child(completion),
9544 )
9545 .when_some(accept_keystroke, |el, accept_keystroke| {
9546 if !accept_keystroke.modifiers().modified() {
9547 return el;
9548 }
9549
9550 el.child(
9551 h_flex()
9552 .h_full()
9553 .border_l_1()
9554 .rounded_r_lg()
9555 .border_color(cx.theme().colors().border)
9556 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9557 .gap_1()
9558 .py_1()
9559 .px_2()
9560 .child(
9561 h_flex()
9562 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9563 .when(is_platform_style_mac, |parent| parent.gap_1())
9564 .child(h_flex().children(ui::render_modifiers(
9565 accept_keystroke.modifiers(),
9566 PlatformStyle::platform(),
9567 Some(if !has_completion {
9568 Color::Muted
9569 } else {
9570 Color::Default
9571 }),
9572 None,
9573 false,
9574 ))),
9575 )
9576 .child(Label::new("Preview").into_any_element())
9577 .opacity(if has_completion { 1.0 } else { 0.4 }),
9578 )
9579 })
9580 .into_any(),
9581 )
9582 }
9583
9584 fn render_edit_prediction_cursor_popover_preview(
9585 &self,
9586 completion: &EditPredictionState,
9587 cursor_point: Point,
9588 style: &EditorStyle,
9589 cx: &mut Context<Editor>,
9590 ) -> Option<Div> {
9591 use text::ToPoint as _;
9592
9593 fn render_relative_row_jump(
9594 prefix: impl Into<String>,
9595 current_row: u32,
9596 target_row: u32,
9597 ) -> Div {
9598 let (row_diff, arrow) = if target_row < current_row {
9599 (current_row - target_row, IconName::ArrowUp)
9600 } else {
9601 (target_row - current_row, IconName::ArrowDown)
9602 };
9603
9604 h_flex()
9605 .child(
9606 Label::new(format!("{}{}", prefix.into(), row_diff))
9607 .color(Color::Muted)
9608 .size(LabelSize::Small),
9609 )
9610 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9611 }
9612
9613 let supports_jump = self
9614 .edit_prediction_provider
9615 .as_ref()
9616 .map(|provider| provider.provider.supports_jump_to_edit())
9617 .unwrap_or(true);
9618
9619 match &completion.completion {
9620 EditPrediction::MoveWithin {
9621 target, snapshot, ..
9622 } => {
9623 if !supports_jump {
9624 return None;
9625 }
9626
9627 Some(
9628 h_flex()
9629 .px_2()
9630 .gap_2()
9631 .flex_1()
9632 .child(
9633 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9634 Icon::new(IconName::ZedPredictDown)
9635 } else {
9636 Icon::new(IconName::ZedPredictUp)
9637 },
9638 )
9639 .child(Label::new("Jump to Edit")),
9640 )
9641 }
9642 EditPrediction::MoveOutside { snapshot, .. } => {
9643 let file_name = snapshot
9644 .file()
9645 .map(|file| file.file_name(cx))
9646 .unwrap_or("untitled");
9647 Some(
9648 h_flex()
9649 .px_2()
9650 .gap_2()
9651 .flex_1()
9652 .child(Icon::new(IconName::ZedPredict))
9653 .child(Label::new(format!("Jump to {file_name}"))),
9654 )
9655 }
9656 EditPrediction::Edit {
9657 edits,
9658 edit_preview,
9659 snapshot,
9660 display_mode: _,
9661 } => {
9662 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9663
9664 let (highlighted_edits, has_more_lines) =
9665 if let Some(edit_preview) = edit_preview.as_ref() {
9666 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9667 .first_line_preview()
9668 } else {
9669 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9670 };
9671
9672 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9673 .with_default_highlights(&style.text, highlighted_edits.highlights);
9674
9675 let preview = h_flex()
9676 .gap_1()
9677 .min_w_16()
9678 .child(styled_text)
9679 .when(has_more_lines, |parent| parent.child("…"));
9680
9681 let left = if supports_jump && first_edit_row != cursor_point.row {
9682 render_relative_row_jump("", cursor_point.row, first_edit_row)
9683 .into_any_element()
9684 } else {
9685 let icon_name =
9686 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9687 Icon::new(icon_name).into_any_element()
9688 };
9689
9690 Some(
9691 h_flex()
9692 .h_full()
9693 .flex_1()
9694 .gap_2()
9695 .pr_1()
9696 .overflow_x_hidden()
9697 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9698 .child(left)
9699 .child(preview),
9700 )
9701 }
9702 }
9703 }
9704
9705 pub fn render_context_menu(
9706 &self,
9707 style: &EditorStyle,
9708 max_height_in_lines: u32,
9709 window: &mut Window,
9710 cx: &mut Context<Editor>,
9711 ) -> Option<AnyElement> {
9712 let menu = self.context_menu.borrow();
9713 let menu = menu.as_ref()?;
9714 if !menu.visible() {
9715 return None;
9716 };
9717 Some(menu.render(style, max_height_in_lines, window, cx))
9718 }
9719
9720 fn render_context_menu_aside(
9721 &mut self,
9722 max_size: Size<Pixels>,
9723 window: &mut Window,
9724 cx: &mut Context<Editor>,
9725 ) -> Option<AnyElement> {
9726 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9727 if menu.visible() {
9728 menu.render_aside(max_size, window, cx)
9729 } else {
9730 None
9731 }
9732 })
9733 }
9734
9735 fn hide_context_menu(
9736 &mut self,
9737 window: &mut Window,
9738 cx: &mut Context<Self>,
9739 ) -> Option<CodeContextMenu> {
9740 cx.notify();
9741 self.completion_tasks.clear();
9742 let context_menu = self.context_menu.borrow_mut().take();
9743 self.stale_edit_prediction_in_menu.take();
9744 self.update_visible_edit_prediction(window, cx);
9745 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9746 && let Some(completion_provider) = &self.completion_provider
9747 {
9748 completion_provider.selection_changed(None, window, cx);
9749 }
9750 context_menu
9751 }
9752
9753 fn show_snippet_choices(
9754 &mut self,
9755 choices: &Vec<String>,
9756 selection: Range<Anchor>,
9757 cx: &mut Context<Self>,
9758 ) {
9759 let Some((_, buffer, _)) = self
9760 .buffer()
9761 .read(cx)
9762 .excerpt_containing(selection.start, cx)
9763 else {
9764 return;
9765 };
9766 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9767 else {
9768 return;
9769 };
9770 if buffer != end_buffer {
9771 log::error!("expected anchor range to have matching buffer IDs");
9772 return;
9773 }
9774
9775 let id = post_inc(&mut self.next_completion_id);
9776 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9777 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9778 CompletionsMenu::new_snippet_choices(
9779 id,
9780 true,
9781 choices,
9782 selection,
9783 buffer,
9784 snippet_sort_order,
9785 ),
9786 ));
9787 }
9788
9789 pub fn insert_snippet(
9790 &mut self,
9791 insertion_ranges: &[Range<usize>],
9792 snippet: Snippet,
9793 window: &mut Window,
9794 cx: &mut Context<Self>,
9795 ) -> Result<()> {
9796 struct Tabstop<T> {
9797 is_end_tabstop: bool,
9798 ranges: Vec<Range<T>>,
9799 choices: Option<Vec<String>>,
9800 }
9801
9802 let tabstops = self.buffer.update(cx, |buffer, cx| {
9803 let snippet_text: Arc<str> = snippet.text.clone().into();
9804 let edits = insertion_ranges
9805 .iter()
9806 .cloned()
9807 .map(|range| (range, snippet_text.clone()));
9808 let autoindent_mode = AutoindentMode::Block {
9809 original_indent_columns: Vec::new(),
9810 };
9811 buffer.edit(edits, Some(autoindent_mode), cx);
9812
9813 let snapshot = &*buffer.read(cx);
9814 let snippet = &snippet;
9815 snippet
9816 .tabstops
9817 .iter()
9818 .map(|tabstop| {
9819 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9820 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9821 });
9822 let mut tabstop_ranges = tabstop
9823 .ranges
9824 .iter()
9825 .flat_map(|tabstop_range| {
9826 let mut delta = 0_isize;
9827 insertion_ranges.iter().map(move |insertion_range| {
9828 let insertion_start = insertion_range.start as isize + delta;
9829 delta +=
9830 snippet.text.len() as isize - insertion_range.len() as isize;
9831
9832 let start = ((insertion_start + tabstop_range.start) as usize)
9833 .min(snapshot.len());
9834 let end = ((insertion_start + tabstop_range.end) as usize)
9835 .min(snapshot.len());
9836 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9837 })
9838 })
9839 .collect::<Vec<_>>();
9840 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9841
9842 Tabstop {
9843 is_end_tabstop,
9844 ranges: tabstop_ranges,
9845 choices: tabstop.choices.clone(),
9846 }
9847 })
9848 .collect::<Vec<_>>()
9849 });
9850 if let Some(tabstop) = tabstops.first() {
9851 self.change_selections(Default::default(), window, cx, |s| {
9852 // Reverse order so that the first range is the newest created selection.
9853 // Completions will use it and autoscroll will prioritize it.
9854 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9855 });
9856
9857 if let Some(choices) = &tabstop.choices
9858 && let Some(selection) = tabstop.ranges.first()
9859 {
9860 self.show_snippet_choices(choices, selection.clone(), cx)
9861 }
9862
9863 // If we're already at the last tabstop and it's at the end of the snippet,
9864 // we're done, we don't need to keep the state around.
9865 if !tabstop.is_end_tabstop {
9866 let choices = tabstops
9867 .iter()
9868 .map(|tabstop| tabstop.choices.clone())
9869 .collect();
9870
9871 let ranges = tabstops
9872 .into_iter()
9873 .map(|tabstop| tabstop.ranges)
9874 .collect::<Vec<_>>();
9875
9876 self.snippet_stack.push(SnippetState {
9877 active_index: 0,
9878 ranges,
9879 choices,
9880 });
9881 }
9882
9883 // Check whether the just-entered snippet ends with an auto-closable bracket.
9884 if self.autoclose_regions.is_empty() {
9885 let snapshot = self.buffer.read(cx).snapshot(cx);
9886 let mut all_selections = self.selections.all::<Point>(cx);
9887 for selection in &mut all_selections {
9888 let selection_head = selection.head();
9889 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9890 continue;
9891 };
9892
9893 let mut bracket_pair = None;
9894 let max_lookup_length = scope
9895 .brackets()
9896 .map(|(pair, _)| {
9897 pair.start
9898 .as_str()
9899 .chars()
9900 .count()
9901 .max(pair.end.as_str().chars().count())
9902 })
9903 .max();
9904 if let Some(max_lookup_length) = max_lookup_length {
9905 let next_text = snapshot
9906 .chars_at(selection_head)
9907 .take(max_lookup_length)
9908 .collect::<String>();
9909 let prev_text = snapshot
9910 .reversed_chars_at(selection_head)
9911 .take(max_lookup_length)
9912 .collect::<String>();
9913
9914 for (pair, enabled) in scope.brackets() {
9915 if enabled
9916 && pair.close
9917 && prev_text.starts_with(pair.start.as_str())
9918 && next_text.starts_with(pair.end.as_str())
9919 {
9920 bracket_pair = Some(pair.clone());
9921 break;
9922 }
9923 }
9924 }
9925
9926 if let Some(pair) = bracket_pair {
9927 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9928 let autoclose_enabled =
9929 self.use_autoclose && snapshot_settings.use_autoclose;
9930 if autoclose_enabled {
9931 let start = snapshot.anchor_after(selection_head);
9932 let end = snapshot.anchor_after(selection_head);
9933 self.autoclose_regions.push(AutocloseRegion {
9934 selection_id: selection.id,
9935 range: start..end,
9936 pair,
9937 });
9938 }
9939 }
9940 }
9941 }
9942 }
9943 Ok(())
9944 }
9945
9946 pub fn move_to_next_snippet_tabstop(
9947 &mut self,
9948 window: &mut Window,
9949 cx: &mut Context<Self>,
9950 ) -> bool {
9951 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9952 }
9953
9954 pub fn move_to_prev_snippet_tabstop(
9955 &mut self,
9956 window: &mut Window,
9957 cx: &mut Context<Self>,
9958 ) -> bool {
9959 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9960 }
9961
9962 pub fn move_to_snippet_tabstop(
9963 &mut self,
9964 bias: Bias,
9965 window: &mut Window,
9966 cx: &mut Context<Self>,
9967 ) -> bool {
9968 if let Some(mut snippet) = self.snippet_stack.pop() {
9969 match bias {
9970 Bias::Left => {
9971 if snippet.active_index > 0 {
9972 snippet.active_index -= 1;
9973 } else {
9974 self.snippet_stack.push(snippet);
9975 return false;
9976 }
9977 }
9978 Bias::Right => {
9979 if snippet.active_index + 1 < snippet.ranges.len() {
9980 snippet.active_index += 1;
9981 } else {
9982 self.snippet_stack.push(snippet);
9983 return false;
9984 }
9985 }
9986 }
9987 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9988 self.change_selections(Default::default(), window, cx, |s| {
9989 // Reverse order so that the first range is the newest created selection.
9990 // Completions will use it and autoscroll will prioritize it.
9991 s.select_ranges(current_ranges.iter().rev().cloned())
9992 });
9993
9994 if let Some(choices) = &snippet.choices[snippet.active_index]
9995 && let Some(selection) = current_ranges.first()
9996 {
9997 self.show_snippet_choices(choices, selection.clone(), cx);
9998 }
9999
10000 // If snippet state is not at the last tabstop, push it back on the stack
10001 if snippet.active_index + 1 < snippet.ranges.len() {
10002 self.snippet_stack.push(snippet);
10003 }
10004 return true;
10005 }
10006 }
10007
10008 false
10009 }
10010
10011 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10012 self.transact(window, cx, |this, window, cx| {
10013 this.select_all(&SelectAll, window, cx);
10014 this.insert("", window, cx);
10015 });
10016 }
10017
10018 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10019 if self.read_only(cx) {
10020 return;
10021 }
10022 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10023 self.transact(window, cx, |this, window, cx| {
10024 this.select_autoclose_pair(window, cx);
10025 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10026 if !this.linked_edit_ranges.is_empty() {
10027 let selections = this.selections.all::<MultiBufferPoint>(cx);
10028 let snapshot = this.buffer.read(cx).snapshot(cx);
10029
10030 for selection in selections.iter() {
10031 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10032 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10033 if selection_start.buffer_id != selection_end.buffer_id {
10034 continue;
10035 }
10036 if let Some(ranges) =
10037 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10038 {
10039 for (buffer, entries) in ranges {
10040 linked_ranges.entry(buffer).or_default().extend(entries);
10041 }
10042 }
10043 }
10044 }
10045
10046 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
10047 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10048 for selection in &mut selections {
10049 if selection.is_empty() {
10050 let old_head = selection.head();
10051 let mut new_head =
10052 movement::left(&display_map, old_head.to_display_point(&display_map))
10053 .to_point(&display_map);
10054 if let Some((buffer, line_buffer_range)) = display_map
10055 .buffer_snapshot()
10056 .buffer_line_for_row(MultiBufferRow(old_head.row))
10057 {
10058 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10059 let indent_len = match indent_size.kind {
10060 IndentKind::Space => {
10061 buffer.settings_at(line_buffer_range.start, cx).tab_size
10062 }
10063 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10064 };
10065 if old_head.column <= indent_size.len && old_head.column > 0 {
10066 let indent_len = indent_len.get();
10067 new_head = cmp::min(
10068 new_head,
10069 MultiBufferPoint::new(
10070 old_head.row,
10071 ((old_head.column - 1) / indent_len) * indent_len,
10072 ),
10073 );
10074 }
10075 }
10076
10077 selection.set_head(new_head, SelectionGoal::None);
10078 }
10079 }
10080
10081 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10082 this.insert("", window, cx);
10083 let empty_str: Arc<str> = Arc::from("");
10084 for (buffer, edits) in linked_ranges {
10085 let snapshot = buffer.read(cx).snapshot();
10086 use text::ToPoint as TP;
10087
10088 let edits = edits
10089 .into_iter()
10090 .map(|range| {
10091 let end_point = TP::to_point(&range.end, &snapshot);
10092 let mut start_point = TP::to_point(&range.start, &snapshot);
10093
10094 if end_point == start_point {
10095 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10096 .saturating_sub(1);
10097 start_point =
10098 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10099 };
10100
10101 (start_point..end_point, empty_str.clone())
10102 })
10103 .sorted_by_key(|(range, _)| range.start)
10104 .collect::<Vec<_>>();
10105 buffer.update(cx, |this, cx| {
10106 this.edit(edits, None, cx);
10107 })
10108 }
10109 this.refresh_edit_prediction(true, false, window, cx);
10110 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
10111 });
10112 }
10113
10114 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10115 if self.read_only(cx) {
10116 return;
10117 }
10118 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10119 self.transact(window, cx, |this, window, cx| {
10120 this.change_selections(Default::default(), window, cx, |s| {
10121 s.move_with(|map, selection| {
10122 if selection.is_empty() {
10123 let cursor = movement::right(map, selection.head());
10124 selection.end = cursor;
10125 selection.reversed = true;
10126 selection.goal = SelectionGoal::None;
10127 }
10128 })
10129 });
10130 this.insert("", window, cx);
10131 this.refresh_edit_prediction(true, false, window, cx);
10132 });
10133 }
10134
10135 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10136 if self.mode.is_single_line() {
10137 cx.propagate();
10138 return;
10139 }
10140
10141 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10142 if self.move_to_prev_snippet_tabstop(window, cx) {
10143 return;
10144 }
10145 self.outdent(&Outdent, window, cx);
10146 }
10147
10148 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10149 if self.mode.is_single_line() {
10150 cx.propagate();
10151 return;
10152 }
10153
10154 if self.move_to_next_snippet_tabstop(window, cx) {
10155 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10156 return;
10157 }
10158 if self.read_only(cx) {
10159 return;
10160 }
10161 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10162 let mut selections = self.selections.all_adjusted(cx);
10163 let buffer = self.buffer.read(cx);
10164 let snapshot = buffer.snapshot(cx);
10165 let rows_iter = selections.iter().map(|s| s.head().row);
10166 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10167
10168 let has_some_cursor_in_whitespace = selections
10169 .iter()
10170 .filter(|selection| selection.is_empty())
10171 .any(|selection| {
10172 let cursor = selection.head();
10173 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10174 cursor.column < current_indent.len
10175 });
10176
10177 let mut edits = Vec::new();
10178 let mut prev_edited_row = 0;
10179 let mut row_delta = 0;
10180 for selection in &mut selections {
10181 if selection.start.row != prev_edited_row {
10182 row_delta = 0;
10183 }
10184 prev_edited_row = selection.end.row;
10185
10186 // If the selection is non-empty, then increase the indentation of the selected lines.
10187 if !selection.is_empty() {
10188 row_delta =
10189 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10190 continue;
10191 }
10192
10193 let cursor = selection.head();
10194 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10195 if let Some(suggested_indent) =
10196 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10197 {
10198 // Don't do anything if already at suggested indent
10199 // and there is any other cursor which is not
10200 if has_some_cursor_in_whitespace
10201 && cursor.column == current_indent.len
10202 && current_indent.len == suggested_indent.len
10203 {
10204 continue;
10205 }
10206
10207 // Adjust line and move cursor to suggested indent
10208 // if cursor is not at suggested indent
10209 if cursor.column < suggested_indent.len
10210 && cursor.column <= current_indent.len
10211 && current_indent.len <= suggested_indent.len
10212 {
10213 selection.start = Point::new(cursor.row, suggested_indent.len);
10214 selection.end = selection.start;
10215 if row_delta == 0 {
10216 edits.extend(Buffer::edit_for_indent_size_adjustment(
10217 cursor.row,
10218 current_indent,
10219 suggested_indent,
10220 ));
10221 row_delta = suggested_indent.len - current_indent.len;
10222 }
10223 continue;
10224 }
10225
10226 // If current indent is more than suggested indent
10227 // only move cursor to current indent and skip indent
10228 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10229 selection.start = Point::new(cursor.row, current_indent.len);
10230 selection.end = selection.start;
10231 continue;
10232 }
10233 }
10234
10235 // Otherwise, insert a hard or soft tab.
10236 let settings = buffer.language_settings_at(cursor, cx);
10237 let tab_size = if settings.hard_tabs {
10238 IndentSize::tab()
10239 } else {
10240 let tab_size = settings.tab_size.get();
10241 let indent_remainder = snapshot
10242 .text_for_range(Point::new(cursor.row, 0)..cursor)
10243 .flat_map(str::chars)
10244 .fold(row_delta % tab_size, |counter: u32, c| {
10245 if c == '\t' {
10246 0
10247 } else {
10248 (counter + 1) % tab_size
10249 }
10250 });
10251
10252 let chars_to_next_tab_stop = tab_size - indent_remainder;
10253 IndentSize::spaces(chars_to_next_tab_stop)
10254 };
10255 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10256 selection.end = selection.start;
10257 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10258 row_delta += tab_size.len;
10259 }
10260
10261 self.transact(window, cx, |this, window, cx| {
10262 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10263 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10264 this.refresh_edit_prediction(true, false, window, cx);
10265 });
10266 }
10267
10268 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10269 if self.read_only(cx) {
10270 return;
10271 }
10272 if self.mode.is_single_line() {
10273 cx.propagate();
10274 return;
10275 }
10276
10277 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10278 let mut selections = self.selections.all::<Point>(cx);
10279 let mut prev_edited_row = 0;
10280 let mut row_delta = 0;
10281 let mut edits = Vec::new();
10282 let buffer = self.buffer.read(cx);
10283 let snapshot = buffer.snapshot(cx);
10284 for selection in &mut selections {
10285 if selection.start.row != prev_edited_row {
10286 row_delta = 0;
10287 }
10288 prev_edited_row = selection.end.row;
10289
10290 row_delta =
10291 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10292 }
10293
10294 self.transact(window, cx, |this, window, cx| {
10295 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10296 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10297 });
10298 }
10299
10300 fn indent_selection(
10301 buffer: &MultiBuffer,
10302 snapshot: &MultiBufferSnapshot,
10303 selection: &mut Selection<Point>,
10304 edits: &mut Vec<(Range<Point>, String)>,
10305 delta_for_start_row: u32,
10306 cx: &App,
10307 ) -> u32 {
10308 let settings = buffer.language_settings_at(selection.start, cx);
10309 let tab_size = settings.tab_size.get();
10310 let indent_kind = if settings.hard_tabs {
10311 IndentKind::Tab
10312 } else {
10313 IndentKind::Space
10314 };
10315 let mut start_row = selection.start.row;
10316 let mut end_row = selection.end.row + 1;
10317
10318 // If a selection ends at the beginning of a line, don't indent
10319 // that last line.
10320 if selection.end.column == 0 && selection.end.row > selection.start.row {
10321 end_row -= 1;
10322 }
10323
10324 // Avoid re-indenting a row that has already been indented by a
10325 // previous selection, but still update this selection's column
10326 // to reflect that indentation.
10327 if delta_for_start_row > 0 {
10328 start_row += 1;
10329 selection.start.column += delta_for_start_row;
10330 if selection.end.row == selection.start.row {
10331 selection.end.column += delta_for_start_row;
10332 }
10333 }
10334
10335 let mut delta_for_end_row = 0;
10336 let has_multiple_rows = start_row + 1 != end_row;
10337 for row in start_row..end_row {
10338 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10339 let indent_delta = match (current_indent.kind, indent_kind) {
10340 (IndentKind::Space, IndentKind::Space) => {
10341 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10342 IndentSize::spaces(columns_to_next_tab_stop)
10343 }
10344 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10345 (_, IndentKind::Tab) => IndentSize::tab(),
10346 };
10347
10348 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10349 0
10350 } else {
10351 selection.start.column
10352 };
10353 let row_start = Point::new(row, start);
10354 edits.push((
10355 row_start..row_start,
10356 indent_delta.chars().collect::<String>(),
10357 ));
10358
10359 // Update this selection's endpoints to reflect the indentation.
10360 if row == selection.start.row {
10361 selection.start.column += indent_delta.len;
10362 }
10363 if row == selection.end.row {
10364 selection.end.column += indent_delta.len;
10365 delta_for_end_row = indent_delta.len;
10366 }
10367 }
10368
10369 if selection.start.row == selection.end.row {
10370 delta_for_start_row + delta_for_end_row
10371 } else {
10372 delta_for_end_row
10373 }
10374 }
10375
10376 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10377 if self.read_only(cx) {
10378 return;
10379 }
10380 if self.mode.is_single_line() {
10381 cx.propagate();
10382 return;
10383 }
10384
10385 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10386 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10387 let selections = self.selections.all::<Point>(cx);
10388 let mut deletion_ranges = Vec::new();
10389 let mut last_outdent = None;
10390 {
10391 let buffer = self.buffer.read(cx);
10392 let snapshot = buffer.snapshot(cx);
10393 for selection in &selections {
10394 let settings = buffer.language_settings_at(selection.start, cx);
10395 let tab_size = settings.tab_size.get();
10396 let mut rows = selection.spanned_rows(false, &display_map);
10397
10398 // Avoid re-outdenting a row that has already been outdented by a
10399 // previous selection.
10400 if let Some(last_row) = last_outdent
10401 && last_row == rows.start
10402 {
10403 rows.start = rows.start.next_row();
10404 }
10405 let has_multiple_rows = rows.len() > 1;
10406 for row in rows.iter_rows() {
10407 let indent_size = snapshot.indent_size_for_line(row);
10408 if indent_size.len > 0 {
10409 let deletion_len = match indent_size.kind {
10410 IndentKind::Space => {
10411 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10412 if columns_to_prev_tab_stop == 0 {
10413 tab_size
10414 } else {
10415 columns_to_prev_tab_stop
10416 }
10417 }
10418 IndentKind::Tab => 1,
10419 };
10420 let start = if has_multiple_rows
10421 || deletion_len > selection.start.column
10422 || indent_size.len < selection.start.column
10423 {
10424 0
10425 } else {
10426 selection.start.column - deletion_len
10427 };
10428 deletion_ranges.push(
10429 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10430 );
10431 last_outdent = Some(row);
10432 }
10433 }
10434 }
10435 }
10436
10437 self.transact(window, cx, |this, window, cx| {
10438 this.buffer.update(cx, |buffer, cx| {
10439 let empty_str: Arc<str> = Arc::default();
10440 buffer.edit(
10441 deletion_ranges
10442 .into_iter()
10443 .map(|range| (range, empty_str.clone())),
10444 None,
10445 cx,
10446 );
10447 });
10448 let selections = this.selections.all::<usize>(cx);
10449 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10450 });
10451 }
10452
10453 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10454 if self.read_only(cx) {
10455 return;
10456 }
10457 if self.mode.is_single_line() {
10458 cx.propagate();
10459 return;
10460 }
10461
10462 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10463 let selections = self
10464 .selections
10465 .all::<usize>(cx)
10466 .into_iter()
10467 .map(|s| s.range());
10468
10469 self.transact(window, cx, |this, window, cx| {
10470 this.buffer.update(cx, |buffer, cx| {
10471 buffer.autoindent_ranges(selections, cx);
10472 });
10473 let selections = this.selections.all::<usize>(cx);
10474 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10475 });
10476 }
10477
10478 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10479 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10480 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10481 let selections = self.selections.all::<Point>(cx);
10482
10483 let mut new_cursors = Vec::new();
10484 let mut edit_ranges = Vec::new();
10485 let mut selections = selections.iter().peekable();
10486 while let Some(selection) = selections.next() {
10487 let mut rows = selection.spanned_rows(false, &display_map);
10488
10489 // Accumulate contiguous regions of rows that we want to delete.
10490 while let Some(next_selection) = selections.peek() {
10491 let next_rows = next_selection.spanned_rows(false, &display_map);
10492 if next_rows.start <= rows.end {
10493 rows.end = next_rows.end;
10494 selections.next().unwrap();
10495 } else {
10496 break;
10497 }
10498 }
10499
10500 let buffer = display_map.buffer_snapshot();
10501 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10502 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10503 // If there's a line after the range, delete the \n from the end of the row range
10504 (
10505 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10506 rows.end,
10507 )
10508 } else {
10509 // If there isn't a line after the range, delete the \n from the line before the
10510 // start of the row range
10511 edit_start = edit_start.saturating_sub(1);
10512 (buffer.len(), rows.start.previous_row())
10513 };
10514
10515 let text_layout_details = self.text_layout_details(window);
10516 let x = display_map.x_for_display_point(
10517 selection.head().to_display_point(&display_map),
10518 &text_layout_details,
10519 );
10520 let row = Point::new(target_row.0, 0)
10521 .to_display_point(&display_map)
10522 .row();
10523 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10524
10525 new_cursors.push((
10526 selection.id,
10527 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10528 SelectionGoal::None,
10529 ));
10530 edit_ranges.push(edit_start..edit_end);
10531 }
10532
10533 self.transact(window, cx, |this, window, cx| {
10534 let buffer = this.buffer.update(cx, |buffer, cx| {
10535 let empty_str: Arc<str> = Arc::default();
10536 buffer.edit(
10537 edit_ranges
10538 .into_iter()
10539 .map(|range| (range, empty_str.clone())),
10540 None,
10541 cx,
10542 );
10543 buffer.snapshot(cx)
10544 });
10545 let new_selections = new_cursors
10546 .into_iter()
10547 .map(|(id, cursor, goal)| {
10548 let cursor = cursor.to_point(&buffer);
10549 Selection {
10550 id,
10551 start: cursor,
10552 end: cursor,
10553 reversed: false,
10554 goal,
10555 }
10556 })
10557 .collect();
10558
10559 this.change_selections(Default::default(), window, cx, |s| {
10560 s.select(new_selections);
10561 });
10562 });
10563 }
10564
10565 pub fn join_lines_impl(
10566 &mut self,
10567 insert_whitespace: bool,
10568 window: &mut Window,
10569 cx: &mut Context<Self>,
10570 ) {
10571 if self.read_only(cx) {
10572 return;
10573 }
10574 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10575 for selection in self.selections.all::<Point>(cx) {
10576 let start = MultiBufferRow(selection.start.row);
10577 // Treat single line selections as if they include the next line. Otherwise this action
10578 // would do nothing for single line selections individual cursors.
10579 let end = if selection.start.row == selection.end.row {
10580 MultiBufferRow(selection.start.row + 1)
10581 } else {
10582 MultiBufferRow(selection.end.row)
10583 };
10584
10585 if let Some(last_row_range) = row_ranges.last_mut()
10586 && start <= last_row_range.end
10587 {
10588 last_row_range.end = end;
10589 continue;
10590 }
10591 row_ranges.push(start..end);
10592 }
10593
10594 let snapshot = self.buffer.read(cx).snapshot(cx);
10595 let mut cursor_positions = Vec::new();
10596 for row_range in &row_ranges {
10597 let anchor = snapshot.anchor_before(Point::new(
10598 row_range.end.previous_row().0,
10599 snapshot.line_len(row_range.end.previous_row()),
10600 ));
10601 cursor_positions.push(anchor..anchor);
10602 }
10603
10604 self.transact(window, cx, |this, window, cx| {
10605 for row_range in row_ranges.into_iter().rev() {
10606 for row in row_range.iter_rows().rev() {
10607 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10608 let next_line_row = row.next_row();
10609 let indent = snapshot.indent_size_for_line(next_line_row);
10610 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10611
10612 let replace =
10613 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10614 " "
10615 } else {
10616 ""
10617 };
10618
10619 this.buffer.update(cx, |buffer, cx| {
10620 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10621 });
10622 }
10623 }
10624
10625 this.change_selections(Default::default(), window, cx, |s| {
10626 s.select_anchor_ranges(cursor_positions)
10627 });
10628 });
10629 }
10630
10631 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10632 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10633 self.join_lines_impl(true, window, cx);
10634 }
10635
10636 pub fn sort_lines_case_sensitive(
10637 &mut self,
10638 _: &SortLinesCaseSensitive,
10639 window: &mut Window,
10640 cx: &mut Context<Self>,
10641 ) {
10642 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10643 }
10644
10645 pub fn sort_lines_by_length(
10646 &mut self,
10647 _: &SortLinesByLength,
10648 window: &mut Window,
10649 cx: &mut Context<Self>,
10650 ) {
10651 self.manipulate_immutable_lines(window, cx, |lines| {
10652 lines.sort_by_key(|&line| line.chars().count())
10653 })
10654 }
10655
10656 pub fn sort_lines_case_insensitive(
10657 &mut self,
10658 _: &SortLinesCaseInsensitive,
10659 window: &mut Window,
10660 cx: &mut Context<Self>,
10661 ) {
10662 self.manipulate_immutable_lines(window, cx, |lines| {
10663 lines.sort_by_key(|line| line.to_lowercase())
10664 })
10665 }
10666
10667 pub fn unique_lines_case_insensitive(
10668 &mut self,
10669 _: &UniqueLinesCaseInsensitive,
10670 window: &mut Window,
10671 cx: &mut Context<Self>,
10672 ) {
10673 self.manipulate_immutable_lines(window, cx, |lines| {
10674 let mut seen = HashSet::default();
10675 lines.retain(|line| seen.insert(line.to_lowercase()));
10676 })
10677 }
10678
10679 pub fn unique_lines_case_sensitive(
10680 &mut self,
10681 _: &UniqueLinesCaseSensitive,
10682 window: &mut Window,
10683 cx: &mut Context<Self>,
10684 ) {
10685 self.manipulate_immutable_lines(window, cx, |lines| {
10686 let mut seen = HashSet::default();
10687 lines.retain(|line| seen.insert(*line));
10688 })
10689 }
10690
10691 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10692 let snapshot = self.buffer.read(cx).snapshot(cx);
10693 for selection in self.selections.disjoint_anchors_arc().iter() {
10694 if snapshot
10695 .language_at(selection.start)
10696 .and_then(|lang| lang.config().wrap_characters.as_ref())
10697 .is_some()
10698 {
10699 return true;
10700 }
10701 }
10702 false
10703 }
10704
10705 fn wrap_selections_in_tag(
10706 &mut self,
10707 _: &WrapSelectionsInTag,
10708 window: &mut Window,
10709 cx: &mut Context<Self>,
10710 ) {
10711 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10712
10713 let snapshot = self.buffer.read(cx).snapshot(cx);
10714
10715 let mut edits = Vec::new();
10716 let mut boundaries = Vec::new();
10717
10718 for selection in self.selections.all::<Point>(cx).iter() {
10719 let Some(wrap_config) = snapshot
10720 .language_at(selection.start)
10721 .and_then(|lang| lang.config().wrap_characters.clone())
10722 else {
10723 continue;
10724 };
10725
10726 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10727 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10728
10729 let start_before = snapshot.anchor_before(selection.start);
10730 let end_after = snapshot.anchor_after(selection.end);
10731
10732 edits.push((start_before..start_before, open_tag));
10733 edits.push((end_after..end_after, close_tag));
10734
10735 boundaries.push((
10736 start_before,
10737 end_after,
10738 wrap_config.start_prefix.len(),
10739 wrap_config.end_suffix.len(),
10740 ));
10741 }
10742
10743 if edits.is_empty() {
10744 return;
10745 }
10746
10747 self.transact(window, cx, |this, window, cx| {
10748 let buffer = this.buffer.update(cx, |buffer, cx| {
10749 buffer.edit(edits, None, cx);
10750 buffer.snapshot(cx)
10751 });
10752
10753 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10754 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10755 boundaries.into_iter()
10756 {
10757 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10758 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10759 new_selections.push(open_offset..open_offset);
10760 new_selections.push(close_offset..close_offset);
10761 }
10762
10763 this.change_selections(Default::default(), window, cx, |s| {
10764 s.select_ranges(new_selections);
10765 });
10766
10767 this.request_autoscroll(Autoscroll::fit(), cx);
10768 });
10769 }
10770
10771 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10772 let Some(project) = self.project.clone() else {
10773 return;
10774 };
10775 self.reload(project, window, cx)
10776 .detach_and_notify_err(window, cx);
10777 }
10778
10779 pub fn restore_file(
10780 &mut self,
10781 _: &::git::RestoreFile,
10782 window: &mut Window,
10783 cx: &mut Context<Self>,
10784 ) {
10785 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10786 let mut buffer_ids = HashSet::default();
10787 let snapshot = self.buffer().read(cx).snapshot(cx);
10788 for selection in self.selections.all::<usize>(cx) {
10789 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10790 }
10791
10792 let buffer = self.buffer().read(cx);
10793 let ranges = buffer_ids
10794 .into_iter()
10795 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10796 .collect::<Vec<_>>();
10797
10798 self.restore_hunks_in_ranges(ranges, window, cx);
10799 }
10800
10801 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10802 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10803 let selections = self
10804 .selections
10805 .all(cx)
10806 .into_iter()
10807 .map(|s| s.range())
10808 .collect();
10809 self.restore_hunks_in_ranges(selections, window, cx);
10810 }
10811
10812 pub fn restore_hunks_in_ranges(
10813 &mut self,
10814 ranges: Vec<Range<Point>>,
10815 window: &mut Window,
10816 cx: &mut Context<Editor>,
10817 ) {
10818 let mut revert_changes = HashMap::default();
10819 let chunk_by = self
10820 .snapshot(window, cx)
10821 .hunks_for_ranges(ranges)
10822 .into_iter()
10823 .chunk_by(|hunk| hunk.buffer_id);
10824 for (buffer_id, hunks) in &chunk_by {
10825 let hunks = hunks.collect::<Vec<_>>();
10826 for hunk in &hunks {
10827 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10828 }
10829 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10830 }
10831 drop(chunk_by);
10832 if !revert_changes.is_empty() {
10833 self.transact(window, cx, |editor, window, cx| {
10834 editor.restore(revert_changes, window, cx);
10835 });
10836 }
10837 }
10838
10839 pub fn open_active_item_in_terminal(
10840 &mut self,
10841 _: &OpenInTerminal,
10842 window: &mut Window,
10843 cx: &mut Context<Self>,
10844 ) {
10845 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10846 let project_path = buffer.read(cx).project_path(cx)?;
10847 let project = self.project()?.read(cx);
10848 let entry = project.entry_for_path(&project_path, cx)?;
10849 let parent = match &entry.canonical_path {
10850 Some(canonical_path) => canonical_path.to_path_buf(),
10851 None => project.absolute_path(&project_path, cx)?,
10852 }
10853 .parent()?
10854 .to_path_buf();
10855 Some(parent)
10856 }) {
10857 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10858 }
10859 }
10860
10861 fn set_breakpoint_context_menu(
10862 &mut self,
10863 display_row: DisplayRow,
10864 position: Option<Anchor>,
10865 clicked_point: gpui::Point<Pixels>,
10866 window: &mut Window,
10867 cx: &mut Context<Self>,
10868 ) {
10869 let source = self
10870 .buffer
10871 .read(cx)
10872 .snapshot(cx)
10873 .anchor_before(Point::new(display_row.0, 0u32));
10874
10875 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10876
10877 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10878 self,
10879 source,
10880 clicked_point,
10881 context_menu,
10882 window,
10883 cx,
10884 );
10885 }
10886
10887 fn add_edit_breakpoint_block(
10888 &mut self,
10889 anchor: Anchor,
10890 breakpoint: &Breakpoint,
10891 edit_action: BreakpointPromptEditAction,
10892 window: &mut Window,
10893 cx: &mut Context<Self>,
10894 ) {
10895 let weak_editor = cx.weak_entity();
10896 let bp_prompt = cx.new(|cx| {
10897 BreakpointPromptEditor::new(
10898 weak_editor,
10899 anchor,
10900 breakpoint.clone(),
10901 edit_action,
10902 window,
10903 cx,
10904 )
10905 });
10906
10907 let height = bp_prompt.update(cx, |this, cx| {
10908 this.prompt
10909 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10910 });
10911 let cloned_prompt = bp_prompt.clone();
10912 let blocks = vec![BlockProperties {
10913 style: BlockStyle::Sticky,
10914 placement: BlockPlacement::Above(anchor),
10915 height: Some(height),
10916 render: Arc::new(move |cx| {
10917 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10918 cloned_prompt.clone().into_any_element()
10919 }),
10920 priority: 0,
10921 }];
10922
10923 let focus_handle = bp_prompt.focus_handle(cx);
10924 window.focus(&focus_handle);
10925
10926 let block_ids = self.insert_blocks(blocks, None, cx);
10927 bp_prompt.update(cx, |prompt, _| {
10928 prompt.add_block_ids(block_ids);
10929 });
10930 }
10931
10932 pub(crate) fn breakpoint_at_row(
10933 &self,
10934 row: u32,
10935 window: &mut Window,
10936 cx: &mut Context<Self>,
10937 ) -> Option<(Anchor, Breakpoint)> {
10938 let snapshot = self.snapshot(window, cx);
10939 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
10940
10941 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10942 }
10943
10944 pub(crate) fn breakpoint_at_anchor(
10945 &self,
10946 breakpoint_position: Anchor,
10947 snapshot: &EditorSnapshot,
10948 cx: &mut Context<Self>,
10949 ) -> Option<(Anchor, Breakpoint)> {
10950 let buffer = self
10951 .buffer
10952 .read(cx)
10953 .buffer_for_anchor(breakpoint_position, cx)?;
10954
10955 let enclosing_excerpt = breakpoint_position.excerpt_id;
10956 let buffer_snapshot = buffer.read(cx).snapshot();
10957
10958 let row = buffer_snapshot
10959 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10960 .row;
10961
10962 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
10963 let anchor_end = snapshot
10964 .buffer_snapshot()
10965 .anchor_after(Point::new(row, line_len));
10966
10967 self.breakpoint_store
10968 .as_ref()?
10969 .read_with(cx, |breakpoint_store, cx| {
10970 breakpoint_store
10971 .breakpoints(
10972 &buffer,
10973 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10974 &buffer_snapshot,
10975 cx,
10976 )
10977 .next()
10978 .and_then(|(bp, _)| {
10979 let breakpoint_row = buffer_snapshot
10980 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10981 .row;
10982
10983 if breakpoint_row == row {
10984 snapshot
10985 .buffer_snapshot()
10986 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10987 .map(|position| (position, bp.bp.clone()))
10988 } else {
10989 None
10990 }
10991 })
10992 })
10993 }
10994
10995 pub fn edit_log_breakpoint(
10996 &mut self,
10997 _: &EditLogBreakpoint,
10998 window: &mut Window,
10999 cx: &mut Context<Self>,
11000 ) {
11001 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11002 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11003 message: None,
11004 state: BreakpointState::Enabled,
11005 condition: None,
11006 hit_condition: None,
11007 });
11008
11009 self.add_edit_breakpoint_block(
11010 anchor,
11011 &breakpoint,
11012 BreakpointPromptEditAction::Log,
11013 window,
11014 cx,
11015 );
11016 }
11017 }
11018
11019 fn breakpoints_at_cursors(
11020 &self,
11021 window: &mut Window,
11022 cx: &mut Context<Self>,
11023 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11024 let snapshot = self.snapshot(window, cx);
11025 let cursors = self
11026 .selections
11027 .disjoint_anchors_arc()
11028 .iter()
11029 .map(|selection| {
11030 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11031
11032 let breakpoint_position = self
11033 .breakpoint_at_row(cursor_position.row, window, cx)
11034 .map(|bp| bp.0)
11035 .unwrap_or_else(|| {
11036 snapshot
11037 .display_snapshot
11038 .buffer_snapshot()
11039 .anchor_after(Point::new(cursor_position.row, 0))
11040 });
11041
11042 let breakpoint = self
11043 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11044 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11045
11046 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11047 })
11048 // 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.
11049 .collect::<HashMap<Anchor, _>>();
11050
11051 cursors.into_iter().collect()
11052 }
11053
11054 pub fn enable_breakpoint(
11055 &mut self,
11056 _: &crate::actions::EnableBreakpoint,
11057 window: &mut Window,
11058 cx: &mut Context<Self>,
11059 ) {
11060 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11061 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11062 continue;
11063 };
11064 self.edit_breakpoint_at_anchor(
11065 anchor,
11066 breakpoint,
11067 BreakpointEditAction::InvertState,
11068 cx,
11069 );
11070 }
11071 }
11072
11073 pub fn disable_breakpoint(
11074 &mut self,
11075 _: &crate::actions::DisableBreakpoint,
11076 window: &mut Window,
11077 cx: &mut Context<Self>,
11078 ) {
11079 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11080 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11081 continue;
11082 };
11083 self.edit_breakpoint_at_anchor(
11084 anchor,
11085 breakpoint,
11086 BreakpointEditAction::InvertState,
11087 cx,
11088 );
11089 }
11090 }
11091
11092 pub fn toggle_breakpoint(
11093 &mut self,
11094 _: &crate::actions::ToggleBreakpoint,
11095 window: &mut Window,
11096 cx: &mut Context<Self>,
11097 ) {
11098 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11099 if let Some(breakpoint) = breakpoint {
11100 self.edit_breakpoint_at_anchor(
11101 anchor,
11102 breakpoint,
11103 BreakpointEditAction::Toggle,
11104 cx,
11105 );
11106 } else {
11107 self.edit_breakpoint_at_anchor(
11108 anchor,
11109 Breakpoint::new_standard(),
11110 BreakpointEditAction::Toggle,
11111 cx,
11112 );
11113 }
11114 }
11115 }
11116
11117 pub fn edit_breakpoint_at_anchor(
11118 &mut self,
11119 breakpoint_position: Anchor,
11120 breakpoint: Breakpoint,
11121 edit_action: BreakpointEditAction,
11122 cx: &mut Context<Self>,
11123 ) {
11124 let Some(breakpoint_store) = &self.breakpoint_store else {
11125 return;
11126 };
11127
11128 let Some(buffer) = self
11129 .buffer
11130 .read(cx)
11131 .buffer_for_anchor(breakpoint_position, cx)
11132 else {
11133 return;
11134 };
11135
11136 breakpoint_store.update(cx, |breakpoint_store, cx| {
11137 breakpoint_store.toggle_breakpoint(
11138 buffer,
11139 BreakpointWithPosition {
11140 position: breakpoint_position.text_anchor,
11141 bp: breakpoint,
11142 },
11143 edit_action,
11144 cx,
11145 );
11146 });
11147
11148 cx.notify();
11149 }
11150
11151 #[cfg(any(test, feature = "test-support"))]
11152 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11153 self.breakpoint_store.clone()
11154 }
11155
11156 pub fn prepare_restore_change(
11157 &self,
11158 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11159 hunk: &MultiBufferDiffHunk,
11160 cx: &mut App,
11161 ) -> Option<()> {
11162 if hunk.is_created_file() {
11163 return None;
11164 }
11165 let buffer = self.buffer.read(cx);
11166 let diff = buffer.diff_for(hunk.buffer_id)?;
11167 let buffer = buffer.buffer(hunk.buffer_id)?;
11168 let buffer = buffer.read(cx);
11169 let original_text = diff
11170 .read(cx)
11171 .base_text()
11172 .as_rope()
11173 .slice(hunk.diff_base_byte_range.clone());
11174 let buffer_snapshot = buffer.snapshot();
11175 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11176 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11177 probe
11178 .0
11179 .start
11180 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11181 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11182 }) {
11183 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11184 Some(())
11185 } else {
11186 None
11187 }
11188 }
11189
11190 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11191 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11192 }
11193
11194 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11195 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11196 }
11197
11198 fn manipulate_lines<M>(
11199 &mut self,
11200 window: &mut Window,
11201 cx: &mut Context<Self>,
11202 mut manipulate: M,
11203 ) where
11204 M: FnMut(&str) -> LineManipulationResult,
11205 {
11206 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11207
11208 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11209 let buffer = self.buffer.read(cx).snapshot(cx);
11210
11211 let mut edits = Vec::new();
11212
11213 let selections = self.selections.all::<Point>(cx);
11214 let mut selections = selections.iter().peekable();
11215 let mut contiguous_row_selections = Vec::new();
11216 let mut new_selections = Vec::new();
11217 let mut added_lines = 0;
11218 let mut removed_lines = 0;
11219
11220 while let Some(selection) = selections.next() {
11221 let (start_row, end_row) = consume_contiguous_rows(
11222 &mut contiguous_row_selections,
11223 selection,
11224 &display_map,
11225 &mut selections,
11226 );
11227
11228 let start_point = Point::new(start_row.0, 0);
11229 let end_point = Point::new(
11230 end_row.previous_row().0,
11231 buffer.line_len(end_row.previous_row()),
11232 );
11233 let text = buffer
11234 .text_for_range(start_point..end_point)
11235 .collect::<String>();
11236
11237 let LineManipulationResult {
11238 new_text,
11239 line_count_before,
11240 line_count_after,
11241 } = manipulate(&text);
11242
11243 edits.push((start_point..end_point, new_text));
11244
11245 // Selections must change based on added and removed line count
11246 let start_row =
11247 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11248 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11249 new_selections.push(Selection {
11250 id: selection.id,
11251 start: start_row,
11252 end: end_row,
11253 goal: SelectionGoal::None,
11254 reversed: selection.reversed,
11255 });
11256
11257 if line_count_after > line_count_before {
11258 added_lines += line_count_after - line_count_before;
11259 } else if line_count_before > line_count_after {
11260 removed_lines += line_count_before - line_count_after;
11261 }
11262 }
11263
11264 self.transact(window, cx, |this, window, cx| {
11265 let buffer = this.buffer.update(cx, |buffer, cx| {
11266 buffer.edit(edits, None, cx);
11267 buffer.snapshot(cx)
11268 });
11269
11270 // Recalculate offsets on newly edited buffer
11271 let new_selections = new_selections
11272 .iter()
11273 .map(|s| {
11274 let start_point = Point::new(s.start.0, 0);
11275 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11276 Selection {
11277 id: s.id,
11278 start: buffer.point_to_offset(start_point),
11279 end: buffer.point_to_offset(end_point),
11280 goal: s.goal,
11281 reversed: s.reversed,
11282 }
11283 })
11284 .collect();
11285
11286 this.change_selections(Default::default(), window, cx, |s| {
11287 s.select(new_selections);
11288 });
11289
11290 this.request_autoscroll(Autoscroll::fit(), cx);
11291 });
11292 }
11293
11294 fn manipulate_immutable_lines<Fn>(
11295 &mut self,
11296 window: &mut Window,
11297 cx: &mut Context<Self>,
11298 mut callback: Fn,
11299 ) where
11300 Fn: FnMut(&mut Vec<&str>),
11301 {
11302 self.manipulate_lines(window, cx, |text| {
11303 let mut lines: Vec<&str> = text.split('\n').collect();
11304 let line_count_before = lines.len();
11305
11306 callback(&mut lines);
11307
11308 LineManipulationResult {
11309 new_text: lines.join("\n"),
11310 line_count_before,
11311 line_count_after: lines.len(),
11312 }
11313 });
11314 }
11315
11316 fn manipulate_mutable_lines<Fn>(
11317 &mut self,
11318 window: &mut Window,
11319 cx: &mut Context<Self>,
11320 mut callback: Fn,
11321 ) where
11322 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11323 {
11324 self.manipulate_lines(window, cx, |text| {
11325 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11326 let line_count_before = lines.len();
11327
11328 callback(&mut lines);
11329
11330 LineManipulationResult {
11331 new_text: lines.join("\n"),
11332 line_count_before,
11333 line_count_after: lines.len(),
11334 }
11335 });
11336 }
11337
11338 pub fn convert_indentation_to_spaces(
11339 &mut self,
11340 _: &ConvertIndentationToSpaces,
11341 window: &mut Window,
11342 cx: &mut Context<Self>,
11343 ) {
11344 let settings = self.buffer.read(cx).language_settings(cx);
11345 let tab_size = settings.tab_size.get() as usize;
11346
11347 self.manipulate_mutable_lines(window, cx, |lines| {
11348 // Allocates a reasonably sized scratch buffer once for the whole loop
11349 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11350 // Avoids recomputing spaces that could be inserted many times
11351 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11352 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11353 .collect();
11354
11355 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11356 let mut chars = line.as_ref().chars();
11357 let mut col = 0;
11358 let mut changed = false;
11359
11360 for ch in chars.by_ref() {
11361 match ch {
11362 ' ' => {
11363 reindented_line.push(' ');
11364 col += 1;
11365 }
11366 '\t' => {
11367 // \t are converted to spaces depending on the current column
11368 let spaces_len = tab_size - (col % tab_size);
11369 reindented_line.extend(&space_cache[spaces_len - 1]);
11370 col += spaces_len;
11371 changed = true;
11372 }
11373 _ => {
11374 // If we dont append before break, the character is consumed
11375 reindented_line.push(ch);
11376 break;
11377 }
11378 }
11379 }
11380
11381 if !changed {
11382 reindented_line.clear();
11383 continue;
11384 }
11385 // Append the rest of the line and replace old reference with new one
11386 reindented_line.extend(chars);
11387 *line = Cow::Owned(reindented_line.clone());
11388 reindented_line.clear();
11389 }
11390 });
11391 }
11392
11393 pub fn convert_indentation_to_tabs(
11394 &mut self,
11395 _: &ConvertIndentationToTabs,
11396 window: &mut Window,
11397 cx: &mut Context<Self>,
11398 ) {
11399 let settings = self.buffer.read(cx).language_settings(cx);
11400 let tab_size = settings.tab_size.get() as usize;
11401
11402 self.manipulate_mutable_lines(window, cx, |lines| {
11403 // Allocates a reasonably sized buffer once for the whole loop
11404 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11405 // Avoids recomputing spaces that could be inserted many times
11406 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11407 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11408 .collect();
11409
11410 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11411 let mut chars = line.chars();
11412 let mut spaces_count = 0;
11413 let mut first_non_indent_char = None;
11414 let mut changed = false;
11415
11416 for ch in chars.by_ref() {
11417 match ch {
11418 ' ' => {
11419 // Keep track of spaces. Append \t when we reach tab_size
11420 spaces_count += 1;
11421 changed = true;
11422 if spaces_count == tab_size {
11423 reindented_line.push('\t');
11424 spaces_count = 0;
11425 }
11426 }
11427 '\t' => {
11428 reindented_line.push('\t');
11429 spaces_count = 0;
11430 }
11431 _ => {
11432 // Dont append it yet, we might have remaining spaces
11433 first_non_indent_char = Some(ch);
11434 break;
11435 }
11436 }
11437 }
11438
11439 if !changed {
11440 reindented_line.clear();
11441 continue;
11442 }
11443 // Remaining spaces that didn't make a full tab stop
11444 if spaces_count > 0 {
11445 reindented_line.extend(&space_cache[spaces_count - 1]);
11446 }
11447 // If we consume an extra character that was not indentation, add it back
11448 if let Some(extra_char) = first_non_indent_char {
11449 reindented_line.push(extra_char);
11450 }
11451 // Append the rest of the line and replace old reference with new one
11452 reindented_line.extend(chars);
11453 *line = Cow::Owned(reindented_line.clone());
11454 reindented_line.clear();
11455 }
11456 });
11457 }
11458
11459 pub fn convert_to_upper_case(
11460 &mut self,
11461 _: &ConvertToUpperCase,
11462 window: &mut Window,
11463 cx: &mut Context<Self>,
11464 ) {
11465 self.manipulate_text(window, cx, |text| text.to_uppercase())
11466 }
11467
11468 pub fn convert_to_lower_case(
11469 &mut self,
11470 _: &ConvertToLowerCase,
11471 window: &mut Window,
11472 cx: &mut Context<Self>,
11473 ) {
11474 self.manipulate_text(window, cx, |text| text.to_lowercase())
11475 }
11476
11477 pub fn convert_to_title_case(
11478 &mut self,
11479 _: &ConvertToTitleCase,
11480 window: &mut Window,
11481 cx: &mut Context<Self>,
11482 ) {
11483 self.manipulate_text(window, cx, |text| {
11484 text.split('\n')
11485 .map(|line| line.to_case(Case::Title))
11486 .join("\n")
11487 })
11488 }
11489
11490 pub fn convert_to_snake_case(
11491 &mut self,
11492 _: &ConvertToSnakeCase,
11493 window: &mut Window,
11494 cx: &mut Context<Self>,
11495 ) {
11496 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11497 }
11498
11499 pub fn convert_to_kebab_case(
11500 &mut self,
11501 _: &ConvertToKebabCase,
11502 window: &mut Window,
11503 cx: &mut Context<Self>,
11504 ) {
11505 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11506 }
11507
11508 pub fn convert_to_upper_camel_case(
11509 &mut self,
11510 _: &ConvertToUpperCamelCase,
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::UpperCamel))
11517 .join("\n")
11518 })
11519 }
11520
11521 pub fn convert_to_lower_camel_case(
11522 &mut self,
11523 _: &ConvertToLowerCamelCase,
11524 window: &mut Window,
11525 cx: &mut Context<Self>,
11526 ) {
11527 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11528 }
11529
11530 pub fn convert_to_opposite_case(
11531 &mut self,
11532 _: &ConvertToOppositeCase,
11533 window: &mut Window,
11534 cx: &mut Context<Self>,
11535 ) {
11536 self.manipulate_text(window, cx, |text| {
11537 text.chars()
11538 .fold(String::with_capacity(text.len()), |mut t, c| {
11539 if c.is_uppercase() {
11540 t.extend(c.to_lowercase());
11541 } else {
11542 t.extend(c.to_uppercase());
11543 }
11544 t
11545 })
11546 })
11547 }
11548
11549 pub fn convert_to_sentence_case(
11550 &mut self,
11551 _: &ConvertToSentenceCase,
11552 window: &mut Window,
11553 cx: &mut Context<Self>,
11554 ) {
11555 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11556 }
11557
11558 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11559 self.manipulate_text(window, cx, |text| {
11560 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11561 if has_upper_case_characters {
11562 text.to_lowercase()
11563 } else {
11564 text.to_uppercase()
11565 }
11566 })
11567 }
11568
11569 pub fn convert_to_rot13(
11570 &mut self,
11571 _: &ConvertToRot13,
11572 window: &mut Window,
11573 cx: &mut Context<Self>,
11574 ) {
11575 self.manipulate_text(window, cx, |text| {
11576 text.chars()
11577 .map(|c| match c {
11578 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11579 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11580 _ => c,
11581 })
11582 .collect()
11583 })
11584 }
11585
11586 pub fn convert_to_rot47(
11587 &mut self,
11588 _: &ConvertToRot47,
11589 window: &mut Window,
11590 cx: &mut Context<Self>,
11591 ) {
11592 self.manipulate_text(window, cx, |text| {
11593 text.chars()
11594 .map(|c| {
11595 let code_point = c as u32;
11596 if code_point >= 33 && code_point <= 126 {
11597 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11598 }
11599 c
11600 })
11601 .collect()
11602 })
11603 }
11604
11605 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11606 where
11607 Fn: FnMut(&str) -> String,
11608 {
11609 let buffer = self.buffer.read(cx).snapshot(cx);
11610
11611 let mut new_selections = Vec::new();
11612 let mut edits = Vec::new();
11613 let mut selection_adjustment = 0i32;
11614
11615 for selection in self.selections.all_adjusted(cx) {
11616 let selection_is_empty = selection.is_empty();
11617
11618 let (start, end) = if selection_is_empty {
11619 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11620 (word_range.start, word_range.end)
11621 } else {
11622 (
11623 buffer.point_to_offset(selection.start),
11624 buffer.point_to_offset(selection.end),
11625 )
11626 };
11627
11628 let text = buffer.text_for_range(start..end).collect::<String>();
11629 let old_length = text.len() as i32;
11630 let text = callback(&text);
11631
11632 new_selections.push(Selection {
11633 start: (start as i32 - selection_adjustment) as usize,
11634 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11635 goal: SelectionGoal::None,
11636 id: selection.id,
11637 reversed: selection.reversed,
11638 });
11639
11640 selection_adjustment += old_length - text.len() as i32;
11641
11642 edits.push((start..end, text));
11643 }
11644
11645 self.transact(window, cx, |this, window, cx| {
11646 this.buffer.update(cx, |buffer, cx| {
11647 buffer.edit(edits, None, cx);
11648 });
11649
11650 this.change_selections(Default::default(), window, cx, |s| {
11651 s.select(new_selections);
11652 });
11653
11654 this.request_autoscroll(Autoscroll::fit(), cx);
11655 });
11656 }
11657
11658 pub fn move_selection_on_drop(
11659 &mut self,
11660 selection: &Selection<Anchor>,
11661 target: DisplayPoint,
11662 is_cut: bool,
11663 window: &mut Window,
11664 cx: &mut Context<Self>,
11665 ) {
11666 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11667 let buffer = display_map.buffer_snapshot();
11668 let mut edits = Vec::new();
11669 let insert_point = display_map
11670 .clip_point(target, Bias::Left)
11671 .to_point(&display_map);
11672 let text = buffer
11673 .text_for_range(selection.start..selection.end)
11674 .collect::<String>();
11675 if is_cut {
11676 edits.push(((selection.start..selection.end), String::new()));
11677 }
11678 let insert_anchor = buffer.anchor_before(insert_point);
11679 edits.push(((insert_anchor..insert_anchor), text));
11680 let last_edit_start = insert_anchor.bias_left(buffer);
11681 let last_edit_end = insert_anchor.bias_right(buffer);
11682 self.transact(window, cx, |this, window, cx| {
11683 this.buffer.update(cx, |buffer, cx| {
11684 buffer.edit(edits, None, cx);
11685 });
11686 this.change_selections(Default::default(), window, cx, |s| {
11687 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11688 });
11689 });
11690 }
11691
11692 pub fn clear_selection_drag_state(&mut self) {
11693 self.selection_drag_state = SelectionDragState::None;
11694 }
11695
11696 pub fn duplicate(
11697 &mut self,
11698 upwards: bool,
11699 whole_lines: bool,
11700 window: &mut Window,
11701 cx: &mut Context<Self>,
11702 ) {
11703 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11704
11705 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11706 let buffer = display_map.buffer_snapshot();
11707 let selections = self.selections.all::<Point>(cx);
11708
11709 let mut edits = Vec::new();
11710 let mut selections_iter = selections.iter().peekable();
11711 while let Some(selection) = selections_iter.next() {
11712 let mut rows = selection.spanned_rows(false, &display_map);
11713 // duplicate line-wise
11714 if whole_lines || selection.start == selection.end {
11715 // Avoid duplicating the same lines twice.
11716 while let Some(next_selection) = selections_iter.peek() {
11717 let next_rows = next_selection.spanned_rows(false, &display_map);
11718 if next_rows.start < rows.end {
11719 rows.end = next_rows.end;
11720 selections_iter.next().unwrap();
11721 } else {
11722 break;
11723 }
11724 }
11725
11726 // Copy the text from the selected row region and splice it either at the start
11727 // or end of the region.
11728 let start = Point::new(rows.start.0, 0);
11729 let end = Point::new(
11730 rows.end.previous_row().0,
11731 buffer.line_len(rows.end.previous_row()),
11732 );
11733
11734 let mut text = buffer.text_for_range(start..end).collect::<String>();
11735
11736 let insert_location = if upwards {
11737 // When duplicating upward, we need to insert before the current line.
11738 // If we're on the last line and it doesn't end with a newline,
11739 // we need to add a newline before the duplicated content.
11740 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11741 && buffer.max_point().column > 0
11742 && !text.ends_with('\n');
11743
11744 if needs_leading_newline {
11745 text.insert(0, '\n');
11746 end
11747 } else {
11748 text.push('\n');
11749 Point::new(rows.end.0, 0)
11750 }
11751 } else {
11752 text.push('\n');
11753 start
11754 };
11755 edits.push((insert_location..insert_location, text));
11756 } else {
11757 // duplicate character-wise
11758 let start = selection.start;
11759 let end = selection.end;
11760 let text = buffer.text_for_range(start..end).collect::<String>();
11761 edits.push((selection.end..selection.end, text));
11762 }
11763 }
11764
11765 self.transact(window, cx, |this, _, cx| {
11766 this.buffer.update(cx, |buffer, cx| {
11767 buffer.edit(edits, None, cx);
11768 });
11769
11770 this.request_autoscroll(Autoscroll::fit(), cx);
11771 });
11772 }
11773
11774 pub fn duplicate_line_up(
11775 &mut self,
11776 _: &DuplicateLineUp,
11777 window: &mut Window,
11778 cx: &mut Context<Self>,
11779 ) {
11780 self.duplicate(true, true, window, cx);
11781 }
11782
11783 pub fn duplicate_line_down(
11784 &mut self,
11785 _: &DuplicateLineDown,
11786 window: &mut Window,
11787 cx: &mut Context<Self>,
11788 ) {
11789 self.duplicate(false, true, window, cx);
11790 }
11791
11792 pub fn duplicate_selection(
11793 &mut self,
11794 _: &DuplicateSelection,
11795 window: &mut Window,
11796 cx: &mut Context<Self>,
11797 ) {
11798 self.duplicate(false, false, window, cx);
11799 }
11800
11801 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11802 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11803 if self.mode.is_single_line() {
11804 cx.propagate();
11805 return;
11806 }
11807
11808 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11809 let buffer = self.buffer.read(cx).snapshot(cx);
11810
11811 let mut edits = Vec::new();
11812 let mut unfold_ranges = Vec::new();
11813 let mut refold_creases = Vec::new();
11814
11815 let selections = self.selections.all::<Point>(cx);
11816 let mut selections = selections.iter().peekable();
11817 let mut contiguous_row_selections = Vec::new();
11818 let mut new_selections = Vec::new();
11819
11820 while let Some(selection) = selections.next() {
11821 // Find all the selections that span a contiguous row range
11822 let (start_row, end_row) = consume_contiguous_rows(
11823 &mut contiguous_row_selections,
11824 selection,
11825 &display_map,
11826 &mut selections,
11827 );
11828
11829 // Move the text spanned by the row range to be before the line preceding the row range
11830 if start_row.0 > 0 {
11831 let range_to_move = Point::new(
11832 start_row.previous_row().0,
11833 buffer.line_len(start_row.previous_row()),
11834 )
11835 ..Point::new(
11836 end_row.previous_row().0,
11837 buffer.line_len(end_row.previous_row()),
11838 );
11839 let insertion_point = display_map
11840 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11841 .0;
11842
11843 // Don't move lines across excerpts
11844 if buffer
11845 .excerpt_containing(insertion_point..range_to_move.end)
11846 .is_some()
11847 {
11848 let text = buffer
11849 .text_for_range(range_to_move.clone())
11850 .flat_map(|s| s.chars())
11851 .skip(1)
11852 .chain(['\n'])
11853 .collect::<String>();
11854
11855 edits.push((
11856 buffer.anchor_after(range_to_move.start)
11857 ..buffer.anchor_before(range_to_move.end),
11858 String::new(),
11859 ));
11860 let insertion_anchor = buffer.anchor_after(insertion_point);
11861 edits.push((insertion_anchor..insertion_anchor, text));
11862
11863 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11864
11865 // Move selections up
11866 new_selections.extend(contiguous_row_selections.drain(..).map(
11867 |mut selection| {
11868 selection.start.row -= row_delta;
11869 selection.end.row -= row_delta;
11870 selection
11871 },
11872 ));
11873
11874 // Move folds up
11875 unfold_ranges.push(range_to_move.clone());
11876 for fold in display_map.folds_in_range(
11877 buffer.anchor_before(range_to_move.start)
11878 ..buffer.anchor_after(range_to_move.end),
11879 ) {
11880 let mut start = fold.range.start.to_point(&buffer);
11881 let mut end = fold.range.end.to_point(&buffer);
11882 start.row -= row_delta;
11883 end.row -= row_delta;
11884 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11885 }
11886 }
11887 }
11888
11889 // If we didn't move line(s), preserve the existing selections
11890 new_selections.append(&mut contiguous_row_selections);
11891 }
11892
11893 self.transact(window, cx, |this, window, cx| {
11894 this.unfold_ranges(&unfold_ranges, true, true, cx);
11895 this.buffer.update(cx, |buffer, cx| {
11896 for (range, text) in edits {
11897 buffer.edit([(range, text)], None, cx);
11898 }
11899 });
11900 this.fold_creases(refold_creases, true, window, cx);
11901 this.change_selections(Default::default(), window, cx, |s| {
11902 s.select(new_selections);
11903 })
11904 });
11905 }
11906
11907 pub fn move_line_down(
11908 &mut self,
11909 _: &MoveLineDown,
11910 window: &mut Window,
11911 cx: &mut Context<Self>,
11912 ) {
11913 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11914 if self.mode.is_single_line() {
11915 cx.propagate();
11916 return;
11917 }
11918
11919 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11920 let buffer = self.buffer.read(cx).snapshot(cx);
11921
11922 let mut edits = Vec::new();
11923 let mut unfold_ranges = Vec::new();
11924 let mut refold_creases = Vec::new();
11925
11926 let selections = self.selections.all::<Point>(cx);
11927 let mut selections = selections.iter().peekable();
11928 let mut contiguous_row_selections = Vec::new();
11929 let mut new_selections = Vec::new();
11930
11931 while let Some(selection) = selections.next() {
11932 // Find all the selections that span a contiguous row range
11933 let (start_row, end_row) = consume_contiguous_rows(
11934 &mut contiguous_row_selections,
11935 selection,
11936 &display_map,
11937 &mut selections,
11938 );
11939
11940 // Move the text spanned by the row range to be after the last line of the row range
11941 if end_row.0 <= buffer.max_point().row {
11942 let range_to_move =
11943 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11944 let insertion_point = display_map
11945 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11946 .0;
11947
11948 // Don't move lines across excerpt boundaries
11949 if buffer
11950 .excerpt_containing(range_to_move.start..insertion_point)
11951 .is_some()
11952 {
11953 let mut text = String::from("\n");
11954 text.extend(buffer.text_for_range(range_to_move.clone()));
11955 text.pop(); // Drop trailing newline
11956 edits.push((
11957 buffer.anchor_after(range_to_move.start)
11958 ..buffer.anchor_before(range_to_move.end),
11959 String::new(),
11960 ));
11961 let insertion_anchor = buffer.anchor_after(insertion_point);
11962 edits.push((insertion_anchor..insertion_anchor, text));
11963
11964 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11965
11966 // Move selections down
11967 new_selections.extend(contiguous_row_selections.drain(..).map(
11968 |mut selection| {
11969 selection.start.row += row_delta;
11970 selection.end.row += row_delta;
11971 selection
11972 },
11973 ));
11974
11975 // Move folds down
11976 unfold_ranges.push(range_to_move.clone());
11977 for fold in display_map.folds_in_range(
11978 buffer.anchor_before(range_to_move.start)
11979 ..buffer.anchor_after(range_to_move.end),
11980 ) {
11981 let mut start = fold.range.start.to_point(&buffer);
11982 let mut end = fold.range.end.to_point(&buffer);
11983 start.row += row_delta;
11984 end.row += row_delta;
11985 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11986 }
11987 }
11988 }
11989
11990 // If we didn't move line(s), preserve the existing selections
11991 new_selections.append(&mut contiguous_row_selections);
11992 }
11993
11994 self.transact(window, cx, |this, window, cx| {
11995 this.unfold_ranges(&unfold_ranges, true, true, cx);
11996 this.buffer.update(cx, |buffer, cx| {
11997 for (range, text) in edits {
11998 buffer.edit([(range, text)], None, cx);
11999 }
12000 });
12001 this.fold_creases(refold_creases, true, window, cx);
12002 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12003 });
12004 }
12005
12006 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12007 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12008 let text_layout_details = &self.text_layout_details(window);
12009 self.transact(window, cx, |this, window, cx| {
12010 let edits = this.change_selections(Default::default(), window, cx, |s| {
12011 let mut edits: Vec<(Range<usize>, String)> = Default::default();
12012 s.move_with(|display_map, selection| {
12013 if !selection.is_empty() {
12014 return;
12015 }
12016
12017 let mut head = selection.head();
12018 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12019 if head.column() == display_map.line_len(head.row()) {
12020 transpose_offset = display_map
12021 .buffer_snapshot()
12022 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12023 }
12024
12025 if transpose_offset == 0 {
12026 return;
12027 }
12028
12029 *head.column_mut() += 1;
12030 head = display_map.clip_point(head, Bias::Right);
12031 let goal = SelectionGoal::HorizontalPosition(
12032 display_map
12033 .x_for_display_point(head, text_layout_details)
12034 .into(),
12035 );
12036 selection.collapse_to(head, goal);
12037
12038 let transpose_start = display_map
12039 .buffer_snapshot()
12040 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12041 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12042 let transpose_end = display_map
12043 .buffer_snapshot()
12044 .clip_offset(transpose_offset + 1, Bias::Right);
12045 if let Some(ch) = display_map
12046 .buffer_snapshot()
12047 .chars_at(transpose_start)
12048 .next()
12049 {
12050 edits.push((transpose_start..transpose_offset, String::new()));
12051 edits.push((transpose_end..transpose_end, ch.to_string()));
12052 }
12053 }
12054 });
12055 edits
12056 });
12057 this.buffer
12058 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12059 let selections = this.selections.all::<usize>(cx);
12060 this.change_selections(Default::default(), window, cx, |s| {
12061 s.select(selections);
12062 });
12063 });
12064 }
12065
12066 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12067 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12068 if self.mode.is_single_line() {
12069 cx.propagate();
12070 return;
12071 }
12072
12073 self.rewrap_impl(RewrapOptions::default(), cx)
12074 }
12075
12076 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12077 let buffer = self.buffer.read(cx).snapshot(cx);
12078 let selections = self.selections.all::<Point>(cx);
12079
12080 #[derive(Clone, Debug, PartialEq)]
12081 enum CommentFormat {
12082 /// single line comment, with prefix for line
12083 Line(String),
12084 /// single line within a block comment, with prefix for line
12085 BlockLine(String),
12086 /// a single line of a block comment that includes the initial delimiter
12087 BlockCommentWithStart(BlockCommentConfig),
12088 /// a single line of a block comment that includes the ending delimiter
12089 BlockCommentWithEnd(BlockCommentConfig),
12090 }
12091
12092 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12093 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12094 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12095 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12096 .peekable();
12097
12098 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12099 row
12100 } else {
12101 return Vec::new();
12102 };
12103
12104 let language_settings = buffer.language_settings_at(selection.head(), cx);
12105 let language_scope = buffer.language_scope_at(selection.head());
12106
12107 let indent_and_prefix_for_row =
12108 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12109 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12110 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12111 &language_scope
12112 {
12113 let indent_end = Point::new(row, indent.len);
12114 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12115 let line_text_after_indent = buffer
12116 .text_for_range(indent_end..line_end)
12117 .collect::<String>();
12118
12119 let is_within_comment_override = buffer
12120 .language_scope_at(indent_end)
12121 .is_some_and(|scope| scope.override_name() == Some("comment"));
12122 let comment_delimiters = if is_within_comment_override {
12123 // we are within a comment syntax node, but we don't
12124 // yet know what kind of comment: block, doc or line
12125 match (
12126 language_scope.documentation_comment(),
12127 language_scope.block_comment(),
12128 ) {
12129 (Some(config), _) | (_, Some(config))
12130 if buffer.contains_str_at(indent_end, &config.start) =>
12131 {
12132 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12133 }
12134 (Some(config), _) | (_, Some(config))
12135 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12136 {
12137 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12138 }
12139 (Some(config), _) | (_, Some(config))
12140 if buffer.contains_str_at(indent_end, &config.prefix) =>
12141 {
12142 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12143 }
12144 (_, _) => language_scope
12145 .line_comment_prefixes()
12146 .iter()
12147 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12148 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12149 }
12150 } else {
12151 // we not in an overridden comment node, but we may
12152 // be within a non-overridden line comment node
12153 language_scope
12154 .line_comment_prefixes()
12155 .iter()
12156 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12157 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12158 };
12159
12160 let rewrap_prefix = language_scope
12161 .rewrap_prefixes()
12162 .iter()
12163 .find_map(|prefix_regex| {
12164 prefix_regex.find(&line_text_after_indent).map(|mat| {
12165 if mat.start() == 0 {
12166 Some(mat.as_str().to_string())
12167 } else {
12168 None
12169 }
12170 })
12171 })
12172 .flatten();
12173 (comment_delimiters, rewrap_prefix)
12174 } else {
12175 (None, None)
12176 };
12177 (indent, comment_prefix, rewrap_prefix)
12178 };
12179
12180 let mut ranges = Vec::new();
12181 let from_empty_selection = selection.is_empty();
12182
12183 let mut current_range_start = first_row;
12184 let mut prev_row = first_row;
12185 let (
12186 mut current_range_indent,
12187 mut current_range_comment_delimiters,
12188 mut current_range_rewrap_prefix,
12189 ) = indent_and_prefix_for_row(first_row);
12190
12191 for row in non_blank_rows_iter.skip(1) {
12192 let has_paragraph_break = row > prev_row + 1;
12193
12194 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12195 indent_and_prefix_for_row(row);
12196
12197 let has_indent_change = row_indent != current_range_indent;
12198 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12199
12200 let has_boundary_change = has_comment_change
12201 || row_rewrap_prefix.is_some()
12202 || (has_indent_change && current_range_comment_delimiters.is_some());
12203
12204 if has_paragraph_break || has_boundary_change {
12205 ranges.push((
12206 language_settings.clone(),
12207 Point::new(current_range_start, 0)
12208 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12209 current_range_indent,
12210 current_range_comment_delimiters.clone(),
12211 current_range_rewrap_prefix.clone(),
12212 from_empty_selection,
12213 ));
12214 current_range_start = row;
12215 current_range_indent = row_indent;
12216 current_range_comment_delimiters = row_comment_delimiters;
12217 current_range_rewrap_prefix = row_rewrap_prefix;
12218 }
12219 prev_row = row;
12220 }
12221
12222 ranges.push((
12223 language_settings.clone(),
12224 Point::new(current_range_start, 0)
12225 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12226 current_range_indent,
12227 current_range_comment_delimiters,
12228 current_range_rewrap_prefix,
12229 from_empty_selection,
12230 ));
12231
12232 ranges
12233 });
12234
12235 let mut edits = Vec::new();
12236 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12237
12238 for (
12239 language_settings,
12240 wrap_range,
12241 mut indent_size,
12242 comment_prefix,
12243 rewrap_prefix,
12244 from_empty_selection,
12245 ) in wrap_ranges
12246 {
12247 let mut start_row = wrap_range.start.row;
12248 let mut end_row = wrap_range.end.row;
12249
12250 // Skip selections that overlap with a range that has already been rewrapped.
12251 let selection_range = start_row..end_row;
12252 if rewrapped_row_ranges
12253 .iter()
12254 .any(|range| range.overlaps(&selection_range))
12255 {
12256 continue;
12257 }
12258
12259 let tab_size = language_settings.tab_size;
12260
12261 let (line_prefix, inside_comment) = match &comment_prefix {
12262 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12263 (Some(prefix.as_str()), true)
12264 }
12265 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12266 (Some(prefix.as_ref()), true)
12267 }
12268 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12269 start: _,
12270 end: _,
12271 prefix,
12272 tab_size,
12273 })) => {
12274 indent_size.len += tab_size;
12275 (Some(prefix.as_ref()), true)
12276 }
12277 None => (None, false),
12278 };
12279 let indent_prefix = indent_size.chars().collect::<String>();
12280 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12281
12282 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12283 RewrapBehavior::InComments => inside_comment,
12284 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12285 RewrapBehavior::Anywhere => true,
12286 };
12287
12288 let should_rewrap = options.override_language_settings
12289 || allow_rewrap_based_on_language
12290 || self.hard_wrap.is_some();
12291 if !should_rewrap {
12292 continue;
12293 }
12294
12295 if from_empty_selection {
12296 'expand_upwards: while start_row > 0 {
12297 let prev_row = start_row - 1;
12298 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12299 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12300 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12301 {
12302 start_row = prev_row;
12303 } else {
12304 break 'expand_upwards;
12305 }
12306 }
12307
12308 'expand_downwards: while end_row < buffer.max_point().row {
12309 let next_row = end_row + 1;
12310 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12311 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12312 && !buffer.is_line_blank(MultiBufferRow(next_row))
12313 {
12314 end_row = next_row;
12315 } else {
12316 break 'expand_downwards;
12317 }
12318 }
12319 }
12320
12321 let start = Point::new(start_row, 0);
12322 let start_offset = ToOffset::to_offset(&start, &buffer);
12323 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12324 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12325 let mut first_line_delimiter = None;
12326 let mut last_line_delimiter = None;
12327 let Some(lines_without_prefixes) = selection_text
12328 .lines()
12329 .enumerate()
12330 .map(|(ix, line)| {
12331 let line_trimmed = line.trim_start();
12332 if rewrap_prefix.is_some() && ix > 0 {
12333 Ok(line_trimmed)
12334 } else if let Some(
12335 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12336 start,
12337 prefix,
12338 end,
12339 tab_size,
12340 })
12341 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12342 start,
12343 prefix,
12344 end,
12345 tab_size,
12346 }),
12347 ) = &comment_prefix
12348 {
12349 let line_trimmed = line_trimmed
12350 .strip_prefix(start.as_ref())
12351 .map(|s| {
12352 let mut indent_size = indent_size;
12353 indent_size.len -= tab_size;
12354 let indent_prefix: String = indent_size.chars().collect();
12355 first_line_delimiter = Some((indent_prefix, start));
12356 s.trim_start()
12357 })
12358 .unwrap_or(line_trimmed);
12359 let line_trimmed = line_trimmed
12360 .strip_suffix(end.as_ref())
12361 .map(|s| {
12362 last_line_delimiter = Some(end);
12363 s.trim_end()
12364 })
12365 .unwrap_or(line_trimmed);
12366 let line_trimmed = line_trimmed
12367 .strip_prefix(prefix.as_ref())
12368 .unwrap_or(line_trimmed);
12369 Ok(line_trimmed)
12370 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12371 line_trimmed.strip_prefix(prefix).with_context(|| {
12372 format!("line did not start with prefix {prefix:?}: {line:?}")
12373 })
12374 } else {
12375 line_trimmed
12376 .strip_prefix(&line_prefix.trim_start())
12377 .with_context(|| {
12378 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12379 })
12380 }
12381 })
12382 .collect::<Result<Vec<_>, _>>()
12383 .log_err()
12384 else {
12385 continue;
12386 };
12387
12388 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12389 buffer
12390 .language_settings_at(Point::new(start_row, 0), cx)
12391 .preferred_line_length as usize
12392 });
12393
12394 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12395 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12396 } else {
12397 line_prefix.clone()
12398 };
12399
12400 let wrapped_text = {
12401 let mut wrapped_text = wrap_with_prefix(
12402 line_prefix,
12403 subsequent_lines_prefix,
12404 lines_without_prefixes.join("\n"),
12405 wrap_column,
12406 tab_size,
12407 options.preserve_existing_whitespace,
12408 );
12409
12410 if let Some((indent, delimiter)) = first_line_delimiter {
12411 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12412 }
12413 if let Some(last_line) = last_line_delimiter {
12414 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12415 }
12416
12417 wrapped_text
12418 };
12419
12420 // TODO: should always use char-based diff while still supporting cursor behavior that
12421 // matches vim.
12422 let mut diff_options = DiffOptions::default();
12423 if options.override_language_settings {
12424 diff_options.max_word_diff_len = 0;
12425 diff_options.max_word_diff_line_count = 0;
12426 } else {
12427 diff_options.max_word_diff_len = usize::MAX;
12428 diff_options.max_word_diff_line_count = usize::MAX;
12429 }
12430
12431 for (old_range, new_text) in
12432 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12433 {
12434 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12435 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12436 edits.push((edit_start..edit_end, new_text));
12437 }
12438
12439 rewrapped_row_ranges.push(start_row..=end_row);
12440 }
12441
12442 self.buffer
12443 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12444 }
12445
12446 pub fn cut_common(
12447 &mut self,
12448 cut_no_selection_line: bool,
12449 window: &mut Window,
12450 cx: &mut Context<Self>,
12451 ) -> ClipboardItem {
12452 let mut text = String::new();
12453 let buffer = self.buffer.read(cx).snapshot(cx);
12454 let mut selections = self.selections.all::<Point>(cx);
12455 let mut clipboard_selections = Vec::with_capacity(selections.len());
12456 {
12457 let max_point = buffer.max_point();
12458 let mut is_first = true;
12459 for selection in &mut selections {
12460 let is_entire_line =
12461 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12462 if is_entire_line {
12463 selection.start = Point::new(selection.start.row, 0);
12464 if !selection.is_empty() && selection.end.column == 0 {
12465 selection.end = cmp::min(max_point, selection.end);
12466 } else {
12467 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12468 }
12469 selection.goal = SelectionGoal::None;
12470 }
12471 if is_first {
12472 is_first = false;
12473 } else {
12474 text += "\n";
12475 }
12476 let mut len = 0;
12477 for chunk in buffer.text_for_range(selection.start..selection.end) {
12478 text.push_str(chunk);
12479 len += chunk.len();
12480 }
12481 clipboard_selections.push(ClipboardSelection {
12482 len,
12483 is_entire_line,
12484 first_line_indent: buffer
12485 .indent_size_for_line(MultiBufferRow(selection.start.row))
12486 .len,
12487 });
12488 }
12489 }
12490
12491 self.transact(window, cx, |this, window, cx| {
12492 this.change_selections(Default::default(), window, cx, |s| {
12493 s.select(selections);
12494 });
12495 this.insert("", window, cx);
12496 });
12497 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12498 }
12499
12500 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12501 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12502 let item = self.cut_common(true, window, cx);
12503 cx.write_to_clipboard(item);
12504 }
12505
12506 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12507 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12508 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12509 s.move_with(|snapshot, sel| {
12510 if sel.is_empty() {
12511 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12512 }
12513 if sel.is_empty() {
12514 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12515 }
12516 });
12517 });
12518 let item = self.cut_common(false, window, cx);
12519 cx.set_global(KillRing(item))
12520 }
12521
12522 pub fn kill_ring_yank(
12523 &mut self,
12524 _: &KillRingYank,
12525 window: &mut Window,
12526 cx: &mut Context<Self>,
12527 ) {
12528 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12529 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12530 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12531 (kill_ring.text().to_string(), kill_ring.metadata_json())
12532 } else {
12533 return;
12534 }
12535 } else {
12536 return;
12537 };
12538 self.do_paste(&text, metadata, false, window, cx);
12539 }
12540
12541 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12542 self.do_copy(true, cx);
12543 }
12544
12545 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12546 self.do_copy(false, cx);
12547 }
12548
12549 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12550 let selections = self.selections.all::<Point>(cx);
12551 let buffer = self.buffer.read(cx).read(cx);
12552 let mut text = String::new();
12553
12554 let mut clipboard_selections = Vec::with_capacity(selections.len());
12555 {
12556 let max_point = buffer.max_point();
12557 let mut is_first = true;
12558 for selection in &selections {
12559 let mut start = selection.start;
12560 let mut end = selection.end;
12561 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12562 let mut add_trailing_newline = false;
12563 if is_entire_line {
12564 start = Point::new(start.row, 0);
12565 let next_line_start = Point::new(end.row + 1, 0);
12566 if next_line_start <= max_point {
12567 end = next_line_start;
12568 } else {
12569 // We're on the last line without a trailing newline.
12570 // Copy to the end of the line and add a newline afterwards.
12571 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12572 add_trailing_newline = true;
12573 }
12574 }
12575
12576 let mut trimmed_selections = Vec::new();
12577 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12578 let row = MultiBufferRow(start.row);
12579 let first_indent = buffer.indent_size_for_line(row);
12580 if first_indent.len == 0 || start.column > first_indent.len {
12581 trimmed_selections.push(start..end);
12582 } else {
12583 trimmed_selections.push(
12584 Point::new(row.0, first_indent.len)
12585 ..Point::new(row.0, buffer.line_len(row)),
12586 );
12587 for row in start.row + 1..=end.row {
12588 let mut line_len = buffer.line_len(MultiBufferRow(row));
12589 if row == end.row {
12590 line_len = end.column;
12591 }
12592 if line_len == 0 {
12593 trimmed_selections
12594 .push(Point::new(row, 0)..Point::new(row, line_len));
12595 continue;
12596 }
12597 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12598 if row_indent_size.len >= first_indent.len {
12599 trimmed_selections.push(
12600 Point::new(row, first_indent.len)..Point::new(row, line_len),
12601 );
12602 } else {
12603 trimmed_selections.clear();
12604 trimmed_selections.push(start..end);
12605 break;
12606 }
12607 }
12608 }
12609 } else {
12610 trimmed_selections.push(start..end);
12611 }
12612
12613 for trimmed_range in trimmed_selections {
12614 if is_first {
12615 is_first = false;
12616 } else {
12617 text += "\n";
12618 }
12619 let mut len = 0;
12620 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12621 text.push_str(chunk);
12622 len += chunk.len();
12623 }
12624 if add_trailing_newline {
12625 text.push('\n');
12626 len += 1;
12627 }
12628 clipboard_selections.push(ClipboardSelection {
12629 len,
12630 is_entire_line,
12631 first_line_indent: buffer
12632 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12633 .len,
12634 });
12635 }
12636 }
12637 }
12638
12639 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12640 text,
12641 clipboard_selections,
12642 ));
12643 }
12644
12645 pub fn do_paste(
12646 &mut self,
12647 text: &String,
12648 clipboard_selections: Option<Vec<ClipboardSelection>>,
12649 handle_entire_lines: bool,
12650 window: &mut Window,
12651 cx: &mut Context<Self>,
12652 ) {
12653 if self.read_only(cx) {
12654 return;
12655 }
12656
12657 let clipboard_text = Cow::Borrowed(text.as_str());
12658
12659 self.transact(window, cx, |this, window, cx| {
12660 let had_active_edit_prediction = this.has_active_edit_prediction();
12661 let old_selections = this.selections.all::<usize>(cx);
12662 let cursor_offset = this.selections.last::<usize>(cx).head();
12663
12664 if let Some(mut clipboard_selections) = clipboard_selections {
12665 let all_selections_were_entire_line =
12666 clipboard_selections.iter().all(|s| s.is_entire_line);
12667 let first_selection_indent_column =
12668 clipboard_selections.first().map(|s| s.first_line_indent);
12669 if clipboard_selections.len() != old_selections.len() {
12670 clipboard_selections.drain(..);
12671 }
12672 let mut auto_indent_on_paste = true;
12673
12674 this.buffer.update(cx, |buffer, cx| {
12675 let snapshot = buffer.read(cx);
12676 auto_indent_on_paste = snapshot
12677 .language_settings_at(cursor_offset, cx)
12678 .auto_indent_on_paste;
12679
12680 let mut start_offset = 0;
12681 let mut edits = Vec::new();
12682 let mut original_indent_columns = Vec::new();
12683 for (ix, selection) in old_selections.iter().enumerate() {
12684 let to_insert;
12685 let entire_line;
12686 let original_indent_column;
12687 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12688 let end_offset = start_offset + clipboard_selection.len;
12689 to_insert = &clipboard_text[start_offset..end_offset];
12690 entire_line = clipboard_selection.is_entire_line;
12691 start_offset = end_offset + 1;
12692 original_indent_column = Some(clipboard_selection.first_line_indent);
12693 } else {
12694 to_insert = &*clipboard_text;
12695 entire_line = all_selections_were_entire_line;
12696 original_indent_column = first_selection_indent_column
12697 }
12698
12699 let (range, to_insert) =
12700 if selection.is_empty() && handle_entire_lines && entire_line {
12701 // If the corresponding selection was empty when this slice of the
12702 // clipboard text was written, then the entire line containing the
12703 // selection was copied. If this selection is also currently empty,
12704 // then paste the line before the current line of the buffer.
12705 let column = selection.start.to_point(&snapshot).column as usize;
12706 let line_start = selection.start - column;
12707 (line_start..line_start, Cow::Borrowed(to_insert))
12708 } else {
12709 let language = snapshot.language_at(selection.head());
12710 let range = selection.range();
12711 if let Some(language) = language
12712 && language.name() == "Markdown".into()
12713 {
12714 edit_for_markdown_paste(
12715 &snapshot,
12716 range,
12717 to_insert,
12718 url::Url::parse(to_insert).ok(),
12719 )
12720 } else {
12721 (range, Cow::Borrowed(to_insert))
12722 }
12723 };
12724
12725 edits.push((range, to_insert));
12726 original_indent_columns.push(original_indent_column);
12727 }
12728 drop(snapshot);
12729
12730 buffer.edit(
12731 edits,
12732 if auto_indent_on_paste {
12733 Some(AutoindentMode::Block {
12734 original_indent_columns,
12735 })
12736 } else {
12737 None
12738 },
12739 cx,
12740 );
12741 });
12742
12743 let selections = this.selections.all::<usize>(cx);
12744 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12745 } else {
12746 let url = url::Url::parse(&clipboard_text).ok();
12747
12748 let auto_indent_mode = if !clipboard_text.is_empty() {
12749 Some(AutoindentMode::Block {
12750 original_indent_columns: Vec::new(),
12751 })
12752 } else {
12753 None
12754 };
12755
12756 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12757 let snapshot = buffer.snapshot(cx);
12758
12759 let anchors = old_selections
12760 .iter()
12761 .map(|s| {
12762 let anchor = snapshot.anchor_after(s.head());
12763 s.map(|_| anchor)
12764 })
12765 .collect::<Vec<_>>();
12766
12767 let mut edits = Vec::new();
12768
12769 for selection in old_selections.iter() {
12770 let language = snapshot.language_at(selection.head());
12771 let range = selection.range();
12772
12773 let (edit_range, edit_text) = if let Some(language) = language
12774 && language.name() == "Markdown".into()
12775 {
12776 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12777 } else {
12778 (range, clipboard_text.clone())
12779 };
12780
12781 edits.push((edit_range, edit_text));
12782 }
12783
12784 drop(snapshot);
12785 buffer.edit(edits, auto_indent_mode, cx);
12786
12787 anchors
12788 });
12789
12790 this.change_selections(Default::default(), window, cx, |s| {
12791 s.select_anchors(selection_anchors);
12792 });
12793 }
12794
12795 let trigger_in_words =
12796 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12797
12798 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12799 });
12800 }
12801
12802 pub fn diff_clipboard_with_selection(
12803 &mut self,
12804 _: &DiffClipboardWithSelection,
12805 window: &mut Window,
12806 cx: &mut Context<Self>,
12807 ) {
12808 let selections = self.selections.all::<usize>(cx);
12809
12810 if selections.is_empty() {
12811 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12812 return;
12813 };
12814
12815 let clipboard_text = match cx.read_from_clipboard() {
12816 Some(item) => match item.entries().first() {
12817 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12818 _ => None,
12819 },
12820 None => None,
12821 };
12822
12823 let Some(clipboard_text) = clipboard_text else {
12824 log::warn!("Clipboard doesn't contain text.");
12825 return;
12826 };
12827
12828 window.dispatch_action(
12829 Box::new(DiffClipboardWithSelectionData {
12830 clipboard_text,
12831 editor: cx.entity(),
12832 }),
12833 cx,
12834 );
12835 }
12836
12837 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12838 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12839 if let Some(item) = cx.read_from_clipboard() {
12840 let entries = item.entries();
12841
12842 match entries.first() {
12843 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12844 // of all the pasted entries.
12845 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12846 .do_paste(
12847 clipboard_string.text(),
12848 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12849 true,
12850 window,
12851 cx,
12852 ),
12853 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12854 }
12855 }
12856 }
12857
12858 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12859 if self.read_only(cx) {
12860 return;
12861 }
12862
12863 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12864
12865 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12866 if let Some((selections, _)) =
12867 self.selection_history.transaction(transaction_id).cloned()
12868 {
12869 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12870 s.select_anchors(selections.to_vec());
12871 });
12872 } else {
12873 log::error!(
12874 "No entry in selection_history found for undo. \
12875 This may correspond to a bug where undo does not update the selection. \
12876 If this is occurring, please add details to \
12877 https://github.com/zed-industries/zed/issues/22692"
12878 );
12879 }
12880 self.request_autoscroll(Autoscroll::fit(), cx);
12881 self.unmark_text(window, cx);
12882 self.refresh_edit_prediction(true, false, window, cx);
12883 cx.emit(EditorEvent::Edited { transaction_id });
12884 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12885 }
12886 }
12887
12888 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12889 if self.read_only(cx) {
12890 return;
12891 }
12892
12893 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12894
12895 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12896 if let Some((_, Some(selections))) =
12897 self.selection_history.transaction(transaction_id).cloned()
12898 {
12899 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12900 s.select_anchors(selections.to_vec());
12901 });
12902 } else {
12903 log::error!(
12904 "No entry in selection_history found for redo. \
12905 This may correspond to a bug where undo does not update the selection. \
12906 If this is occurring, please add details to \
12907 https://github.com/zed-industries/zed/issues/22692"
12908 );
12909 }
12910 self.request_autoscroll(Autoscroll::fit(), cx);
12911 self.unmark_text(window, cx);
12912 self.refresh_edit_prediction(true, false, window, cx);
12913 cx.emit(EditorEvent::Edited { transaction_id });
12914 }
12915 }
12916
12917 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12918 self.buffer
12919 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12920 }
12921
12922 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12923 self.buffer
12924 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12925 }
12926
12927 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12928 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12929 self.change_selections(Default::default(), window, cx, |s| {
12930 s.move_with(|map, selection| {
12931 let cursor = if selection.is_empty() {
12932 movement::left(map, selection.start)
12933 } else {
12934 selection.start
12935 };
12936 selection.collapse_to(cursor, SelectionGoal::None);
12937 });
12938 })
12939 }
12940
12941 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12942 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12943 self.change_selections(Default::default(), window, cx, |s| {
12944 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12945 })
12946 }
12947
12948 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12949 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12950 self.change_selections(Default::default(), window, cx, |s| {
12951 s.move_with(|map, selection| {
12952 let cursor = if selection.is_empty() {
12953 movement::right(map, selection.end)
12954 } else {
12955 selection.end
12956 };
12957 selection.collapse_to(cursor, SelectionGoal::None)
12958 });
12959 })
12960 }
12961
12962 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12963 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12964 self.change_selections(Default::default(), window, cx, |s| {
12965 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12966 });
12967 }
12968
12969 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12970 if self.take_rename(true, window, cx).is_some() {
12971 return;
12972 }
12973
12974 if self.mode.is_single_line() {
12975 cx.propagate();
12976 return;
12977 }
12978
12979 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12980
12981 let text_layout_details = &self.text_layout_details(window);
12982 let selection_count = self.selections.count();
12983 let first_selection = self.selections.first_anchor();
12984
12985 self.change_selections(Default::default(), window, cx, |s| {
12986 s.move_with(|map, selection| {
12987 if !selection.is_empty() {
12988 selection.goal = SelectionGoal::None;
12989 }
12990 let (cursor, goal) = movement::up(
12991 map,
12992 selection.start,
12993 selection.goal,
12994 false,
12995 text_layout_details,
12996 );
12997 selection.collapse_to(cursor, goal);
12998 });
12999 });
13000
13001 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13002 {
13003 cx.propagate();
13004 }
13005 }
13006
13007 pub fn move_up_by_lines(
13008 &mut self,
13009 action: &MoveUpByLines,
13010 window: &mut Window,
13011 cx: &mut Context<Self>,
13012 ) {
13013 if self.take_rename(true, window, cx).is_some() {
13014 return;
13015 }
13016
13017 if self.mode.is_single_line() {
13018 cx.propagate();
13019 return;
13020 }
13021
13022 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13023
13024 let text_layout_details = &self.text_layout_details(window);
13025
13026 self.change_selections(Default::default(), window, cx, |s| {
13027 s.move_with(|map, selection| {
13028 if !selection.is_empty() {
13029 selection.goal = SelectionGoal::None;
13030 }
13031 let (cursor, goal) = movement::up_by_rows(
13032 map,
13033 selection.start,
13034 action.lines,
13035 selection.goal,
13036 false,
13037 text_layout_details,
13038 );
13039 selection.collapse_to(cursor, goal);
13040 });
13041 })
13042 }
13043
13044 pub fn move_down_by_lines(
13045 &mut self,
13046 action: &MoveDownByLines,
13047 window: &mut Window,
13048 cx: &mut Context<Self>,
13049 ) {
13050 if self.take_rename(true, window, cx).is_some() {
13051 return;
13052 }
13053
13054 if self.mode.is_single_line() {
13055 cx.propagate();
13056 return;
13057 }
13058
13059 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13060
13061 let text_layout_details = &self.text_layout_details(window);
13062
13063 self.change_selections(Default::default(), window, cx, |s| {
13064 s.move_with(|map, selection| {
13065 if !selection.is_empty() {
13066 selection.goal = SelectionGoal::None;
13067 }
13068 let (cursor, goal) = movement::down_by_rows(
13069 map,
13070 selection.start,
13071 action.lines,
13072 selection.goal,
13073 false,
13074 text_layout_details,
13075 );
13076 selection.collapse_to(cursor, goal);
13077 });
13078 })
13079 }
13080
13081 pub fn select_down_by_lines(
13082 &mut self,
13083 action: &SelectDownByLines,
13084 window: &mut Window,
13085 cx: &mut Context<Self>,
13086 ) {
13087 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13088 let text_layout_details = &self.text_layout_details(window);
13089 self.change_selections(Default::default(), window, cx, |s| {
13090 s.move_heads_with(|map, head, goal| {
13091 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13092 })
13093 })
13094 }
13095
13096 pub fn select_up_by_lines(
13097 &mut self,
13098 action: &SelectUpByLines,
13099 window: &mut Window,
13100 cx: &mut Context<Self>,
13101 ) {
13102 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13103 let text_layout_details = &self.text_layout_details(window);
13104 self.change_selections(Default::default(), window, cx, |s| {
13105 s.move_heads_with(|map, head, goal| {
13106 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13107 })
13108 })
13109 }
13110
13111 pub fn select_page_up(
13112 &mut self,
13113 _: &SelectPageUp,
13114 window: &mut Window,
13115 cx: &mut Context<Self>,
13116 ) {
13117 let Some(row_count) = self.visible_row_count() else {
13118 return;
13119 };
13120
13121 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13122
13123 let text_layout_details = &self.text_layout_details(window);
13124
13125 self.change_selections(Default::default(), window, cx, |s| {
13126 s.move_heads_with(|map, head, goal| {
13127 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13128 })
13129 })
13130 }
13131
13132 pub fn move_page_up(
13133 &mut self,
13134 action: &MovePageUp,
13135 window: &mut Window,
13136 cx: &mut Context<Self>,
13137 ) {
13138 if self.take_rename(true, window, cx).is_some() {
13139 return;
13140 }
13141
13142 if self
13143 .context_menu
13144 .borrow_mut()
13145 .as_mut()
13146 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13147 .unwrap_or(false)
13148 {
13149 return;
13150 }
13151
13152 if matches!(self.mode, EditorMode::SingleLine) {
13153 cx.propagate();
13154 return;
13155 }
13156
13157 let Some(row_count) = self.visible_row_count() else {
13158 return;
13159 };
13160
13161 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13162
13163 let effects = if action.center_cursor {
13164 SelectionEffects::scroll(Autoscroll::center())
13165 } else {
13166 SelectionEffects::default()
13167 };
13168
13169 let text_layout_details = &self.text_layout_details(window);
13170
13171 self.change_selections(effects, window, cx, |s| {
13172 s.move_with(|map, selection| {
13173 if !selection.is_empty() {
13174 selection.goal = SelectionGoal::None;
13175 }
13176 let (cursor, goal) = movement::up_by_rows(
13177 map,
13178 selection.end,
13179 row_count,
13180 selection.goal,
13181 false,
13182 text_layout_details,
13183 );
13184 selection.collapse_to(cursor, goal);
13185 });
13186 });
13187 }
13188
13189 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13190 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13191 let text_layout_details = &self.text_layout_details(window);
13192 self.change_selections(Default::default(), window, cx, |s| {
13193 s.move_heads_with(|map, head, goal| {
13194 movement::up(map, head, goal, false, text_layout_details)
13195 })
13196 })
13197 }
13198
13199 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13200 self.take_rename(true, window, cx);
13201
13202 if self.mode.is_single_line() {
13203 cx.propagate();
13204 return;
13205 }
13206
13207 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13208
13209 let text_layout_details = &self.text_layout_details(window);
13210 let selection_count = self.selections.count();
13211 let first_selection = self.selections.first_anchor();
13212
13213 self.change_selections(Default::default(), window, cx, |s| {
13214 s.move_with(|map, selection| {
13215 if !selection.is_empty() {
13216 selection.goal = SelectionGoal::None;
13217 }
13218 let (cursor, goal) = movement::down(
13219 map,
13220 selection.end,
13221 selection.goal,
13222 false,
13223 text_layout_details,
13224 );
13225 selection.collapse_to(cursor, goal);
13226 });
13227 });
13228
13229 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13230 {
13231 cx.propagate();
13232 }
13233 }
13234
13235 pub fn select_page_down(
13236 &mut self,
13237 _: &SelectPageDown,
13238 window: &mut Window,
13239 cx: &mut Context<Self>,
13240 ) {
13241 let Some(row_count) = self.visible_row_count() else {
13242 return;
13243 };
13244
13245 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13246
13247 let text_layout_details = &self.text_layout_details(window);
13248
13249 self.change_selections(Default::default(), window, cx, |s| {
13250 s.move_heads_with(|map, head, goal| {
13251 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13252 })
13253 })
13254 }
13255
13256 pub fn move_page_down(
13257 &mut self,
13258 action: &MovePageDown,
13259 window: &mut Window,
13260 cx: &mut Context<Self>,
13261 ) {
13262 if self.take_rename(true, window, cx).is_some() {
13263 return;
13264 }
13265
13266 if self
13267 .context_menu
13268 .borrow_mut()
13269 .as_mut()
13270 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13271 .unwrap_or(false)
13272 {
13273 return;
13274 }
13275
13276 if matches!(self.mode, EditorMode::SingleLine) {
13277 cx.propagate();
13278 return;
13279 }
13280
13281 let Some(row_count) = self.visible_row_count() else {
13282 return;
13283 };
13284
13285 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13286
13287 let effects = if action.center_cursor {
13288 SelectionEffects::scroll(Autoscroll::center())
13289 } else {
13290 SelectionEffects::default()
13291 };
13292
13293 let text_layout_details = &self.text_layout_details(window);
13294 self.change_selections(effects, window, cx, |s| {
13295 s.move_with(|map, selection| {
13296 if !selection.is_empty() {
13297 selection.goal = SelectionGoal::None;
13298 }
13299 let (cursor, goal) = movement::down_by_rows(
13300 map,
13301 selection.end,
13302 row_count,
13303 selection.goal,
13304 false,
13305 text_layout_details,
13306 );
13307 selection.collapse_to(cursor, goal);
13308 });
13309 });
13310 }
13311
13312 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13313 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13314 let text_layout_details = &self.text_layout_details(window);
13315 self.change_selections(Default::default(), window, cx, |s| {
13316 s.move_heads_with(|map, head, goal| {
13317 movement::down(map, head, goal, false, text_layout_details)
13318 })
13319 });
13320 }
13321
13322 pub fn context_menu_first(
13323 &mut self,
13324 _: &ContextMenuFirst,
13325 window: &mut Window,
13326 cx: &mut Context<Self>,
13327 ) {
13328 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13329 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13330 }
13331 }
13332
13333 pub fn context_menu_prev(
13334 &mut self,
13335 _: &ContextMenuPrevious,
13336 window: &mut Window,
13337 cx: &mut Context<Self>,
13338 ) {
13339 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13340 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13341 }
13342 }
13343
13344 pub fn context_menu_next(
13345 &mut self,
13346 _: &ContextMenuNext,
13347 window: &mut Window,
13348 cx: &mut Context<Self>,
13349 ) {
13350 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13351 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13352 }
13353 }
13354
13355 pub fn context_menu_last(
13356 &mut self,
13357 _: &ContextMenuLast,
13358 window: &mut Window,
13359 cx: &mut Context<Self>,
13360 ) {
13361 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13362 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13363 }
13364 }
13365
13366 pub fn signature_help_prev(
13367 &mut self,
13368 _: &SignatureHelpPrevious,
13369 _: &mut Window,
13370 cx: &mut Context<Self>,
13371 ) {
13372 if let Some(popover) = self.signature_help_state.popover_mut() {
13373 if popover.current_signature == 0 {
13374 popover.current_signature = popover.signatures.len() - 1;
13375 } else {
13376 popover.current_signature -= 1;
13377 }
13378 cx.notify();
13379 }
13380 }
13381
13382 pub fn signature_help_next(
13383 &mut self,
13384 _: &SignatureHelpNext,
13385 _: &mut Window,
13386 cx: &mut Context<Self>,
13387 ) {
13388 if let Some(popover) = self.signature_help_state.popover_mut() {
13389 if popover.current_signature + 1 == popover.signatures.len() {
13390 popover.current_signature = 0;
13391 } else {
13392 popover.current_signature += 1;
13393 }
13394 cx.notify();
13395 }
13396 }
13397
13398 pub fn move_to_previous_word_start(
13399 &mut self,
13400 _: &MoveToPreviousWordStart,
13401 window: &mut Window,
13402 cx: &mut Context<Self>,
13403 ) {
13404 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13405 self.change_selections(Default::default(), window, cx, |s| {
13406 s.move_cursors_with(|map, head, _| {
13407 (
13408 movement::previous_word_start(map, head),
13409 SelectionGoal::None,
13410 )
13411 });
13412 })
13413 }
13414
13415 pub fn move_to_previous_subword_start(
13416 &mut self,
13417 _: &MoveToPreviousSubwordStart,
13418 window: &mut Window,
13419 cx: &mut Context<Self>,
13420 ) {
13421 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13422 self.change_selections(Default::default(), window, cx, |s| {
13423 s.move_cursors_with(|map, head, _| {
13424 (
13425 movement::previous_subword_start(map, head),
13426 SelectionGoal::None,
13427 )
13428 });
13429 })
13430 }
13431
13432 pub fn select_to_previous_word_start(
13433 &mut self,
13434 _: &SelectToPreviousWordStart,
13435 window: &mut Window,
13436 cx: &mut Context<Self>,
13437 ) {
13438 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13439 self.change_selections(Default::default(), window, cx, |s| {
13440 s.move_heads_with(|map, head, _| {
13441 (
13442 movement::previous_word_start(map, head),
13443 SelectionGoal::None,
13444 )
13445 });
13446 })
13447 }
13448
13449 pub fn select_to_previous_subword_start(
13450 &mut self,
13451 _: &SelectToPreviousSubwordStart,
13452 window: &mut Window,
13453 cx: &mut Context<Self>,
13454 ) {
13455 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13456 self.change_selections(Default::default(), window, cx, |s| {
13457 s.move_heads_with(|map, head, _| {
13458 (
13459 movement::previous_subword_start(map, head),
13460 SelectionGoal::None,
13461 )
13462 });
13463 })
13464 }
13465
13466 pub fn delete_to_previous_word_start(
13467 &mut self,
13468 action: &DeleteToPreviousWordStart,
13469 window: &mut Window,
13470 cx: &mut Context<Self>,
13471 ) {
13472 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13473 self.transact(window, cx, |this, window, cx| {
13474 this.select_autoclose_pair(window, cx);
13475 this.change_selections(Default::default(), window, cx, |s| {
13476 s.move_with(|map, selection| {
13477 if selection.is_empty() {
13478 let mut cursor = if action.ignore_newlines {
13479 movement::previous_word_start(map, selection.head())
13480 } else {
13481 movement::previous_word_start_or_newline(map, selection.head())
13482 };
13483 cursor = movement::adjust_greedy_deletion(
13484 map,
13485 selection.head(),
13486 cursor,
13487 action.ignore_brackets,
13488 );
13489 selection.set_head(cursor, SelectionGoal::None);
13490 }
13491 });
13492 });
13493 this.insert("", window, cx);
13494 });
13495 }
13496
13497 pub fn delete_to_previous_subword_start(
13498 &mut self,
13499 _: &DeleteToPreviousSubwordStart,
13500 window: &mut Window,
13501 cx: &mut Context<Self>,
13502 ) {
13503 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13504 self.transact(window, cx, |this, window, cx| {
13505 this.select_autoclose_pair(window, cx);
13506 this.change_selections(Default::default(), window, cx, |s| {
13507 s.move_with(|map, selection| {
13508 if selection.is_empty() {
13509 let mut cursor = movement::previous_subword_start(map, selection.head());
13510 cursor =
13511 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13512 selection.set_head(cursor, SelectionGoal::None);
13513 }
13514 });
13515 });
13516 this.insert("", window, cx);
13517 });
13518 }
13519
13520 pub fn move_to_next_word_end(
13521 &mut self,
13522 _: &MoveToNextWordEnd,
13523 window: &mut Window,
13524 cx: &mut Context<Self>,
13525 ) {
13526 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13527 self.change_selections(Default::default(), window, cx, |s| {
13528 s.move_cursors_with(|map, head, _| {
13529 (movement::next_word_end(map, head), SelectionGoal::None)
13530 });
13531 })
13532 }
13533
13534 pub fn move_to_next_subword_end(
13535 &mut self,
13536 _: &MoveToNextSubwordEnd,
13537 window: &mut Window,
13538 cx: &mut Context<Self>,
13539 ) {
13540 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13541 self.change_selections(Default::default(), window, cx, |s| {
13542 s.move_cursors_with(|map, head, _| {
13543 (movement::next_subword_end(map, head), SelectionGoal::None)
13544 });
13545 })
13546 }
13547
13548 pub fn select_to_next_word_end(
13549 &mut self,
13550 _: &SelectToNextWordEnd,
13551 window: &mut Window,
13552 cx: &mut Context<Self>,
13553 ) {
13554 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13555 self.change_selections(Default::default(), window, cx, |s| {
13556 s.move_heads_with(|map, head, _| {
13557 (movement::next_word_end(map, head), SelectionGoal::None)
13558 });
13559 })
13560 }
13561
13562 pub fn select_to_next_subword_end(
13563 &mut self,
13564 _: &SelectToNextSubwordEnd,
13565 window: &mut Window,
13566 cx: &mut Context<Self>,
13567 ) {
13568 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13569 self.change_selections(Default::default(), window, cx, |s| {
13570 s.move_heads_with(|map, head, _| {
13571 (movement::next_subword_end(map, head), SelectionGoal::None)
13572 });
13573 })
13574 }
13575
13576 pub fn delete_to_next_word_end(
13577 &mut self,
13578 action: &DeleteToNextWordEnd,
13579 window: &mut Window,
13580 cx: &mut Context<Self>,
13581 ) {
13582 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13583 self.transact(window, cx, |this, window, cx| {
13584 this.change_selections(Default::default(), window, cx, |s| {
13585 s.move_with(|map, selection| {
13586 if selection.is_empty() {
13587 let mut cursor = if action.ignore_newlines {
13588 movement::next_word_end(map, selection.head())
13589 } else {
13590 movement::next_word_end_or_newline(map, selection.head())
13591 };
13592 cursor = movement::adjust_greedy_deletion(
13593 map,
13594 selection.head(),
13595 cursor,
13596 action.ignore_brackets,
13597 );
13598 selection.set_head(cursor, SelectionGoal::None);
13599 }
13600 });
13601 });
13602 this.insert("", window, cx);
13603 });
13604 }
13605
13606 pub fn delete_to_next_subword_end(
13607 &mut self,
13608 _: &DeleteToNextSubwordEnd,
13609 window: &mut Window,
13610 cx: &mut Context<Self>,
13611 ) {
13612 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13613 self.transact(window, cx, |this, window, cx| {
13614 this.change_selections(Default::default(), window, cx, |s| {
13615 s.move_with(|map, selection| {
13616 if selection.is_empty() {
13617 let mut cursor = movement::next_subword_end(map, selection.head());
13618 cursor =
13619 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13620 selection.set_head(cursor, SelectionGoal::None);
13621 }
13622 });
13623 });
13624 this.insert("", window, cx);
13625 });
13626 }
13627
13628 pub fn move_to_beginning_of_line(
13629 &mut self,
13630 action: &MoveToBeginningOfLine,
13631 window: &mut Window,
13632 cx: &mut Context<Self>,
13633 ) {
13634 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13635 self.change_selections(Default::default(), window, cx, |s| {
13636 s.move_cursors_with(|map, head, _| {
13637 (
13638 movement::indented_line_beginning(
13639 map,
13640 head,
13641 action.stop_at_soft_wraps,
13642 action.stop_at_indent,
13643 ),
13644 SelectionGoal::None,
13645 )
13646 });
13647 })
13648 }
13649
13650 pub fn select_to_beginning_of_line(
13651 &mut self,
13652 action: &SelectToBeginningOfLine,
13653 window: &mut Window,
13654 cx: &mut Context<Self>,
13655 ) {
13656 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13657 self.change_selections(Default::default(), window, cx, |s| {
13658 s.move_heads_with(|map, head, _| {
13659 (
13660 movement::indented_line_beginning(
13661 map,
13662 head,
13663 action.stop_at_soft_wraps,
13664 action.stop_at_indent,
13665 ),
13666 SelectionGoal::None,
13667 )
13668 });
13669 });
13670 }
13671
13672 pub fn delete_to_beginning_of_line(
13673 &mut self,
13674 action: &DeleteToBeginningOfLine,
13675 window: &mut Window,
13676 cx: &mut Context<Self>,
13677 ) {
13678 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13679 self.transact(window, cx, |this, window, cx| {
13680 this.change_selections(Default::default(), window, cx, |s| {
13681 s.move_with(|_, selection| {
13682 selection.reversed = true;
13683 });
13684 });
13685
13686 this.select_to_beginning_of_line(
13687 &SelectToBeginningOfLine {
13688 stop_at_soft_wraps: false,
13689 stop_at_indent: action.stop_at_indent,
13690 },
13691 window,
13692 cx,
13693 );
13694 this.backspace(&Backspace, window, cx);
13695 });
13696 }
13697
13698 pub fn move_to_end_of_line(
13699 &mut self,
13700 action: &MoveToEndOfLine,
13701 window: &mut Window,
13702 cx: &mut Context<Self>,
13703 ) {
13704 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13705 self.change_selections(Default::default(), window, cx, |s| {
13706 s.move_cursors_with(|map, head, _| {
13707 (
13708 movement::line_end(map, head, action.stop_at_soft_wraps),
13709 SelectionGoal::None,
13710 )
13711 });
13712 })
13713 }
13714
13715 pub fn select_to_end_of_line(
13716 &mut self,
13717 action: &SelectToEndOfLine,
13718 window: &mut Window,
13719 cx: &mut Context<Self>,
13720 ) {
13721 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13722 self.change_selections(Default::default(), window, cx, |s| {
13723 s.move_heads_with(|map, head, _| {
13724 (
13725 movement::line_end(map, head, action.stop_at_soft_wraps),
13726 SelectionGoal::None,
13727 )
13728 });
13729 })
13730 }
13731
13732 pub fn delete_to_end_of_line(
13733 &mut self,
13734 _: &DeleteToEndOfLine,
13735 window: &mut Window,
13736 cx: &mut Context<Self>,
13737 ) {
13738 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13739 self.transact(window, cx, |this, window, cx| {
13740 this.select_to_end_of_line(
13741 &SelectToEndOfLine {
13742 stop_at_soft_wraps: false,
13743 },
13744 window,
13745 cx,
13746 );
13747 this.delete(&Delete, window, cx);
13748 });
13749 }
13750
13751 pub fn cut_to_end_of_line(
13752 &mut self,
13753 action: &CutToEndOfLine,
13754 window: &mut Window,
13755 cx: &mut Context<Self>,
13756 ) {
13757 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13758 self.transact(window, cx, |this, window, cx| {
13759 this.select_to_end_of_line(
13760 &SelectToEndOfLine {
13761 stop_at_soft_wraps: false,
13762 },
13763 window,
13764 cx,
13765 );
13766 if !action.stop_at_newlines {
13767 this.change_selections(Default::default(), window, cx, |s| {
13768 s.move_with(|_, sel| {
13769 if sel.is_empty() {
13770 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13771 }
13772 });
13773 });
13774 }
13775 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13776 let item = this.cut_common(false, window, cx);
13777 cx.write_to_clipboard(item);
13778 });
13779 }
13780
13781 pub fn move_to_start_of_paragraph(
13782 &mut self,
13783 _: &MoveToStartOfParagraph,
13784 window: &mut Window,
13785 cx: &mut Context<Self>,
13786 ) {
13787 if matches!(self.mode, EditorMode::SingleLine) {
13788 cx.propagate();
13789 return;
13790 }
13791 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13792 self.change_selections(Default::default(), window, cx, |s| {
13793 s.move_with(|map, selection| {
13794 selection.collapse_to(
13795 movement::start_of_paragraph(map, selection.head(), 1),
13796 SelectionGoal::None,
13797 )
13798 });
13799 })
13800 }
13801
13802 pub fn move_to_end_of_paragraph(
13803 &mut self,
13804 _: &MoveToEndOfParagraph,
13805 window: &mut Window,
13806 cx: &mut Context<Self>,
13807 ) {
13808 if matches!(self.mode, EditorMode::SingleLine) {
13809 cx.propagate();
13810 return;
13811 }
13812 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13813 self.change_selections(Default::default(), window, cx, |s| {
13814 s.move_with(|map, selection| {
13815 selection.collapse_to(
13816 movement::end_of_paragraph(map, selection.head(), 1),
13817 SelectionGoal::None,
13818 )
13819 });
13820 })
13821 }
13822
13823 pub fn select_to_start_of_paragraph(
13824 &mut self,
13825 _: &SelectToStartOfParagraph,
13826 window: &mut Window,
13827 cx: &mut Context<Self>,
13828 ) {
13829 if matches!(self.mode, EditorMode::SingleLine) {
13830 cx.propagate();
13831 return;
13832 }
13833 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13834 self.change_selections(Default::default(), window, cx, |s| {
13835 s.move_heads_with(|map, head, _| {
13836 (
13837 movement::start_of_paragraph(map, head, 1),
13838 SelectionGoal::None,
13839 )
13840 });
13841 })
13842 }
13843
13844 pub fn select_to_end_of_paragraph(
13845 &mut self,
13846 _: &SelectToEndOfParagraph,
13847 window: &mut Window,
13848 cx: &mut Context<Self>,
13849 ) {
13850 if matches!(self.mode, EditorMode::SingleLine) {
13851 cx.propagate();
13852 return;
13853 }
13854 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13855 self.change_selections(Default::default(), window, cx, |s| {
13856 s.move_heads_with(|map, head, _| {
13857 (
13858 movement::end_of_paragraph(map, head, 1),
13859 SelectionGoal::None,
13860 )
13861 });
13862 })
13863 }
13864
13865 pub fn move_to_start_of_excerpt(
13866 &mut self,
13867 _: &MoveToStartOfExcerpt,
13868 window: &mut Window,
13869 cx: &mut Context<Self>,
13870 ) {
13871 if matches!(self.mode, EditorMode::SingleLine) {
13872 cx.propagate();
13873 return;
13874 }
13875 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13876 self.change_selections(Default::default(), window, cx, |s| {
13877 s.move_with(|map, selection| {
13878 selection.collapse_to(
13879 movement::start_of_excerpt(
13880 map,
13881 selection.head(),
13882 workspace::searchable::Direction::Prev,
13883 ),
13884 SelectionGoal::None,
13885 )
13886 });
13887 })
13888 }
13889
13890 pub fn move_to_start_of_next_excerpt(
13891 &mut self,
13892 _: &MoveToStartOfNextExcerpt,
13893 window: &mut Window,
13894 cx: &mut Context<Self>,
13895 ) {
13896 if matches!(self.mode, EditorMode::SingleLine) {
13897 cx.propagate();
13898 return;
13899 }
13900
13901 self.change_selections(Default::default(), window, cx, |s| {
13902 s.move_with(|map, selection| {
13903 selection.collapse_to(
13904 movement::start_of_excerpt(
13905 map,
13906 selection.head(),
13907 workspace::searchable::Direction::Next,
13908 ),
13909 SelectionGoal::None,
13910 )
13911 });
13912 })
13913 }
13914
13915 pub fn move_to_end_of_excerpt(
13916 &mut self,
13917 _: &MoveToEndOfExcerpt,
13918 window: &mut Window,
13919 cx: &mut Context<Self>,
13920 ) {
13921 if matches!(self.mode, EditorMode::SingleLine) {
13922 cx.propagate();
13923 return;
13924 }
13925 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13926 self.change_selections(Default::default(), window, cx, |s| {
13927 s.move_with(|map, selection| {
13928 selection.collapse_to(
13929 movement::end_of_excerpt(
13930 map,
13931 selection.head(),
13932 workspace::searchable::Direction::Next,
13933 ),
13934 SelectionGoal::None,
13935 )
13936 });
13937 })
13938 }
13939
13940 pub fn move_to_end_of_previous_excerpt(
13941 &mut self,
13942 _: &MoveToEndOfPreviousExcerpt,
13943 window: &mut Window,
13944 cx: &mut Context<Self>,
13945 ) {
13946 if matches!(self.mode, EditorMode::SingleLine) {
13947 cx.propagate();
13948 return;
13949 }
13950 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13951 self.change_selections(Default::default(), window, cx, |s| {
13952 s.move_with(|map, selection| {
13953 selection.collapse_to(
13954 movement::end_of_excerpt(
13955 map,
13956 selection.head(),
13957 workspace::searchable::Direction::Prev,
13958 ),
13959 SelectionGoal::None,
13960 )
13961 });
13962 })
13963 }
13964
13965 pub fn select_to_start_of_excerpt(
13966 &mut self,
13967 _: &SelectToStartOfExcerpt,
13968 window: &mut Window,
13969 cx: &mut Context<Self>,
13970 ) {
13971 if matches!(self.mode, EditorMode::SingleLine) {
13972 cx.propagate();
13973 return;
13974 }
13975 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13976 self.change_selections(Default::default(), window, cx, |s| {
13977 s.move_heads_with(|map, head, _| {
13978 (
13979 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13980 SelectionGoal::None,
13981 )
13982 });
13983 })
13984 }
13985
13986 pub fn select_to_start_of_next_excerpt(
13987 &mut self,
13988 _: &SelectToStartOfNextExcerpt,
13989 window: &mut Window,
13990 cx: &mut Context<Self>,
13991 ) {
13992 if matches!(self.mode, EditorMode::SingleLine) {
13993 cx.propagate();
13994 return;
13995 }
13996 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13997 self.change_selections(Default::default(), window, cx, |s| {
13998 s.move_heads_with(|map, head, _| {
13999 (
14000 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14001 SelectionGoal::None,
14002 )
14003 });
14004 })
14005 }
14006
14007 pub fn select_to_end_of_excerpt(
14008 &mut self,
14009 _: &SelectToEndOfExcerpt,
14010 window: &mut Window,
14011 cx: &mut Context<Self>,
14012 ) {
14013 if matches!(self.mode, EditorMode::SingleLine) {
14014 cx.propagate();
14015 return;
14016 }
14017 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14018 self.change_selections(Default::default(), window, cx, |s| {
14019 s.move_heads_with(|map, head, _| {
14020 (
14021 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14022 SelectionGoal::None,
14023 )
14024 });
14025 })
14026 }
14027
14028 pub fn select_to_end_of_previous_excerpt(
14029 &mut self,
14030 _: &SelectToEndOfPreviousExcerpt,
14031 window: &mut Window,
14032 cx: &mut Context<Self>,
14033 ) {
14034 if matches!(self.mode, EditorMode::SingleLine) {
14035 cx.propagate();
14036 return;
14037 }
14038 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14039 self.change_selections(Default::default(), window, cx, |s| {
14040 s.move_heads_with(|map, head, _| {
14041 (
14042 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14043 SelectionGoal::None,
14044 )
14045 });
14046 })
14047 }
14048
14049 pub fn move_to_beginning(
14050 &mut self,
14051 _: &MoveToBeginning,
14052 window: &mut Window,
14053 cx: &mut Context<Self>,
14054 ) {
14055 if matches!(self.mode, EditorMode::SingleLine) {
14056 cx.propagate();
14057 return;
14058 }
14059 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14060 self.change_selections(Default::default(), window, cx, |s| {
14061 s.select_ranges(vec![0..0]);
14062 });
14063 }
14064
14065 pub fn select_to_beginning(
14066 &mut self,
14067 _: &SelectToBeginning,
14068 window: &mut Window,
14069 cx: &mut Context<Self>,
14070 ) {
14071 let mut selection = self.selections.last::<Point>(cx);
14072 selection.set_head(Point::zero(), SelectionGoal::None);
14073 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14074 self.change_selections(Default::default(), window, cx, |s| {
14075 s.select(vec![selection]);
14076 });
14077 }
14078
14079 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14080 if matches!(self.mode, EditorMode::SingleLine) {
14081 cx.propagate();
14082 return;
14083 }
14084 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14085 let cursor = self.buffer.read(cx).read(cx).len();
14086 self.change_selections(Default::default(), window, cx, |s| {
14087 s.select_ranges(vec![cursor..cursor])
14088 });
14089 }
14090
14091 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14092 self.nav_history = nav_history;
14093 }
14094
14095 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14096 self.nav_history.as_ref()
14097 }
14098
14099 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14100 self.push_to_nav_history(
14101 self.selections.newest_anchor().head(),
14102 None,
14103 false,
14104 true,
14105 cx,
14106 );
14107 }
14108
14109 fn push_to_nav_history(
14110 &mut self,
14111 cursor_anchor: Anchor,
14112 new_position: Option<Point>,
14113 is_deactivate: bool,
14114 always: bool,
14115 cx: &mut Context<Self>,
14116 ) {
14117 if let Some(nav_history) = self.nav_history.as_mut() {
14118 let buffer = self.buffer.read(cx).read(cx);
14119 let cursor_position = cursor_anchor.to_point(&buffer);
14120 let scroll_state = self.scroll_manager.anchor();
14121 let scroll_top_row = scroll_state.top_row(&buffer);
14122 drop(buffer);
14123
14124 if let Some(new_position) = new_position {
14125 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14126 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14127 return;
14128 }
14129 }
14130
14131 nav_history.push(
14132 Some(NavigationData {
14133 cursor_anchor,
14134 cursor_position,
14135 scroll_anchor: scroll_state,
14136 scroll_top_row,
14137 }),
14138 cx,
14139 );
14140 cx.emit(EditorEvent::PushedToNavHistory {
14141 anchor: cursor_anchor,
14142 is_deactivate,
14143 })
14144 }
14145 }
14146
14147 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14148 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14149 let buffer = self.buffer.read(cx).snapshot(cx);
14150 let mut selection = self.selections.first::<usize>(cx);
14151 selection.set_head(buffer.len(), SelectionGoal::None);
14152 self.change_selections(Default::default(), window, cx, |s| {
14153 s.select(vec![selection]);
14154 });
14155 }
14156
14157 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14158 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14159 let end = self.buffer.read(cx).read(cx).len();
14160 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14161 s.select_ranges(vec![0..end]);
14162 });
14163 }
14164
14165 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14166 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14167 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14168 let mut selections = self.selections.all::<Point>(cx);
14169 let max_point = display_map.buffer_snapshot().max_point();
14170 for selection in &mut selections {
14171 let rows = selection.spanned_rows(true, &display_map);
14172 selection.start = Point::new(rows.start.0, 0);
14173 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14174 selection.reversed = false;
14175 }
14176 self.change_selections(Default::default(), window, cx, |s| {
14177 s.select(selections);
14178 });
14179 }
14180
14181 pub fn split_selection_into_lines(
14182 &mut self,
14183 action: &SplitSelectionIntoLines,
14184 window: &mut Window,
14185 cx: &mut Context<Self>,
14186 ) {
14187 let selections = self
14188 .selections
14189 .all::<Point>(cx)
14190 .into_iter()
14191 .map(|selection| selection.start..selection.end)
14192 .collect::<Vec<_>>();
14193 self.unfold_ranges(&selections, true, true, cx);
14194
14195 let mut new_selection_ranges = Vec::new();
14196 {
14197 let buffer = self.buffer.read(cx).read(cx);
14198 for selection in selections {
14199 for row in selection.start.row..selection.end.row {
14200 let line_start = Point::new(row, 0);
14201 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14202
14203 if action.keep_selections {
14204 // Keep the selection range for each line
14205 let selection_start = if row == selection.start.row {
14206 selection.start
14207 } else {
14208 line_start
14209 };
14210 new_selection_ranges.push(selection_start..line_end);
14211 } else {
14212 // Collapse to cursor at end of line
14213 new_selection_ranges.push(line_end..line_end);
14214 }
14215 }
14216
14217 let is_multiline_selection = selection.start.row != selection.end.row;
14218 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14219 // so this action feels more ergonomic when paired with other selection operations
14220 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14221 if !should_skip_last {
14222 if action.keep_selections {
14223 if is_multiline_selection {
14224 let line_start = Point::new(selection.end.row, 0);
14225 new_selection_ranges.push(line_start..selection.end);
14226 } else {
14227 new_selection_ranges.push(selection.start..selection.end);
14228 }
14229 } else {
14230 new_selection_ranges.push(selection.end..selection.end);
14231 }
14232 }
14233 }
14234 }
14235 self.change_selections(Default::default(), window, cx, |s| {
14236 s.select_ranges(new_selection_ranges);
14237 });
14238 }
14239
14240 pub fn add_selection_above(
14241 &mut self,
14242 _: &AddSelectionAbove,
14243 window: &mut Window,
14244 cx: &mut Context<Self>,
14245 ) {
14246 self.add_selection(true, window, cx);
14247 }
14248
14249 pub fn add_selection_below(
14250 &mut self,
14251 _: &AddSelectionBelow,
14252 window: &mut Window,
14253 cx: &mut Context<Self>,
14254 ) {
14255 self.add_selection(false, window, cx);
14256 }
14257
14258 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
14259 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14260
14261 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14262 let all_selections = self.selections.all::<Point>(cx);
14263 let text_layout_details = self.text_layout_details(window);
14264
14265 let (mut columnar_selections, new_selections_to_columnarize) = {
14266 if let Some(state) = self.add_selections_state.as_ref() {
14267 let columnar_selection_ids: HashSet<_> = state
14268 .groups
14269 .iter()
14270 .flat_map(|group| group.stack.iter())
14271 .copied()
14272 .collect();
14273
14274 all_selections
14275 .into_iter()
14276 .partition(|s| columnar_selection_ids.contains(&s.id))
14277 } else {
14278 (Vec::new(), all_selections)
14279 }
14280 };
14281
14282 let mut state = self
14283 .add_selections_state
14284 .take()
14285 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14286
14287 for selection in new_selections_to_columnarize {
14288 let range = selection.display_range(&display_map).sorted();
14289 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14290 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14291 let positions = start_x.min(end_x)..start_x.max(end_x);
14292 let mut stack = Vec::new();
14293 for row in range.start.row().0..=range.end.row().0 {
14294 if let Some(selection) = self.selections.build_columnar_selection(
14295 &display_map,
14296 DisplayRow(row),
14297 &positions,
14298 selection.reversed,
14299 &text_layout_details,
14300 ) {
14301 stack.push(selection.id);
14302 columnar_selections.push(selection);
14303 }
14304 }
14305 if !stack.is_empty() {
14306 if above {
14307 stack.reverse();
14308 }
14309 state.groups.push(AddSelectionsGroup { above, stack });
14310 }
14311 }
14312
14313 let mut final_selections = Vec::new();
14314 let end_row = if above {
14315 DisplayRow(0)
14316 } else {
14317 display_map.max_point().row()
14318 };
14319
14320 let mut last_added_item_per_group = HashMap::default();
14321 for group in state.groups.iter_mut() {
14322 if let Some(last_id) = group.stack.last() {
14323 last_added_item_per_group.insert(*last_id, group);
14324 }
14325 }
14326
14327 for selection in columnar_selections {
14328 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14329 if above == group.above {
14330 let range = selection.display_range(&display_map).sorted();
14331 debug_assert_eq!(range.start.row(), range.end.row());
14332 let mut row = range.start.row();
14333 let positions =
14334 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14335 Pixels::from(start)..Pixels::from(end)
14336 } else {
14337 let start_x =
14338 display_map.x_for_display_point(range.start, &text_layout_details);
14339 let end_x =
14340 display_map.x_for_display_point(range.end, &text_layout_details);
14341 start_x.min(end_x)..start_x.max(end_x)
14342 };
14343
14344 let mut maybe_new_selection = None;
14345 while row != end_row {
14346 if above {
14347 row.0 -= 1;
14348 } else {
14349 row.0 += 1;
14350 }
14351 if let Some(new_selection) = self.selections.build_columnar_selection(
14352 &display_map,
14353 row,
14354 &positions,
14355 selection.reversed,
14356 &text_layout_details,
14357 ) {
14358 maybe_new_selection = Some(new_selection);
14359 break;
14360 }
14361 }
14362
14363 if let Some(new_selection) = maybe_new_selection {
14364 group.stack.push(new_selection.id);
14365 if above {
14366 final_selections.push(new_selection);
14367 final_selections.push(selection);
14368 } else {
14369 final_selections.push(selection);
14370 final_selections.push(new_selection);
14371 }
14372 } else {
14373 final_selections.push(selection);
14374 }
14375 } else {
14376 group.stack.pop();
14377 }
14378 } else {
14379 final_selections.push(selection);
14380 }
14381 }
14382
14383 self.change_selections(Default::default(), window, cx, |s| {
14384 s.select(final_selections);
14385 });
14386
14387 let final_selection_ids: HashSet<_> = self
14388 .selections
14389 .all::<Point>(cx)
14390 .iter()
14391 .map(|s| s.id)
14392 .collect();
14393 state.groups.retain_mut(|group| {
14394 // selections might get merged above so we remove invalid items from stacks
14395 group.stack.retain(|id| final_selection_ids.contains(id));
14396
14397 // single selection in stack can be treated as initial state
14398 group.stack.len() > 1
14399 });
14400
14401 if !state.groups.is_empty() {
14402 self.add_selections_state = Some(state);
14403 }
14404 }
14405
14406 fn select_match_ranges(
14407 &mut self,
14408 range: Range<usize>,
14409 reversed: bool,
14410 replace_newest: bool,
14411 auto_scroll: Option<Autoscroll>,
14412 window: &mut Window,
14413 cx: &mut Context<Editor>,
14414 ) {
14415 self.unfold_ranges(
14416 std::slice::from_ref(&range),
14417 false,
14418 auto_scroll.is_some(),
14419 cx,
14420 );
14421 let effects = if let Some(scroll) = auto_scroll {
14422 SelectionEffects::scroll(scroll)
14423 } else {
14424 SelectionEffects::no_scroll()
14425 };
14426 self.change_selections(effects, window, cx, |s| {
14427 if replace_newest {
14428 s.delete(s.newest_anchor().id);
14429 }
14430 if reversed {
14431 s.insert_range(range.end..range.start);
14432 } else {
14433 s.insert_range(range);
14434 }
14435 });
14436 }
14437
14438 pub fn select_next_match_internal(
14439 &mut self,
14440 display_map: &DisplaySnapshot,
14441 replace_newest: bool,
14442 autoscroll: Option<Autoscroll>,
14443 window: &mut Window,
14444 cx: &mut Context<Self>,
14445 ) -> Result<()> {
14446 let buffer = display_map.buffer_snapshot();
14447 let mut selections = self.selections.all::<usize>(cx);
14448 if let Some(mut select_next_state) = self.select_next_state.take() {
14449 let query = &select_next_state.query;
14450 if !select_next_state.done {
14451 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14452 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14453 let mut next_selected_range = None;
14454
14455 // Collect and sort selection ranges for efficient overlap checking
14456 let mut selection_ranges: Vec<_> = selections.iter().map(|s| s.range()).collect();
14457 selection_ranges.sort_by_key(|r| r.start);
14458
14459 let bytes_after_last_selection =
14460 buffer.bytes_in_range(last_selection.end..buffer.len());
14461 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14462 let query_matches = query
14463 .stream_find_iter(bytes_after_last_selection)
14464 .map(|result| (last_selection.end, result))
14465 .chain(
14466 query
14467 .stream_find_iter(bytes_before_first_selection)
14468 .map(|result| (0, result)),
14469 );
14470
14471 for (start_offset, query_match) in query_matches {
14472 let query_match = query_match.unwrap(); // can only fail due to I/O
14473 let offset_range =
14474 start_offset + query_match.start()..start_offset + query_match.end();
14475
14476 if !select_next_state.wordwise
14477 || (!buffer.is_inside_word(offset_range.start, None)
14478 && !buffer.is_inside_word(offset_range.end, None))
14479 {
14480 // Use binary search to check for overlap (O(log n))
14481 let overlaps = selection_ranges
14482 .binary_search_by(|range| {
14483 if range.end <= offset_range.start {
14484 std::cmp::Ordering::Less
14485 } else if range.start >= offset_range.end {
14486 std::cmp::Ordering::Greater
14487 } else {
14488 std::cmp::Ordering::Equal
14489 }
14490 })
14491 .is_ok();
14492
14493 if !overlaps {
14494 next_selected_range = Some(offset_range);
14495 break;
14496 }
14497 }
14498 }
14499
14500 if let Some(next_selected_range) = next_selected_range {
14501 self.select_match_ranges(
14502 next_selected_range,
14503 last_selection.reversed,
14504 replace_newest,
14505 autoscroll,
14506 window,
14507 cx,
14508 );
14509 } else {
14510 select_next_state.done = true;
14511 }
14512 }
14513
14514 self.select_next_state = Some(select_next_state);
14515 } else {
14516 let mut only_carets = true;
14517 let mut same_text_selected = true;
14518 let mut selected_text = None;
14519
14520 let mut selections_iter = selections.iter().peekable();
14521 while let Some(selection) = selections_iter.next() {
14522 if selection.start != selection.end {
14523 only_carets = false;
14524 }
14525
14526 if same_text_selected {
14527 if selected_text.is_none() {
14528 selected_text =
14529 Some(buffer.text_for_range(selection.range()).collect::<String>());
14530 }
14531
14532 if let Some(next_selection) = selections_iter.peek() {
14533 if next_selection.range().len() == selection.range().len() {
14534 let next_selected_text = buffer
14535 .text_for_range(next_selection.range())
14536 .collect::<String>();
14537 if Some(next_selected_text) != selected_text {
14538 same_text_selected = false;
14539 selected_text = None;
14540 }
14541 } else {
14542 same_text_selected = false;
14543 selected_text = None;
14544 }
14545 }
14546 }
14547 }
14548
14549 if only_carets {
14550 for selection in &mut selections {
14551 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14552 selection.start = word_range.start;
14553 selection.end = word_range.end;
14554 selection.goal = SelectionGoal::None;
14555 selection.reversed = false;
14556 self.select_match_ranges(
14557 selection.start..selection.end,
14558 selection.reversed,
14559 replace_newest,
14560 autoscroll,
14561 window,
14562 cx,
14563 );
14564 }
14565
14566 if selections.len() == 1 {
14567 let selection = selections
14568 .last()
14569 .expect("ensured that there's only one selection");
14570 let query = buffer
14571 .text_for_range(selection.start..selection.end)
14572 .collect::<String>();
14573 let is_empty = query.is_empty();
14574 let select_state = SelectNextState {
14575 query: AhoCorasick::new(&[query])?,
14576 wordwise: true,
14577 done: is_empty,
14578 };
14579 self.select_next_state = Some(select_state);
14580 } else {
14581 self.select_next_state = None;
14582 }
14583 } else if let Some(selected_text) = selected_text {
14584 self.select_next_state = Some(SelectNextState {
14585 query: AhoCorasick::new(&[selected_text])?,
14586 wordwise: false,
14587 done: false,
14588 });
14589 self.select_next_match_internal(
14590 display_map,
14591 replace_newest,
14592 autoscroll,
14593 window,
14594 cx,
14595 )?;
14596 }
14597 }
14598 Ok(())
14599 }
14600
14601 pub fn select_all_matches(
14602 &mut self,
14603 _action: &SelectAllMatches,
14604 window: &mut Window,
14605 cx: &mut Context<Self>,
14606 ) -> Result<()> {
14607 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14608
14609 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14610
14611 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14612 let Some(select_next_state) = self.select_next_state.as_mut() else {
14613 return Ok(());
14614 };
14615 if select_next_state.done {
14616 return Ok(());
14617 }
14618
14619 let mut new_selections = Vec::new();
14620
14621 let reversed = self.selections.oldest::<usize>(cx).reversed;
14622 let buffer = display_map.buffer_snapshot();
14623 let query_matches = select_next_state
14624 .query
14625 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14626
14627 for query_match in query_matches.into_iter() {
14628 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14629 let offset_range = if reversed {
14630 query_match.end()..query_match.start()
14631 } else {
14632 query_match.start()..query_match.end()
14633 };
14634
14635 if !select_next_state.wordwise
14636 || (!buffer.is_inside_word(offset_range.start, None)
14637 && !buffer.is_inside_word(offset_range.end, None))
14638 {
14639 new_selections.push(offset_range.start..offset_range.end);
14640 }
14641 }
14642
14643 select_next_state.done = true;
14644
14645 if new_selections.is_empty() {
14646 log::error!("bug: new_selections is empty in select_all_matches");
14647 return Ok(());
14648 }
14649
14650 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14651 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14652 selections.select_ranges(new_selections)
14653 });
14654
14655 Ok(())
14656 }
14657
14658 pub fn select_next(
14659 &mut self,
14660 action: &SelectNext,
14661 window: &mut Window,
14662 cx: &mut Context<Self>,
14663 ) -> Result<()> {
14664 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14665 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14666 self.select_next_match_internal(
14667 &display_map,
14668 action.replace_newest,
14669 Some(Autoscroll::newest()),
14670 window,
14671 cx,
14672 )?;
14673 Ok(())
14674 }
14675
14676 pub fn select_previous(
14677 &mut self,
14678 action: &SelectPrevious,
14679 window: &mut Window,
14680 cx: &mut Context<Self>,
14681 ) -> Result<()> {
14682 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14683 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14684 let buffer = display_map.buffer_snapshot();
14685 let mut selections = self.selections.all::<usize>(cx);
14686 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14687 let query = &select_prev_state.query;
14688 if !select_prev_state.done {
14689 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14690 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14691 let mut next_selected_range = None;
14692 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14693 let bytes_before_last_selection =
14694 buffer.reversed_bytes_in_range(0..last_selection.start);
14695 let bytes_after_first_selection =
14696 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14697 let query_matches = query
14698 .stream_find_iter(bytes_before_last_selection)
14699 .map(|result| (last_selection.start, result))
14700 .chain(
14701 query
14702 .stream_find_iter(bytes_after_first_selection)
14703 .map(|result| (buffer.len(), result)),
14704 );
14705 for (end_offset, query_match) in query_matches {
14706 let query_match = query_match.unwrap(); // can only fail due to I/O
14707 let offset_range =
14708 end_offset - query_match.end()..end_offset - query_match.start();
14709
14710 if !select_prev_state.wordwise
14711 || (!buffer.is_inside_word(offset_range.start, None)
14712 && !buffer.is_inside_word(offset_range.end, None))
14713 {
14714 next_selected_range = Some(offset_range);
14715 break;
14716 }
14717 }
14718
14719 if let Some(next_selected_range) = next_selected_range {
14720 self.select_match_ranges(
14721 next_selected_range,
14722 last_selection.reversed,
14723 action.replace_newest,
14724 Some(Autoscroll::newest()),
14725 window,
14726 cx,
14727 );
14728 } else {
14729 select_prev_state.done = true;
14730 }
14731 }
14732
14733 self.select_prev_state = Some(select_prev_state);
14734 } else {
14735 let mut only_carets = true;
14736 let mut same_text_selected = true;
14737 let mut selected_text = None;
14738
14739 let mut selections_iter = selections.iter().peekable();
14740 while let Some(selection) = selections_iter.next() {
14741 if selection.start != selection.end {
14742 only_carets = false;
14743 }
14744
14745 if same_text_selected {
14746 if selected_text.is_none() {
14747 selected_text =
14748 Some(buffer.text_for_range(selection.range()).collect::<String>());
14749 }
14750
14751 if let Some(next_selection) = selections_iter.peek() {
14752 if next_selection.range().len() == selection.range().len() {
14753 let next_selected_text = buffer
14754 .text_for_range(next_selection.range())
14755 .collect::<String>();
14756 if Some(next_selected_text) != selected_text {
14757 same_text_selected = false;
14758 selected_text = None;
14759 }
14760 } else {
14761 same_text_selected = false;
14762 selected_text = None;
14763 }
14764 }
14765 }
14766 }
14767
14768 if only_carets {
14769 for selection in &mut selections {
14770 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14771 selection.start = word_range.start;
14772 selection.end = word_range.end;
14773 selection.goal = SelectionGoal::None;
14774 selection.reversed = false;
14775 self.select_match_ranges(
14776 selection.start..selection.end,
14777 selection.reversed,
14778 action.replace_newest,
14779 Some(Autoscroll::newest()),
14780 window,
14781 cx,
14782 );
14783 }
14784 if selections.len() == 1 {
14785 let selection = selections
14786 .last()
14787 .expect("ensured that there's only one selection");
14788 let query = buffer
14789 .text_for_range(selection.start..selection.end)
14790 .collect::<String>();
14791 let is_empty = query.is_empty();
14792 let select_state = SelectNextState {
14793 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14794 wordwise: true,
14795 done: is_empty,
14796 };
14797 self.select_prev_state = Some(select_state);
14798 } else {
14799 self.select_prev_state = None;
14800 }
14801 } else if let Some(selected_text) = selected_text {
14802 self.select_prev_state = Some(SelectNextState {
14803 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14804 wordwise: false,
14805 done: false,
14806 });
14807 self.select_previous(action, window, cx)?;
14808 }
14809 }
14810 Ok(())
14811 }
14812
14813 pub fn find_next_match(
14814 &mut self,
14815 _: &FindNextMatch,
14816 window: &mut Window,
14817 cx: &mut Context<Self>,
14818 ) -> Result<()> {
14819 let selections = self.selections.disjoint_anchors_arc();
14820 match selections.first() {
14821 Some(first) if selections.len() >= 2 => {
14822 self.change_selections(Default::default(), window, cx, |s| {
14823 s.select_ranges([first.range()]);
14824 });
14825 }
14826 _ => self.select_next(
14827 &SelectNext {
14828 replace_newest: true,
14829 },
14830 window,
14831 cx,
14832 )?,
14833 }
14834 Ok(())
14835 }
14836
14837 pub fn find_previous_match(
14838 &mut self,
14839 _: &FindPreviousMatch,
14840 window: &mut Window,
14841 cx: &mut Context<Self>,
14842 ) -> Result<()> {
14843 let selections = self.selections.disjoint_anchors_arc();
14844 match selections.last() {
14845 Some(last) if selections.len() >= 2 => {
14846 self.change_selections(Default::default(), window, cx, |s| {
14847 s.select_ranges([last.range()]);
14848 });
14849 }
14850 _ => self.select_previous(
14851 &SelectPrevious {
14852 replace_newest: true,
14853 },
14854 window,
14855 cx,
14856 )?,
14857 }
14858 Ok(())
14859 }
14860
14861 pub fn toggle_comments(
14862 &mut self,
14863 action: &ToggleComments,
14864 window: &mut Window,
14865 cx: &mut Context<Self>,
14866 ) {
14867 if self.read_only(cx) {
14868 return;
14869 }
14870 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14871 let text_layout_details = &self.text_layout_details(window);
14872 self.transact(window, cx, |this, window, cx| {
14873 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14874 let mut edits = Vec::new();
14875 let mut selection_edit_ranges = Vec::new();
14876 let mut last_toggled_row = None;
14877 let snapshot = this.buffer.read(cx).read(cx);
14878 let empty_str: Arc<str> = Arc::default();
14879 let mut suffixes_inserted = Vec::new();
14880 let ignore_indent = action.ignore_indent;
14881
14882 fn comment_prefix_range(
14883 snapshot: &MultiBufferSnapshot,
14884 row: MultiBufferRow,
14885 comment_prefix: &str,
14886 comment_prefix_whitespace: &str,
14887 ignore_indent: bool,
14888 ) -> Range<Point> {
14889 let indent_size = if ignore_indent {
14890 0
14891 } else {
14892 snapshot.indent_size_for_line(row).len
14893 };
14894
14895 let start = Point::new(row.0, indent_size);
14896
14897 let mut line_bytes = snapshot
14898 .bytes_in_range(start..snapshot.max_point())
14899 .flatten()
14900 .copied();
14901
14902 // If this line currently begins with the line comment prefix, then record
14903 // the range containing the prefix.
14904 if line_bytes
14905 .by_ref()
14906 .take(comment_prefix.len())
14907 .eq(comment_prefix.bytes())
14908 {
14909 // Include any whitespace that matches the comment prefix.
14910 let matching_whitespace_len = line_bytes
14911 .zip(comment_prefix_whitespace.bytes())
14912 .take_while(|(a, b)| a == b)
14913 .count() as u32;
14914 let end = Point::new(
14915 start.row,
14916 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14917 );
14918 start..end
14919 } else {
14920 start..start
14921 }
14922 }
14923
14924 fn comment_suffix_range(
14925 snapshot: &MultiBufferSnapshot,
14926 row: MultiBufferRow,
14927 comment_suffix: &str,
14928 comment_suffix_has_leading_space: bool,
14929 ) -> Range<Point> {
14930 let end = Point::new(row.0, snapshot.line_len(row));
14931 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14932
14933 let mut line_end_bytes = snapshot
14934 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14935 .flatten()
14936 .copied();
14937
14938 let leading_space_len = if suffix_start_column > 0
14939 && line_end_bytes.next() == Some(b' ')
14940 && comment_suffix_has_leading_space
14941 {
14942 1
14943 } else {
14944 0
14945 };
14946
14947 // If this line currently begins with the line comment prefix, then record
14948 // the range containing the prefix.
14949 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14950 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14951 start..end
14952 } else {
14953 end..end
14954 }
14955 }
14956
14957 // TODO: Handle selections that cross excerpts
14958 for selection in &mut selections {
14959 let start_column = snapshot
14960 .indent_size_for_line(MultiBufferRow(selection.start.row))
14961 .len;
14962 let language = if let Some(language) =
14963 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14964 {
14965 language
14966 } else {
14967 continue;
14968 };
14969
14970 selection_edit_ranges.clear();
14971
14972 // If multiple selections contain a given row, avoid processing that
14973 // row more than once.
14974 let mut start_row = MultiBufferRow(selection.start.row);
14975 if last_toggled_row == Some(start_row) {
14976 start_row = start_row.next_row();
14977 }
14978 let end_row =
14979 if selection.end.row > selection.start.row && selection.end.column == 0 {
14980 MultiBufferRow(selection.end.row - 1)
14981 } else {
14982 MultiBufferRow(selection.end.row)
14983 };
14984 last_toggled_row = Some(end_row);
14985
14986 if start_row > end_row {
14987 continue;
14988 }
14989
14990 // If the language has line comments, toggle those.
14991 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14992
14993 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14994 if ignore_indent {
14995 full_comment_prefixes = full_comment_prefixes
14996 .into_iter()
14997 .map(|s| Arc::from(s.trim_end()))
14998 .collect();
14999 }
15000
15001 if !full_comment_prefixes.is_empty() {
15002 let first_prefix = full_comment_prefixes
15003 .first()
15004 .expect("prefixes is non-empty");
15005 let prefix_trimmed_lengths = full_comment_prefixes
15006 .iter()
15007 .map(|p| p.trim_end_matches(' ').len())
15008 .collect::<SmallVec<[usize; 4]>>();
15009
15010 let mut all_selection_lines_are_comments = true;
15011
15012 for row in start_row.0..=end_row.0 {
15013 let row = MultiBufferRow(row);
15014 if start_row < end_row && snapshot.is_line_blank(row) {
15015 continue;
15016 }
15017
15018 let prefix_range = full_comment_prefixes
15019 .iter()
15020 .zip(prefix_trimmed_lengths.iter().copied())
15021 .map(|(prefix, trimmed_prefix_len)| {
15022 comment_prefix_range(
15023 snapshot.deref(),
15024 row,
15025 &prefix[..trimmed_prefix_len],
15026 &prefix[trimmed_prefix_len..],
15027 ignore_indent,
15028 )
15029 })
15030 .max_by_key(|range| range.end.column - range.start.column)
15031 .expect("prefixes is non-empty");
15032
15033 if prefix_range.is_empty() {
15034 all_selection_lines_are_comments = false;
15035 }
15036
15037 selection_edit_ranges.push(prefix_range);
15038 }
15039
15040 if all_selection_lines_are_comments {
15041 edits.extend(
15042 selection_edit_ranges
15043 .iter()
15044 .cloned()
15045 .map(|range| (range, empty_str.clone())),
15046 );
15047 } else {
15048 let min_column = selection_edit_ranges
15049 .iter()
15050 .map(|range| range.start.column)
15051 .min()
15052 .unwrap_or(0);
15053 edits.extend(selection_edit_ranges.iter().map(|range| {
15054 let position = Point::new(range.start.row, min_column);
15055 (position..position, first_prefix.clone())
15056 }));
15057 }
15058 } else if let Some(BlockCommentConfig {
15059 start: full_comment_prefix,
15060 end: comment_suffix,
15061 ..
15062 }) = language.block_comment()
15063 {
15064 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15065 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15066 let prefix_range = comment_prefix_range(
15067 snapshot.deref(),
15068 start_row,
15069 comment_prefix,
15070 comment_prefix_whitespace,
15071 ignore_indent,
15072 );
15073 let suffix_range = comment_suffix_range(
15074 snapshot.deref(),
15075 end_row,
15076 comment_suffix.trim_start_matches(' '),
15077 comment_suffix.starts_with(' '),
15078 );
15079
15080 if prefix_range.is_empty() || suffix_range.is_empty() {
15081 edits.push((
15082 prefix_range.start..prefix_range.start,
15083 full_comment_prefix.clone(),
15084 ));
15085 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15086 suffixes_inserted.push((end_row, comment_suffix.len()));
15087 } else {
15088 edits.push((prefix_range, empty_str.clone()));
15089 edits.push((suffix_range, empty_str.clone()));
15090 }
15091 } else {
15092 continue;
15093 }
15094 }
15095
15096 drop(snapshot);
15097 this.buffer.update(cx, |buffer, cx| {
15098 buffer.edit(edits, None, cx);
15099 });
15100
15101 // Adjust selections so that they end before any comment suffixes that
15102 // were inserted.
15103 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15104 let mut selections = this.selections.all::<Point>(cx);
15105 let snapshot = this.buffer.read(cx).read(cx);
15106 for selection in &mut selections {
15107 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15108 match row.cmp(&MultiBufferRow(selection.end.row)) {
15109 Ordering::Less => {
15110 suffixes_inserted.next();
15111 continue;
15112 }
15113 Ordering::Greater => break,
15114 Ordering::Equal => {
15115 if selection.end.column == snapshot.line_len(row) {
15116 if selection.is_empty() {
15117 selection.start.column -= suffix_len as u32;
15118 }
15119 selection.end.column -= suffix_len as u32;
15120 }
15121 break;
15122 }
15123 }
15124 }
15125 }
15126
15127 drop(snapshot);
15128 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15129
15130 let selections = this.selections.all::<Point>(cx);
15131 let selections_on_single_row = selections.windows(2).all(|selections| {
15132 selections[0].start.row == selections[1].start.row
15133 && selections[0].end.row == selections[1].end.row
15134 && selections[0].start.row == selections[0].end.row
15135 });
15136 let selections_selecting = selections
15137 .iter()
15138 .any(|selection| selection.start != selection.end);
15139 let advance_downwards = action.advance_downwards
15140 && selections_on_single_row
15141 && !selections_selecting
15142 && !matches!(this.mode, EditorMode::SingleLine);
15143
15144 if advance_downwards {
15145 let snapshot = this.buffer.read(cx).snapshot(cx);
15146
15147 this.change_selections(Default::default(), window, cx, |s| {
15148 s.move_cursors_with(|display_snapshot, display_point, _| {
15149 let mut point = display_point.to_point(display_snapshot);
15150 point.row += 1;
15151 point = snapshot.clip_point(point, Bias::Left);
15152 let display_point = point.to_display_point(display_snapshot);
15153 let goal = SelectionGoal::HorizontalPosition(
15154 display_snapshot
15155 .x_for_display_point(display_point, text_layout_details)
15156 .into(),
15157 );
15158 (display_point, goal)
15159 })
15160 });
15161 }
15162 });
15163 }
15164
15165 pub fn select_enclosing_symbol(
15166 &mut self,
15167 _: &SelectEnclosingSymbol,
15168 window: &mut Window,
15169 cx: &mut Context<Self>,
15170 ) {
15171 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15172
15173 let buffer = self.buffer.read(cx).snapshot(cx);
15174 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
15175
15176 fn update_selection(
15177 selection: &Selection<usize>,
15178 buffer_snap: &MultiBufferSnapshot,
15179 ) -> Option<Selection<usize>> {
15180 let cursor = selection.head();
15181 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15182 for symbol in symbols.iter().rev() {
15183 let start = symbol.range.start.to_offset(buffer_snap);
15184 let end = symbol.range.end.to_offset(buffer_snap);
15185 let new_range = start..end;
15186 if start < selection.start || end > selection.end {
15187 return Some(Selection {
15188 id: selection.id,
15189 start: new_range.start,
15190 end: new_range.end,
15191 goal: SelectionGoal::None,
15192 reversed: selection.reversed,
15193 });
15194 }
15195 }
15196 None
15197 }
15198
15199 let mut selected_larger_symbol = false;
15200 let new_selections = old_selections
15201 .iter()
15202 .map(|selection| match update_selection(selection, &buffer) {
15203 Some(new_selection) => {
15204 if new_selection.range() != selection.range() {
15205 selected_larger_symbol = true;
15206 }
15207 new_selection
15208 }
15209 None => selection.clone(),
15210 })
15211 .collect::<Vec<_>>();
15212
15213 if selected_larger_symbol {
15214 self.change_selections(Default::default(), window, cx, |s| {
15215 s.select(new_selections);
15216 });
15217 }
15218 }
15219
15220 pub fn select_larger_syntax_node(
15221 &mut self,
15222 _: &SelectLargerSyntaxNode,
15223 window: &mut Window,
15224 cx: &mut Context<Self>,
15225 ) {
15226 let Some(visible_row_count) = self.visible_row_count() else {
15227 return;
15228 };
15229 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15230 if old_selections.is_empty() {
15231 return;
15232 }
15233
15234 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15235
15236 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15237 let buffer = self.buffer.read(cx).snapshot(cx);
15238
15239 let mut selected_larger_node = false;
15240 let mut new_selections = old_selections
15241 .iter()
15242 .map(|selection| {
15243 let old_range = selection.start..selection.end;
15244
15245 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15246 // manually select word at selection
15247 if ["string_content", "inline"].contains(&node.kind()) {
15248 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15249 // ignore if word is already selected
15250 if !word_range.is_empty() && old_range != word_range {
15251 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15252 // only select word if start and end point belongs to same word
15253 if word_range == last_word_range {
15254 selected_larger_node = true;
15255 return Selection {
15256 id: selection.id,
15257 start: word_range.start,
15258 end: word_range.end,
15259 goal: SelectionGoal::None,
15260 reversed: selection.reversed,
15261 };
15262 }
15263 }
15264 }
15265 }
15266
15267 let mut new_range = old_range.clone();
15268 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15269 new_range = range;
15270 if !node.is_named() {
15271 continue;
15272 }
15273 if !display_map.intersects_fold(new_range.start)
15274 && !display_map.intersects_fold(new_range.end)
15275 {
15276 break;
15277 }
15278 }
15279
15280 selected_larger_node |= new_range != old_range;
15281 Selection {
15282 id: selection.id,
15283 start: new_range.start,
15284 end: new_range.end,
15285 goal: SelectionGoal::None,
15286 reversed: selection.reversed,
15287 }
15288 })
15289 .collect::<Vec<_>>();
15290
15291 if !selected_larger_node {
15292 return; // don't put this call in the history
15293 }
15294
15295 // scroll based on transformation done to the last selection created by the user
15296 let (last_old, last_new) = old_selections
15297 .last()
15298 .zip(new_selections.last().cloned())
15299 .expect("old_selections isn't empty");
15300
15301 // revert selection
15302 let is_selection_reversed = {
15303 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15304 new_selections.last_mut().expect("checked above").reversed =
15305 should_newest_selection_be_reversed;
15306 should_newest_selection_be_reversed
15307 };
15308
15309 if selected_larger_node {
15310 self.select_syntax_node_history.disable_clearing = true;
15311 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15312 s.select(new_selections.clone());
15313 });
15314 self.select_syntax_node_history.disable_clearing = false;
15315 }
15316
15317 let start_row = last_new.start.to_display_point(&display_map).row().0;
15318 let end_row = last_new.end.to_display_point(&display_map).row().0;
15319 let selection_height = end_row - start_row + 1;
15320 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15321
15322 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15323 let scroll_behavior = if fits_on_the_screen {
15324 self.request_autoscroll(Autoscroll::fit(), cx);
15325 SelectSyntaxNodeScrollBehavior::FitSelection
15326 } else if is_selection_reversed {
15327 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15328 SelectSyntaxNodeScrollBehavior::CursorTop
15329 } else {
15330 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15331 SelectSyntaxNodeScrollBehavior::CursorBottom
15332 };
15333
15334 self.select_syntax_node_history.push((
15335 old_selections,
15336 scroll_behavior,
15337 is_selection_reversed,
15338 ));
15339 }
15340
15341 pub fn select_smaller_syntax_node(
15342 &mut self,
15343 _: &SelectSmallerSyntaxNode,
15344 window: &mut Window,
15345 cx: &mut Context<Self>,
15346 ) {
15347 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15348
15349 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15350 self.select_syntax_node_history.pop()
15351 {
15352 if let Some(selection) = selections.last_mut() {
15353 selection.reversed = is_selection_reversed;
15354 }
15355
15356 self.select_syntax_node_history.disable_clearing = true;
15357 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15358 s.select(selections.to_vec());
15359 });
15360 self.select_syntax_node_history.disable_clearing = false;
15361
15362 match scroll_behavior {
15363 SelectSyntaxNodeScrollBehavior::CursorTop => {
15364 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15365 }
15366 SelectSyntaxNodeScrollBehavior::FitSelection => {
15367 self.request_autoscroll(Autoscroll::fit(), cx);
15368 }
15369 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15370 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15371 }
15372 }
15373 }
15374 }
15375
15376 pub fn unwrap_syntax_node(
15377 &mut self,
15378 _: &UnwrapSyntaxNode,
15379 window: &mut Window,
15380 cx: &mut Context<Self>,
15381 ) {
15382 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15383
15384 let buffer = self.buffer.read(cx).snapshot(cx);
15385 let selections = self
15386 .selections
15387 .all::<usize>(cx)
15388 .into_iter()
15389 // subtracting the offset requires sorting
15390 .sorted_by_key(|i| i.start);
15391
15392 let full_edits = selections
15393 .into_iter()
15394 .filter_map(|selection| {
15395 let child = if selection.is_empty()
15396 && let Some((_, ancestor_range)) =
15397 buffer.syntax_ancestor(selection.start..selection.end)
15398 {
15399 ancestor_range
15400 } else {
15401 selection.range()
15402 };
15403
15404 let mut parent = child.clone();
15405 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15406 parent = ancestor_range;
15407 if parent.start < child.start || parent.end > child.end {
15408 break;
15409 }
15410 }
15411
15412 if parent == child {
15413 return None;
15414 }
15415 let text = buffer.text_for_range(child).collect::<String>();
15416 Some((selection.id, parent, text))
15417 })
15418 .collect::<Vec<_>>();
15419 if full_edits.is_empty() {
15420 return;
15421 }
15422
15423 self.transact(window, cx, |this, window, cx| {
15424 this.buffer.update(cx, |buffer, cx| {
15425 buffer.edit(
15426 full_edits
15427 .iter()
15428 .map(|(_, p, t)| (p.clone(), t.clone()))
15429 .collect::<Vec<_>>(),
15430 None,
15431 cx,
15432 );
15433 });
15434 this.change_selections(Default::default(), window, cx, |s| {
15435 let mut offset = 0;
15436 let mut selections = vec![];
15437 for (id, parent, text) in full_edits {
15438 let start = parent.start - offset;
15439 offset += parent.len() - text.len();
15440 selections.push(Selection {
15441 id,
15442 start,
15443 end: start + text.len(),
15444 reversed: false,
15445 goal: Default::default(),
15446 });
15447 }
15448 s.select(selections);
15449 });
15450 });
15451 }
15452
15453 pub fn select_next_syntax_node(
15454 &mut self,
15455 _: &SelectNextSyntaxNode,
15456 window: &mut Window,
15457 cx: &mut Context<Self>,
15458 ) {
15459 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15460 if old_selections.is_empty() {
15461 return;
15462 }
15463
15464 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15465
15466 let buffer = self.buffer.read(cx).snapshot(cx);
15467 let mut selected_sibling = false;
15468
15469 let new_selections = old_selections
15470 .iter()
15471 .map(|selection| {
15472 let old_range = selection.start..selection.end;
15473
15474 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15475 let new_range = node.byte_range();
15476 selected_sibling = true;
15477 Selection {
15478 id: selection.id,
15479 start: new_range.start,
15480 end: new_range.end,
15481 goal: SelectionGoal::None,
15482 reversed: selection.reversed,
15483 }
15484 } else {
15485 selection.clone()
15486 }
15487 })
15488 .collect::<Vec<_>>();
15489
15490 if selected_sibling {
15491 self.change_selections(
15492 SelectionEffects::scroll(Autoscroll::fit()),
15493 window,
15494 cx,
15495 |s| {
15496 s.select(new_selections);
15497 },
15498 );
15499 }
15500 }
15501
15502 pub fn select_prev_syntax_node(
15503 &mut self,
15504 _: &SelectPreviousSyntaxNode,
15505 window: &mut Window,
15506 cx: &mut Context<Self>,
15507 ) {
15508 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15509 if old_selections.is_empty() {
15510 return;
15511 }
15512
15513 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15514
15515 let buffer = self.buffer.read(cx).snapshot(cx);
15516 let mut selected_sibling = false;
15517
15518 let new_selections = old_selections
15519 .iter()
15520 .map(|selection| {
15521 let old_range = selection.start..selection.end;
15522
15523 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15524 let new_range = node.byte_range();
15525 selected_sibling = true;
15526 Selection {
15527 id: selection.id,
15528 start: new_range.start,
15529 end: new_range.end,
15530 goal: SelectionGoal::None,
15531 reversed: selection.reversed,
15532 }
15533 } else {
15534 selection.clone()
15535 }
15536 })
15537 .collect::<Vec<_>>();
15538
15539 if selected_sibling {
15540 self.change_selections(
15541 SelectionEffects::scroll(Autoscroll::fit()),
15542 window,
15543 cx,
15544 |s| {
15545 s.select(new_selections);
15546 },
15547 );
15548 }
15549 }
15550
15551 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15552 if !EditorSettings::get_global(cx).gutter.runnables {
15553 self.clear_tasks();
15554 return Task::ready(());
15555 }
15556 let project = self.project().map(Entity::downgrade);
15557 let task_sources = self.lsp_task_sources(cx);
15558 let multi_buffer = self.buffer.downgrade();
15559 cx.spawn_in(window, async move |editor, cx| {
15560 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15561 let Some(project) = project.and_then(|p| p.upgrade()) else {
15562 return;
15563 };
15564 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15565 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15566 }) else {
15567 return;
15568 };
15569
15570 let hide_runnables = project
15571 .update(cx, |project, _| project.is_via_collab())
15572 .unwrap_or(true);
15573 if hide_runnables {
15574 return;
15575 }
15576 let new_rows =
15577 cx.background_spawn({
15578 let snapshot = display_snapshot.clone();
15579 async move {
15580 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15581 }
15582 })
15583 .await;
15584 let Ok(lsp_tasks) =
15585 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15586 else {
15587 return;
15588 };
15589 let lsp_tasks = lsp_tasks.await;
15590
15591 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15592 lsp_tasks
15593 .into_iter()
15594 .flat_map(|(kind, tasks)| {
15595 tasks.into_iter().filter_map(move |(location, task)| {
15596 Some((kind.clone(), location?, task))
15597 })
15598 })
15599 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15600 let buffer = location.target.buffer;
15601 let buffer_snapshot = buffer.read(cx).snapshot();
15602 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15603 |(excerpt_id, snapshot, _)| {
15604 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15605 display_snapshot
15606 .buffer_snapshot()
15607 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15608 } else {
15609 None
15610 }
15611 },
15612 );
15613 if let Some(offset) = offset {
15614 let task_buffer_range =
15615 location.target.range.to_point(&buffer_snapshot);
15616 let context_buffer_range =
15617 task_buffer_range.to_offset(&buffer_snapshot);
15618 let context_range = BufferOffset(context_buffer_range.start)
15619 ..BufferOffset(context_buffer_range.end);
15620
15621 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15622 .or_insert_with(|| RunnableTasks {
15623 templates: Vec::new(),
15624 offset,
15625 column: task_buffer_range.start.column,
15626 extra_variables: HashMap::default(),
15627 context_range,
15628 })
15629 .templates
15630 .push((kind, task.original_task().clone()));
15631 }
15632
15633 acc
15634 })
15635 }) else {
15636 return;
15637 };
15638
15639 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15640 buffer.language_settings(cx).tasks.prefer_lsp
15641 }) else {
15642 return;
15643 };
15644
15645 let rows = Self::runnable_rows(
15646 project,
15647 display_snapshot,
15648 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15649 new_rows,
15650 cx.clone(),
15651 )
15652 .await;
15653 editor
15654 .update(cx, |editor, _| {
15655 editor.clear_tasks();
15656 for (key, mut value) in rows {
15657 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15658 value.templates.extend(lsp_tasks.templates);
15659 }
15660
15661 editor.insert_tasks(key, value);
15662 }
15663 for (key, value) in lsp_tasks_by_rows {
15664 editor.insert_tasks(key, value);
15665 }
15666 })
15667 .ok();
15668 })
15669 }
15670 fn fetch_runnable_ranges(
15671 snapshot: &DisplaySnapshot,
15672 range: Range<Anchor>,
15673 ) -> Vec<language::RunnableRange> {
15674 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15675 }
15676
15677 fn runnable_rows(
15678 project: Entity<Project>,
15679 snapshot: DisplaySnapshot,
15680 prefer_lsp: bool,
15681 runnable_ranges: Vec<RunnableRange>,
15682 cx: AsyncWindowContext,
15683 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15684 cx.spawn(async move |cx| {
15685 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15686 for mut runnable in runnable_ranges {
15687 let Some(tasks) = cx
15688 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15689 .ok()
15690 else {
15691 continue;
15692 };
15693 let mut tasks = tasks.await;
15694
15695 if prefer_lsp {
15696 tasks.retain(|(task_kind, _)| {
15697 !matches!(task_kind, TaskSourceKind::Language { .. })
15698 });
15699 }
15700 if tasks.is_empty() {
15701 continue;
15702 }
15703
15704 let point = runnable
15705 .run_range
15706 .start
15707 .to_point(&snapshot.buffer_snapshot());
15708 let Some(row) = snapshot
15709 .buffer_snapshot()
15710 .buffer_line_for_row(MultiBufferRow(point.row))
15711 .map(|(_, range)| range.start.row)
15712 else {
15713 continue;
15714 };
15715
15716 let context_range =
15717 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15718 runnable_rows.push((
15719 (runnable.buffer_id, row),
15720 RunnableTasks {
15721 templates: tasks,
15722 offset: snapshot
15723 .buffer_snapshot()
15724 .anchor_before(runnable.run_range.start),
15725 context_range,
15726 column: point.column,
15727 extra_variables: runnable.extra_captures,
15728 },
15729 ));
15730 }
15731 runnable_rows
15732 })
15733 }
15734
15735 fn templates_with_tags(
15736 project: &Entity<Project>,
15737 runnable: &mut Runnable,
15738 cx: &mut App,
15739 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15740 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15741 let (worktree_id, file) = project
15742 .buffer_for_id(runnable.buffer, cx)
15743 .and_then(|buffer| buffer.read(cx).file())
15744 .map(|file| (file.worktree_id(cx), file.clone()))
15745 .unzip();
15746
15747 (
15748 project.task_store().read(cx).task_inventory().cloned(),
15749 worktree_id,
15750 file,
15751 )
15752 });
15753
15754 let tags = mem::take(&mut runnable.tags);
15755 let language = runnable.language.clone();
15756 cx.spawn(async move |cx| {
15757 let mut templates_with_tags = Vec::new();
15758 if let Some(inventory) = inventory {
15759 for RunnableTag(tag) in tags {
15760 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15761 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15762 }) else {
15763 return templates_with_tags;
15764 };
15765 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15766 move |(_, template)| {
15767 template.tags.iter().any(|source_tag| source_tag == &tag)
15768 },
15769 ));
15770 }
15771 }
15772 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15773
15774 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15775 // Strongest source wins; if we have worktree tag binding, prefer that to
15776 // global and language bindings;
15777 // if we have a global binding, prefer that to language binding.
15778 let first_mismatch = templates_with_tags
15779 .iter()
15780 .position(|(tag_source, _)| tag_source != leading_tag_source);
15781 if let Some(index) = first_mismatch {
15782 templates_with_tags.truncate(index);
15783 }
15784 }
15785
15786 templates_with_tags
15787 })
15788 }
15789
15790 pub fn move_to_enclosing_bracket(
15791 &mut self,
15792 _: &MoveToEnclosingBracket,
15793 window: &mut Window,
15794 cx: &mut Context<Self>,
15795 ) {
15796 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15797 self.change_selections(Default::default(), window, cx, |s| {
15798 s.move_offsets_with(|snapshot, selection| {
15799 let Some(enclosing_bracket_ranges) =
15800 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15801 else {
15802 return;
15803 };
15804
15805 let mut best_length = usize::MAX;
15806 let mut best_inside = false;
15807 let mut best_in_bracket_range = false;
15808 let mut best_destination = None;
15809 for (open, close) in enclosing_bracket_ranges {
15810 let close = close.to_inclusive();
15811 let length = close.end() - open.start;
15812 let inside = selection.start >= open.end && selection.end <= *close.start();
15813 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15814 || close.contains(&selection.head());
15815
15816 // If best is next to a bracket and current isn't, skip
15817 if !in_bracket_range && best_in_bracket_range {
15818 continue;
15819 }
15820
15821 // Prefer smaller lengths unless best is inside and current isn't
15822 if length > best_length && (best_inside || !inside) {
15823 continue;
15824 }
15825
15826 best_length = length;
15827 best_inside = inside;
15828 best_in_bracket_range = in_bracket_range;
15829 best_destination = Some(
15830 if close.contains(&selection.start) && close.contains(&selection.end) {
15831 if inside { open.end } else { open.start }
15832 } else if inside {
15833 *close.start()
15834 } else {
15835 *close.end()
15836 },
15837 );
15838 }
15839
15840 if let Some(destination) = best_destination {
15841 selection.collapse_to(destination, SelectionGoal::None);
15842 }
15843 })
15844 });
15845 }
15846
15847 pub fn undo_selection(
15848 &mut self,
15849 _: &UndoSelection,
15850 window: &mut Window,
15851 cx: &mut Context<Self>,
15852 ) {
15853 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15854 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15855 self.selection_history.mode = SelectionHistoryMode::Undoing;
15856 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15857 this.end_selection(window, cx);
15858 this.change_selections(
15859 SelectionEffects::scroll(Autoscroll::newest()),
15860 window,
15861 cx,
15862 |s| s.select_anchors(entry.selections.to_vec()),
15863 );
15864 });
15865 self.selection_history.mode = SelectionHistoryMode::Normal;
15866
15867 self.select_next_state = entry.select_next_state;
15868 self.select_prev_state = entry.select_prev_state;
15869 self.add_selections_state = entry.add_selections_state;
15870 }
15871 }
15872
15873 pub fn redo_selection(
15874 &mut self,
15875 _: &RedoSelection,
15876 window: &mut Window,
15877 cx: &mut Context<Self>,
15878 ) {
15879 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15880 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15881 self.selection_history.mode = SelectionHistoryMode::Redoing;
15882 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15883 this.end_selection(window, cx);
15884 this.change_selections(
15885 SelectionEffects::scroll(Autoscroll::newest()),
15886 window,
15887 cx,
15888 |s| s.select_anchors(entry.selections.to_vec()),
15889 );
15890 });
15891 self.selection_history.mode = SelectionHistoryMode::Normal;
15892
15893 self.select_next_state = entry.select_next_state;
15894 self.select_prev_state = entry.select_prev_state;
15895 self.add_selections_state = entry.add_selections_state;
15896 }
15897 }
15898
15899 pub fn expand_excerpts(
15900 &mut self,
15901 action: &ExpandExcerpts,
15902 _: &mut Window,
15903 cx: &mut Context<Self>,
15904 ) {
15905 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15906 }
15907
15908 pub fn expand_excerpts_down(
15909 &mut self,
15910 action: &ExpandExcerptsDown,
15911 _: &mut Window,
15912 cx: &mut Context<Self>,
15913 ) {
15914 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15915 }
15916
15917 pub fn expand_excerpts_up(
15918 &mut self,
15919 action: &ExpandExcerptsUp,
15920 _: &mut Window,
15921 cx: &mut Context<Self>,
15922 ) {
15923 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15924 }
15925
15926 pub fn expand_excerpts_for_direction(
15927 &mut self,
15928 lines: u32,
15929 direction: ExpandExcerptDirection,
15930
15931 cx: &mut Context<Self>,
15932 ) {
15933 let selections = self.selections.disjoint_anchors_arc();
15934
15935 let lines = if lines == 0 {
15936 EditorSettings::get_global(cx).expand_excerpt_lines
15937 } else {
15938 lines
15939 };
15940
15941 self.buffer.update(cx, |buffer, cx| {
15942 let snapshot = buffer.snapshot(cx);
15943 let mut excerpt_ids = selections
15944 .iter()
15945 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15946 .collect::<Vec<_>>();
15947 excerpt_ids.sort();
15948 excerpt_ids.dedup();
15949 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15950 })
15951 }
15952
15953 pub fn expand_excerpt(
15954 &mut self,
15955 excerpt: ExcerptId,
15956 direction: ExpandExcerptDirection,
15957 window: &mut Window,
15958 cx: &mut Context<Self>,
15959 ) {
15960 let current_scroll_position = self.scroll_position(cx);
15961 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15962 let mut should_scroll_up = false;
15963
15964 if direction == ExpandExcerptDirection::Down {
15965 let multi_buffer = self.buffer.read(cx);
15966 let snapshot = multi_buffer.snapshot(cx);
15967 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15968 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15969 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
15970 {
15971 let buffer_snapshot = buffer.read(cx).snapshot();
15972 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15973 let last_row = buffer_snapshot.max_point().row;
15974 let lines_below = last_row.saturating_sub(excerpt_end_row);
15975 should_scroll_up = lines_below >= lines_to_expand;
15976 }
15977 }
15978
15979 self.buffer.update(cx, |buffer, cx| {
15980 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15981 });
15982
15983 if should_scroll_up {
15984 let new_scroll_position =
15985 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as ScrollOffset);
15986 self.set_scroll_position(new_scroll_position, window, cx);
15987 }
15988 }
15989
15990 pub fn go_to_singleton_buffer_point(
15991 &mut self,
15992 point: Point,
15993 window: &mut Window,
15994 cx: &mut Context<Self>,
15995 ) {
15996 self.go_to_singleton_buffer_range(point..point, window, cx);
15997 }
15998
15999 pub fn go_to_singleton_buffer_range(
16000 &mut self,
16001 range: Range<Point>,
16002 window: &mut Window,
16003 cx: &mut Context<Self>,
16004 ) {
16005 let multibuffer = self.buffer().read(cx);
16006 let Some(buffer) = multibuffer.as_singleton() else {
16007 return;
16008 };
16009 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16010 return;
16011 };
16012 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16013 return;
16014 };
16015 self.change_selections(
16016 SelectionEffects::default().nav_history(true),
16017 window,
16018 cx,
16019 |s| s.select_anchor_ranges([start..end]),
16020 );
16021 }
16022
16023 pub fn go_to_diagnostic(
16024 &mut self,
16025 action: &GoToDiagnostic,
16026 window: &mut Window,
16027 cx: &mut Context<Self>,
16028 ) {
16029 if !self.diagnostics_enabled() {
16030 return;
16031 }
16032 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16033 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16034 }
16035
16036 pub fn go_to_prev_diagnostic(
16037 &mut self,
16038 action: &GoToPreviousDiagnostic,
16039 window: &mut Window,
16040 cx: &mut Context<Self>,
16041 ) {
16042 if !self.diagnostics_enabled() {
16043 return;
16044 }
16045 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16046 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16047 }
16048
16049 pub fn go_to_diagnostic_impl(
16050 &mut self,
16051 direction: Direction,
16052 severity: GoToDiagnosticSeverityFilter,
16053 window: &mut Window,
16054 cx: &mut Context<Self>,
16055 ) {
16056 let buffer = self.buffer.read(cx).snapshot(cx);
16057 let selection = self.selections.newest::<usize>(cx);
16058
16059 let mut active_group_id = None;
16060 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16061 && active_group.active_range.start.to_offset(&buffer) == selection.start
16062 {
16063 active_group_id = Some(active_group.group_id);
16064 }
16065
16066 fn filtered<'a>(
16067 snapshot: EditorSnapshot,
16068 severity: GoToDiagnosticSeverityFilter,
16069 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
16070 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
16071 diagnostics
16072 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16073 .filter(|entry| entry.range.start != entry.range.end)
16074 .filter(|entry| !entry.diagnostic.is_unnecessary)
16075 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
16076 }
16077
16078 let snapshot = self.snapshot(window, cx);
16079 let before = filtered(
16080 snapshot.clone(),
16081 severity,
16082 buffer
16083 .diagnostics_in_range(0..selection.start)
16084 .filter(|entry| entry.range.start <= selection.start),
16085 );
16086 let after = filtered(
16087 snapshot,
16088 severity,
16089 buffer
16090 .diagnostics_in_range(selection.start..buffer.len())
16091 .filter(|entry| entry.range.start >= selection.start),
16092 );
16093
16094 let mut found: Option<DiagnosticEntryRef<usize>> = None;
16095 if direction == Direction::Prev {
16096 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16097 {
16098 for diagnostic in prev_diagnostics.into_iter().rev() {
16099 if diagnostic.range.start != selection.start
16100 || active_group_id
16101 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16102 {
16103 found = Some(diagnostic);
16104 break 'outer;
16105 }
16106 }
16107 }
16108 } else {
16109 for diagnostic in after.chain(before) {
16110 if diagnostic.range.start != selection.start
16111 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16112 {
16113 found = Some(diagnostic);
16114 break;
16115 }
16116 }
16117 }
16118 let Some(next_diagnostic) = found else {
16119 return;
16120 };
16121
16122 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16123 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16124 return;
16125 };
16126 self.change_selections(Default::default(), window, cx, |s| {
16127 s.select_ranges(vec![
16128 next_diagnostic.range.start..next_diagnostic.range.start,
16129 ])
16130 });
16131 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16132 self.refresh_edit_prediction(false, true, window, cx);
16133 }
16134
16135 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16136 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16137 let snapshot = self.snapshot(window, cx);
16138 let selection = self.selections.newest::<Point>(cx);
16139 self.go_to_hunk_before_or_after_position(
16140 &snapshot,
16141 selection.head(),
16142 Direction::Next,
16143 window,
16144 cx,
16145 );
16146 }
16147
16148 pub fn go_to_hunk_before_or_after_position(
16149 &mut self,
16150 snapshot: &EditorSnapshot,
16151 position: Point,
16152 direction: Direction,
16153 window: &mut Window,
16154 cx: &mut Context<Editor>,
16155 ) {
16156 let row = if direction == Direction::Next {
16157 self.hunk_after_position(snapshot, position)
16158 .map(|hunk| hunk.row_range.start)
16159 } else {
16160 self.hunk_before_position(snapshot, position)
16161 };
16162
16163 if let Some(row) = row {
16164 let destination = Point::new(row.0, 0);
16165 let autoscroll = Autoscroll::center();
16166
16167 self.unfold_ranges(&[destination..destination], false, false, cx);
16168 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16169 s.select_ranges([destination..destination]);
16170 });
16171 }
16172 }
16173
16174 fn hunk_after_position(
16175 &mut self,
16176 snapshot: &EditorSnapshot,
16177 position: Point,
16178 ) -> Option<MultiBufferDiffHunk> {
16179 snapshot
16180 .buffer_snapshot()
16181 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16182 .find(|hunk| hunk.row_range.start.0 > position.row)
16183 .or_else(|| {
16184 snapshot
16185 .buffer_snapshot()
16186 .diff_hunks_in_range(Point::zero()..position)
16187 .find(|hunk| hunk.row_range.end.0 < position.row)
16188 })
16189 }
16190
16191 fn go_to_prev_hunk(
16192 &mut self,
16193 _: &GoToPreviousHunk,
16194 window: &mut Window,
16195 cx: &mut Context<Self>,
16196 ) {
16197 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16198 let snapshot = self.snapshot(window, cx);
16199 let selection = self.selections.newest::<Point>(cx);
16200 self.go_to_hunk_before_or_after_position(
16201 &snapshot,
16202 selection.head(),
16203 Direction::Prev,
16204 window,
16205 cx,
16206 );
16207 }
16208
16209 fn hunk_before_position(
16210 &mut self,
16211 snapshot: &EditorSnapshot,
16212 position: Point,
16213 ) -> Option<MultiBufferRow> {
16214 snapshot
16215 .buffer_snapshot()
16216 .diff_hunk_before(position)
16217 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16218 }
16219
16220 fn go_to_next_change(
16221 &mut self,
16222 _: &GoToNextChange,
16223 window: &mut Window,
16224 cx: &mut Context<Self>,
16225 ) {
16226 if let Some(selections) = self
16227 .change_list
16228 .next_change(1, Direction::Next)
16229 .map(|s| s.to_vec())
16230 {
16231 self.change_selections(Default::default(), window, cx, |s| {
16232 let map = s.display_map();
16233 s.select_display_ranges(selections.iter().map(|a| {
16234 let point = a.to_display_point(&map);
16235 point..point
16236 }))
16237 })
16238 }
16239 }
16240
16241 fn go_to_previous_change(
16242 &mut self,
16243 _: &GoToPreviousChange,
16244 window: &mut Window,
16245 cx: &mut Context<Self>,
16246 ) {
16247 if let Some(selections) = self
16248 .change_list
16249 .next_change(1, Direction::Prev)
16250 .map(|s| s.to_vec())
16251 {
16252 self.change_selections(Default::default(), window, cx, |s| {
16253 let map = s.display_map();
16254 s.select_display_ranges(selections.iter().map(|a| {
16255 let point = a.to_display_point(&map);
16256 point..point
16257 }))
16258 })
16259 }
16260 }
16261
16262 pub fn go_to_next_document_highlight(
16263 &mut self,
16264 _: &GoToNextDocumentHighlight,
16265 window: &mut Window,
16266 cx: &mut Context<Self>,
16267 ) {
16268 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16269 }
16270
16271 pub fn go_to_prev_document_highlight(
16272 &mut self,
16273 _: &GoToPreviousDocumentHighlight,
16274 window: &mut Window,
16275 cx: &mut Context<Self>,
16276 ) {
16277 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16278 }
16279
16280 pub fn go_to_document_highlight_before_or_after_position(
16281 &mut self,
16282 direction: Direction,
16283 window: &mut Window,
16284 cx: &mut Context<Editor>,
16285 ) {
16286 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16287 let snapshot = self.snapshot(window, cx);
16288 let buffer = &snapshot.buffer_snapshot();
16289 let position = self.selections.newest::<Point>(cx).head();
16290 let anchor_position = buffer.anchor_after(position);
16291
16292 // Get all document highlights (both read and write)
16293 let mut all_highlights = Vec::new();
16294
16295 if let Some((_, read_highlights)) = self
16296 .background_highlights
16297 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16298 {
16299 all_highlights.extend(read_highlights.iter());
16300 }
16301
16302 if let Some((_, write_highlights)) = self
16303 .background_highlights
16304 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16305 {
16306 all_highlights.extend(write_highlights.iter());
16307 }
16308
16309 if all_highlights.is_empty() {
16310 return;
16311 }
16312
16313 // Sort highlights by position
16314 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16315
16316 let target_highlight = match direction {
16317 Direction::Next => {
16318 // Find the first highlight after the current position
16319 all_highlights
16320 .iter()
16321 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16322 }
16323 Direction::Prev => {
16324 // Find the last highlight before the current position
16325 all_highlights
16326 .iter()
16327 .rev()
16328 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16329 }
16330 };
16331
16332 if let Some(highlight) = target_highlight {
16333 let destination = highlight.start.to_point(buffer);
16334 let autoscroll = Autoscroll::center();
16335
16336 self.unfold_ranges(&[destination..destination], false, false, cx);
16337 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16338 s.select_ranges([destination..destination]);
16339 });
16340 }
16341 }
16342
16343 fn go_to_line<T: 'static>(
16344 &mut self,
16345 position: Anchor,
16346 highlight_color: Option<Hsla>,
16347 window: &mut Window,
16348 cx: &mut Context<Self>,
16349 ) {
16350 let snapshot = self.snapshot(window, cx).display_snapshot;
16351 let position = position.to_point(&snapshot.buffer_snapshot());
16352 let start = snapshot
16353 .buffer_snapshot()
16354 .clip_point(Point::new(position.row, 0), Bias::Left);
16355 let end = start + Point::new(1, 0);
16356 let start = snapshot.buffer_snapshot().anchor_before(start);
16357 let end = snapshot.buffer_snapshot().anchor_before(end);
16358
16359 self.highlight_rows::<T>(
16360 start..end,
16361 highlight_color
16362 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16363 Default::default(),
16364 cx,
16365 );
16366
16367 if self.buffer.read(cx).is_singleton() {
16368 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16369 }
16370 }
16371
16372 pub fn go_to_definition(
16373 &mut self,
16374 _: &GoToDefinition,
16375 window: &mut Window,
16376 cx: &mut Context<Self>,
16377 ) -> Task<Result<Navigated>> {
16378 let definition =
16379 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16380 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16381 cx.spawn_in(window, async move |editor, cx| {
16382 if definition.await? == Navigated::Yes {
16383 return Ok(Navigated::Yes);
16384 }
16385 match fallback_strategy {
16386 GoToDefinitionFallback::None => Ok(Navigated::No),
16387 GoToDefinitionFallback::FindAllReferences => {
16388 match editor.update_in(cx, |editor, window, cx| {
16389 editor.find_all_references(&FindAllReferences, window, cx)
16390 })? {
16391 Some(references) => references.await,
16392 None => Ok(Navigated::No),
16393 }
16394 }
16395 }
16396 })
16397 }
16398
16399 pub fn go_to_declaration(
16400 &mut self,
16401 _: &GoToDeclaration,
16402 window: &mut Window,
16403 cx: &mut Context<Self>,
16404 ) -> Task<Result<Navigated>> {
16405 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16406 }
16407
16408 pub fn go_to_declaration_split(
16409 &mut self,
16410 _: &GoToDeclaration,
16411 window: &mut Window,
16412 cx: &mut Context<Self>,
16413 ) -> Task<Result<Navigated>> {
16414 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16415 }
16416
16417 pub fn go_to_implementation(
16418 &mut self,
16419 _: &GoToImplementation,
16420 window: &mut Window,
16421 cx: &mut Context<Self>,
16422 ) -> Task<Result<Navigated>> {
16423 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16424 }
16425
16426 pub fn go_to_implementation_split(
16427 &mut self,
16428 _: &GoToImplementationSplit,
16429 window: &mut Window,
16430 cx: &mut Context<Self>,
16431 ) -> Task<Result<Navigated>> {
16432 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16433 }
16434
16435 pub fn go_to_type_definition(
16436 &mut self,
16437 _: &GoToTypeDefinition,
16438 window: &mut Window,
16439 cx: &mut Context<Self>,
16440 ) -> Task<Result<Navigated>> {
16441 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16442 }
16443
16444 pub fn go_to_definition_split(
16445 &mut self,
16446 _: &GoToDefinitionSplit,
16447 window: &mut Window,
16448 cx: &mut Context<Self>,
16449 ) -> Task<Result<Navigated>> {
16450 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16451 }
16452
16453 pub fn go_to_type_definition_split(
16454 &mut self,
16455 _: &GoToTypeDefinitionSplit,
16456 window: &mut Window,
16457 cx: &mut Context<Self>,
16458 ) -> Task<Result<Navigated>> {
16459 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16460 }
16461
16462 fn go_to_definition_of_kind(
16463 &mut self,
16464 kind: GotoDefinitionKind,
16465 split: bool,
16466 window: &mut Window,
16467 cx: &mut Context<Self>,
16468 ) -> Task<Result<Navigated>> {
16469 let Some(provider) = self.semantics_provider.clone() else {
16470 return Task::ready(Ok(Navigated::No));
16471 };
16472 let head = self.selections.newest::<usize>(cx).head();
16473 let buffer = self.buffer.read(cx);
16474 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16475 return Task::ready(Ok(Navigated::No));
16476 };
16477 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16478 return Task::ready(Ok(Navigated::No));
16479 };
16480
16481 cx.spawn_in(window, async move |editor, cx| {
16482 let Some(definitions) = definitions.await? else {
16483 return Ok(Navigated::No);
16484 };
16485 let navigated = editor
16486 .update_in(cx, |editor, window, cx| {
16487 editor.navigate_to_hover_links(
16488 Some(kind),
16489 definitions
16490 .into_iter()
16491 .filter(|location| {
16492 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16493 })
16494 .map(HoverLink::Text)
16495 .collect::<Vec<_>>(),
16496 split,
16497 window,
16498 cx,
16499 )
16500 })?
16501 .await?;
16502 anyhow::Ok(navigated)
16503 })
16504 }
16505
16506 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16507 let selection = self.selections.newest_anchor();
16508 let head = selection.head();
16509 let tail = selection.tail();
16510
16511 let Some((buffer, start_position)) =
16512 self.buffer.read(cx).text_anchor_for_position(head, cx)
16513 else {
16514 return;
16515 };
16516
16517 let end_position = if head != tail {
16518 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16519 return;
16520 };
16521 Some(pos)
16522 } else {
16523 None
16524 };
16525
16526 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16527 let url = if let Some(end_pos) = end_position {
16528 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16529 } else {
16530 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16531 };
16532
16533 if let Some(url) = url {
16534 cx.update(|window, cx| {
16535 if parse_zed_link(&url, cx).is_some() {
16536 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16537 } else {
16538 cx.open_url(&url);
16539 }
16540 })?;
16541 }
16542
16543 anyhow::Ok(())
16544 });
16545
16546 url_finder.detach();
16547 }
16548
16549 pub fn open_selected_filename(
16550 &mut self,
16551 _: &OpenSelectedFilename,
16552 window: &mut Window,
16553 cx: &mut Context<Self>,
16554 ) {
16555 let Some(workspace) = self.workspace() else {
16556 return;
16557 };
16558
16559 let position = self.selections.newest_anchor().head();
16560
16561 let Some((buffer, buffer_position)) =
16562 self.buffer.read(cx).text_anchor_for_position(position, cx)
16563 else {
16564 return;
16565 };
16566
16567 let project = self.project.clone();
16568
16569 cx.spawn_in(window, async move |_, cx| {
16570 let result = find_file(&buffer, project, buffer_position, cx).await;
16571
16572 if let Some((_, path)) = result {
16573 workspace
16574 .update_in(cx, |workspace, window, cx| {
16575 workspace.open_resolved_path(path, window, cx)
16576 })?
16577 .await?;
16578 }
16579 anyhow::Ok(())
16580 })
16581 .detach();
16582 }
16583
16584 pub(crate) fn navigate_to_hover_links(
16585 &mut self,
16586 kind: Option<GotoDefinitionKind>,
16587 definitions: Vec<HoverLink>,
16588 split: bool,
16589 window: &mut Window,
16590 cx: &mut Context<Editor>,
16591 ) -> Task<Result<Navigated>> {
16592 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16593 let mut first_url_or_file = None;
16594 let definitions: Vec<_> = definitions
16595 .into_iter()
16596 .filter_map(|def| match def {
16597 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16598 HoverLink::InlayHint(lsp_location, server_id) => {
16599 let computation =
16600 self.compute_target_location(lsp_location, server_id, window, cx);
16601 Some(cx.background_spawn(computation))
16602 }
16603 HoverLink::Url(url) => {
16604 first_url_or_file = Some(Either::Left(url));
16605 None
16606 }
16607 HoverLink::File(path) => {
16608 first_url_or_file = Some(Either::Right(path));
16609 None
16610 }
16611 })
16612 .collect();
16613
16614 let workspace = self.workspace();
16615
16616 cx.spawn_in(window, async move |editor, cx| {
16617 let locations: Vec<Location> = future::join_all(definitions)
16618 .await
16619 .into_iter()
16620 .filter_map(|location| location.transpose())
16621 .collect::<Result<_>>()
16622 .context("location tasks")?;
16623 let mut locations = cx.update(|_, cx| {
16624 locations
16625 .into_iter()
16626 .map(|location| {
16627 let buffer = location.buffer.read(cx);
16628 (location.buffer, location.range.to_point(buffer))
16629 })
16630 .into_group_map()
16631 })?;
16632 let mut num_locations = 0;
16633 for ranges in locations.values_mut() {
16634 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16635 ranges.dedup();
16636 num_locations += ranges.len();
16637 }
16638
16639 if num_locations > 1 {
16640 let Some(workspace) = workspace else {
16641 return Ok(Navigated::No);
16642 };
16643
16644 let tab_kind = match kind {
16645 Some(GotoDefinitionKind::Implementation) => "Implementations",
16646 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16647 Some(GotoDefinitionKind::Declaration) => "Declarations",
16648 Some(GotoDefinitionKind::Type) => "Types",
16649 };
16650 let title = editor
16651 .update_in(cx, |_, _, cx| {
16652 let target = locations
16653 .iter()
16654 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16655 .map(|(buffer, location)| {
16656 buffer
16657 .read(cx)
16658 .text_for_range(location.clone())
16659 .collect::<String>()
16660 })
16661 .filter(|text| !text.contains('\n'))
16662 .unique()
16663 .take(3)
16664 .join(", ");
16665 if target.is_empty() {
16666 tab_kind.to_owned()
16667 } else {
16668 format!("{tab_kind} for {target}")
16669 }
16670 })
16671 .context("buffer title")?;
16672
16673 let opened = workspace
16674 .update_in(cx, |workspace, window, cx| {
16675 Self::open_locations_in_multibuffer(
16676 workspace,
16677 locations,
16678 title,
16679 split,
16680 MultibufferSelectionMode::First,
16681 window,
16682 cx,
16683 )
16684 })
16685 .is_ok();
16686
16687 anyhow::Ok(Navigated::from_bool(opened))
16688 } else if num_locations == 0 {
16689 // If there is one url or file, open it directly
16690 match first_url_or_file {
16691 Some(Either::Left(url)) => {
16692 cx.update(|_, cx| cx.open_url(&url))?;
16693 Ok(Navigated::Yes)
16694 }
16695 Some(Either::Right(path)) => {
16696 let Some(workspace) = workspace else {
16697 return Ok(Navigated::No);
16698 };
16699
16700 workspace
16701 .update_in(cx, |workspace, window, cx| {
16702 workspace.open_resolved_path(path, window, cx)
16703 })?
16704 .await?;
16705 Ok(Navigated::Yes)
16706 }
16707 None => Ok(Navigated::No),
16708 }
16709 } else {
16710 let Some(workspace) = workspace else {
16711 return Ok(Navigated::No);
16712 };
16713
16714 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16715 let target_range = target_ranges.first().unwrap().clone();
16716
16717 editor.update_in(cx, |editor, window, cx| {
16718 let range = target_range.to_point(target_buffer.read(cx));
16719 let range = editor.range_for_match(&range);
16720 let range = collapse_multiline_range(range);
16721
16722 if !split
16723 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16724 {
16725 editor.go_to_singleton_buffer_range(range, window, cx);
16726 } else {
16727 let pane = workspace.read(cx).active_pane().clone();
16728 window.defer(cx, move |window, cx| {
16729 let target_editor: Entity<Self> =
16730 workspace.update(cx, |workspace, cx| {
16731 let pane = if split {
16732 workspace.adjacent_pane(window, cx)
16733 } else {
16734 workspace.active_pane().clone()
16735 };
16736
16737 workspace.open_project_item(
16738 pane,
16739 target_buffer.clone(),
16740 true,
16741 true,
16742 window,
16743 cx,
16744 )
16745 });
16746 target_editor.update(cx, |target_editor, cx| {
16747 // When selecting a definition in a different buffer, disable the nav history
16748 // to avoid creating a history entry at the previous cursor location.
16749 pane.update(cx, |pane, _| pane.disable_history());
16750 target_editor.go_to_singleton_buffer_range(range, window, cx);
16751 pane.update(cx, |pane, _| pane.enable_history());
16752 });
16753 });
16754 }
16755 Navigated::Yes
16756 })
16757 }
16758 })
16759 }
16760
16761 fn compute_target_location(
16762 &self,
16763 lsp_location: lsp::Location,
16764 server_id: LanguageServerId,
16765 window: &mut Window,
16766 cx: &mut Context<Self>,
16767 ) -> Task<anyhow::Result<Option<Location>>> {
16768 let Some(project) = self.project.clone() else {
16769 return Task::ready(Ok(None));
16770 };
16771
16772 cx.spawn_in(window, async move |editor, cx| {
16773 let location_task = editor.update(cx, |_, cx| {
16774 project.update(cx, |project, cx| {
16775 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16776 })
16777 })?;
16778 let location = Some({
16779 let target_buffer_handle = location_task.await.context("open local buffer")?;
16780 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16781 let target_start = target_buffer
16782 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16783 let target_end = target_buffer
16784 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16785 target_buffer.anchor_after(target_start)
16786 ..target_buffer.anchor_before(target_end)
16787 })?;
16788 Location {
16789 buffer: target_buffer_handle,
16790 range,
16791 }
16792 });
16793 Ok(location)
16794 })
16795 }
16796
16797 pub fn find_all_references(
16798 &mut self,
16799 _: &FindAllReferences,
16800 window: &mut Window,
16801 cx: &mut Context<Self>,
16802 ) -> Option<Task<Result<Navigated>>> {
16803 let selection = self.selections.newest::<usize>(cx);
16804 let multi_buffer = self.buffer.read(cx);
16805 let head = selection.head();
16806
16807 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16808 let head_anchor = multi_buffer_snapshot.anchor_at(
16809 head,
16810 if head < selection.tail() {
16811 Bias::Right
16812 } else {
16813 Bias::Left
16814 },
16815 );
16816
16817 match self
16818 .find_all_references_task_sources
16819 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16820 {
16821 Ok(_) => {
16822 log::info!(
16823 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16824 );
16825 return None;
16826 }
16827 Err(i) => {
16828 self.find_all_references_task_sources.insert(i, head_anchor);
16829 }
16830 }
16831
16832 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16833 let workspace = self.workspace()?;
16834 let project = workspace.read(cx).project().clone();
16835 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16836 Some(cx.spawn_in(window, async move |editor, cx| {
16837 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16838 if let Ok(i) = editor
16839 .find_all_references_task_sources
16840 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16841 {
16842 editor.find_all_references_task_sources.remove(i);
16843 }
16844 });
16845
16846 let Some(locations) = references.await? else {
16847 return anyhow::Ok(Navigated::No);
16848 };
16849 let mut locations = cx.update(|_, cx| {
16850 locations
16851 .into_iter()
16852 .map(|location| {
16853 let buffer = location.buffer.read(cx);
16854 (location.buffer, location.range.to_point(buffer))
16855 })
16856 .into_group_map()
16857 })?;
16858 if locations.is_empty() {
16859 return anyhow::Ok(Navigated::No);
16860 }
16861 for ranges in locations.values_mut() {
16862 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16863 ranges.dedup();
16864 }
16865
16866 workspace.update_in(cx, |workspace, window, cx| {
16867 let target = locations
16868 .iter()
16869 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16870 .map(|(buffer, location)| {
16871 buffer
16872 .read(cx)
16873 .text_for_range(location.clone())
16874 .collect::<String>()
16875 })
16876 .filter(|text| !text.contains('\n'))
16877 .unique()
16878 .take(3)
16879 .join(", ");
16880 let title = if target.is_empty() {
16881 "References".to_owned()
16882 } else {
16883 format!("References to {target}")
16884 };
16885 Self::open_locations_in_multibuffer(
16886 workspace,
16887 locations,
16888 title,
16889 false,
16890 MultibufferSelectionMode::First,
16891 window,
16892 cx,
16893 );
16894 Navigated::Yes
16895 })
16896 }))
16897 }
16898
16899 /// Opens a multibuffer with the given project locations in it
16900 pub fn open_locations_in_multibuffer(
16901 workspace: &mut Workspace,
16902 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
16903 title: String,
16904 split: bool,
16905 multibuffer_selection_mode: MultibufferSelectionMode,
16906 window: &mut Window,
16907 cx: &mut Context<Workspace>,
16908 ) {
16909 if locations.is_empty() {
16910 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16911 return;
16912 }
16913
16914 let capability = workspace.project().read(cx).capability();
16915 let mut ranges = <Vec<Range<Anchor>>>::new();
16916
16917 // a key to find existing multibuffer editors with the same set of locations
16918 // to prevent us from opening more and more multibuffer tabs for searches and the like
16919 let mut key = (title.clone(), vec![]);
16920 let excerpt_buffer = cx.new(|cx| {
16921 let key = &mut key.1;
16922 let mut multibuffer = MultiBuffer::new(capability);
16923 for (buffer, mut ranges_for_buffer) in locations {
16924 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16925 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
16926 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16927 PathKey::for_buffer(&buffer, cx),
16928 buffer.clone(),
16929 ranges_for_buffer,
16930 multibuffer_context_lines(cx),
16931 cx,
16932 );
16933 ranges.extend(new_ranges)
16934 }
16935
16936 multibuffer.with_title(title)
16937 });
16938 let existing = workspace.active_pane().update(cx, |pane, cx| {
16939 pane.items()
16940 .filter_map(|item| item.downcast::<Editor>())
16941 .find(|editor| {
16942 editor
16943 .read(cx)
16944 .lookup_key
16945 .as_ref()
16946 .and_then(|it| {
16947 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
16948 })
16949 .is_some_and(|it| *it == key)
16950 })
16951 });
16952 let editor = existing.unwrap_or_else(|| {
16953 cx.new(|cx| {
16954 let mut editor = Editor::for_multibuffer(
16955 excerpt_buffer,
16956 Some(workspace.project().clone()),
16957 window,
16958 cx,
16959 );
16960 editor.lookup_key = Some(Box::new(key));
16961 editor
16962 })
16963 });
16964 editor.update(cx, |editor, cx| {
16965 match multibuffer_selection_mode {
16966 MultibufferSelectionMode::First => {
16967 if let Some(first_range) = ranges.first() {
16968 editor.change_selections(
16969 SelectionEffects::no_scroll(),
16970 window,
16971 cx,
16972 |selections| {
16973 selections.clear_disjoint();
16974 selections
16975 .select_anchor_ranges(std::iter::once(first_range.clone()));
16976 },
16977 );
16978 }
16979 editor.highlight_background::<Self>(
16980 &ranges,
16981 |theme| theme.colors().editor_highlighted_line_background,
16982 cx,
16983 );
16984 }
16985 MultibufferSelectionMode::All => {
16986 editor.change_selections(
16987 SelectionEffects::no_scroll(),
16988 window,
16989 cx,
16990 |selections| {
16991 selections.clear_disjoint();
16992 selections.select_anchor_ranges(ranges);
16993 },
16994 );
16995 }
16996 }
16997 editor.register_buffers_with_language_servers(cx);
16998 });
16999
17000 let item = Box::new(editor);
17001 let item_id = item.item_id();
17002
17003 if split {
17004 let pane = workspace.adjacent_pane(window, cx);
17005 workspace.add_item(pane, item, None, true, true, window, cx);
17006 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17007 let (preview_item_id, preview_item_idx) =
17008 workspace.active_pane().read_with(cx, |pane, _| {
17009 (pane.preview_item_id(), pane.preview_item_idx())
17010 });
17011
17012 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17013
17014 if let Some(preview_item_id) = preview_item_id {
17015 workspace.active_pane().update(cx, |pane, cx| {
17016 pane.remove_item(preview_item_id, false, false, window, cx);
17017 });
17018 }
17019 } else {
17020 workspace.add_item_to_active_pane(item, None, true, window, cx);
17021 }
17022 workspace.active_pane().update(cx, |pane, cx| {
17023 pane.set_preview_item_id(Some(item_id), cx);
17024 });
17025 }
17026
17027 pub fn rename(
17028 &mut self,
17029 _: &Rename,
17030 window: &mut Window,
17031 cx: &mut Context<Self>,
17032 ) -> Option<Task<Result<()>>> {
17033 use language::ToOffset as _;
17034
17035 let provider = self.semantics_provider.clone()?;
17036 let selection = self.selections.newest_anchor().clone();
17037 let (cursor_buffer, cursor_buffer_position) = self
17038 .buffer
17039 .read(cx)
17040 .text_anchor_for_position(selection.head(), cx)?;
17041 let (tail_buffer, cursor_buffer_position_end) = self
17042 .buffer
17043 .read(cx)
17044 .text_anchor_for_position(selection.tail(), cx)?;
17045 if tail_buffer != cursor_buffer {
17046 return None;
17047 }
17048
17049 let snapshot = cursor_buffer.read(cx).snapshot();
17050 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17051 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17052 let prepare_rename = provider
17053 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17054 .unwrap_or_else(|| Task::ready(Ok(None)));
17055 drop(snapshot);
17056
17057 Some(cx.spawn_in(window, async move |this, cx| {
17058 let rename_range = if let Some(range) = prepare_rename.await? {
17059 Some(range)
17060 } else {
17061 this.update(cx, |this, cx| {
17062 let buffer = this.buffer.read(cx).snapshot(cx);
17063 let mut buffer_highlights = this
17064 .document_highlights_for_position(selection.head(), &buffer)
17065 .filter(|highlight| {
17066 highlight.start.excerpt_id == selection.head().excerpt_id
17067 && highlight.end.excerpt_id == selection.head().excerpt_id
17068 });
17069 buffer_highlights
17070 .next()
17071 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17072 })?
17073 };
17074 if let Some(rename_range) = rename_range {
17075 this.update_in(cx, |this, window, cx| {
17076 let snapshot = cursor_buffer.read(cx).snapshot();
17077 let rename_buffer_range = rename_range.to_offset(&snapshot);
17078 let cursor_offset_in_rename_range =
17079 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17080 let cursor_offset_in_rename_range_end =
17081 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17082
17083 this.take_rename(false, window, cx);
17084 let buffer = this.buffer.read(cx).read(cx);
17085 let cursor_offset = selection.head().to_offset(&buffer);
17086 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17087 let rename_end = rename_start + rename_buffer_range.len();
17088 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17089 let mut old_highlight_id = None;
17090 let old_name: Arc<str> = buffer
17091 .chunks(rename_start..rename_end, true)
17092 .map(|chunk| {
17093 if old_highlight_id.is_none() {
17094 old_highlight_id = chunk.syntax_highlight_id;
17095 }
17096 chunk.text
17097 })
17098 .collect::<String>()
17099 .into();
17100
17101 drop(buffer);
17102
17103 // Position the selection in the rename editor so that it matches the current selection.
17104 this.show_local_selections = false;
17105 let rename_editor = cx.new(|cx| {
17106 let mut editor = Editor::single_line(window, cx);
17107 editor.buffer.update(cx, |buffer, cx| {
17108 buffer.edit([(0..0, old_name.clone())], None, cx)
17109 });
17110 let rename_selection_range = match cursor_offset_in_rename_range
17111 .cmp(&cursor_offset_in_rename_range_end)
17112 {
17113 Ordering::Equal => {
17114 editor.select_all(&SelectAll, window, cx);
17115 return editor;
17116 }
17117 Ordering::Less => {
17118 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17119 }
17120 Ordering::Greater => {
17121 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17122 }
17123 };
17124 if rename_selection_range.end > old_name.len() {
17125 editor.select_all(&SelectAll, window, cx);
17126 } else {
17127 editor.change_selections(Default::default(), window, cx, |s| {
17128 s.select_ranges([rename_selection_range]);
17129 });
17130 }
17131 editor
17132 });
17133 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17134 if e == &EditorEvent::Focused {
17135 cx.emit(EditorEvent::FocusedIn)
17136 }
17137 })
17138 .detach();
17139
17140 let write_highlights =
17141 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17142 let read_highlights =
17143 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17144 let ranges = write_highlights
17145 .iter()
17146 .flat_map(|(_, ranges)| ranges.iter())
17147 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17148 .cloned()
17149 .collect();
17150
17151 this.highlight_text::<Rename>(
17152 ranges,
17153 HighlightStyle {
17154 fade_out: Some(0.6),
17155 ..Default::default()
17156 },
17157 cx,
17158 );
17159 let rename_focus_handle = rename_editor.focus_handle(cx);
17160 window.focus(&rename_focus_handle);
17161 let block_id = this.insert_blocks(
17162 [BlockProperties {
17163 style: BlockStyle::Flex,
17164 placement: BlockPlacement::Below(range.start),
17165 height: Some(1),
17166 render: Arc::new({
17167 let rename_editor = rename_editor.clone();
17168 move |cx: &mut BlockContext| {
17169 let mut text_style = cx.editor_style.text.clone();
17170 if let Some(highlight_style) = old_highlight_id
17171 .and_then(|h| h.style(&cx.editor_style.syntax))
17172 {
17173 text_style = text_style.highlight(highlight_style);
17174 }
17175 div()
17176 .block_mouse_except_scroll()
17177 .pl(cx.anchor_x)
17178 .child(EditorElement::new(
17179 &rename_editor,
17180 EditorStyle {
17181 background: cx.theme().system().transparent,
17182 local_player: cx.editor_style.local_player,
17183 text: text_style,
17184 scrollbar_width: cx.editor_style.scrollbar_width,
17185 syntax: cx.editor_style.syntax.clone(),
17186 status: cx.editor_style.status.clone(),
17187 inlay_hints_style: HighlightStyle {
17188 font_weight: Some(FontWeight::BOLD),
17189 ..make_inlay_hints_style(cx.app)
17190 },
17191 edit_prediction_styles: make_suggestion_styles(
17192 cx.app,
17193 ),
17194 ..EditorStyle::default()
17195 },
17196 ))
17197 .into_any_element()
17198 }
17199 }),
17200 priority: 0,
17201 }],
17202 Some(Autoscroll::fit()),
17203 cx,
17204 )[0];
17205 this.pending_rename = Some(RenameState {
17206 range,
17207 old_name,
17208 editor: rename_editor,
17209 block_id,
17210 });
17211 })?;
17212 }
17213
17214 Ok(())
17215 }))
17216 }
17217
17218 pub fn confirm_rename(
17219 &mut self,
17220 _: &ConfirmRename,
17221 window: &mut Window,
17222 cx: &mut Context<Self>,
17223 ) -> Option<Task<Result<()>>> {
17224 let rename = self.take_rename(false, window, cx)?;
17225 let workspace = self.workspace()?.downgrade();
17226 let (buffer, start) = self
17227 .buffer
17228 .read(cx)
17229 .text_anchor_for_position(rename.range.start, cx)?;
17230 let (end_buffer, _) = self
17231 .buffer
17232 .read(cx)
17233 .text_anchor_for_position(rename.range.end, cx)?;
17234 if buffer != end_buffer {
17235 return None;
17236 }
17237
17238 let old_name = rename.old_name;
17239 let new_name = rename.editor.read(cx).text(cx);
17240
17241 let rename = self.semantics_provider.as_ref()?.perform_rename(
17242 &buffer,
17243 start,
17244 new_name.clone(),
17245 cx,
17246 )?;
17247
17248 Some(cx.spawn_in(window, async move |editor, cx| {
17249 let project_transaction = rename.await?;
17250 Self::open_project_transaction(
17251 &editor,
17252 workspace,
17253 project_transaction,
17254 format!("Rename: {} → {}", old_name, new_name),
17255 cx,
17256 )
17257 .await?;
17258
17259 editor.update(cx, |editor, cx| {
17260 editor.refresh_document_highlights(cx);
17261 })?;
17262 Ok(())
17263 }))
17264 }
17265
17266 fn take_rename(
17267 &mut self,
17268 moving_cursor: bool,
17269 window: &mut Window,
17270 cx: &mut Context<Self>,
17271 ) -> Option<RenameState> {
17272 let rename = self.pending_rename.take()?;
17273 if rename.editor.focus_handle(cx).is_focused(window) {
17274 window.focus(&self.focus_handle);
17275 }
17276
17277 self.remove_blocks(
17278 [rename.block_id].into_iter().collect(),
17279 Some(Autoscroll::fit()),
17280 cx,
17281 );
17282 self.clear_highlights::<Rename>(cx);
17283 self.show_local_selections = true;
17284
17285 if moving_cursor {
17286 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17287 editor.selections.newest::<usize>(cx).head()
17288 });
17289
17290 // Update the selection to match the position of the selection inside
17291 // the rename editor.
17292 let snapshot = self.buffer.read(cx).read(cx);
17293 let rename_range = rename.range.to_offset(&snapshot);
17294 let cursor_in_editor = snapshot
17295 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17296 .min(rename_range.end);
17297 drop(snapshot);
17298
17299 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17300 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17301 });
17302 } else {
17303 self.refresh_document_highlights(cx);
17304 }
17305
17306 Some(rename)
17307 }
17308
17309 pub fn pending_rename(&self) -> Option<&RenameState> {
17310 self.pending_rename.as_ref()
17311 }
17312
17313 fn format(
17314 &mut self,
17315 _: &Format,
17316 window: &mut Window,
17317 cx: &mut Context<Self>,
17318 ) -> Option<Task<Result<()>>> {
17319 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17320
17321 let project = match &self.project {
17322 Some(project) => project.clone(),
17323 None => return None,
17324 };
17325
17326 Some(self.perform_format(
17327 project,
17328 FormatTrigger::Manual,
17329 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17330 window,
17331 cx,
17332 ))
17333 }
17334
17335 fn format_selections(
17336 &mut self,
17337 _: &FormatSelections,
17338 window: &mut Window,
17339 cx: &mut Context<Self>,
17340 ) -> Option<Task<Result<()>>> {
17341 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17342
17343 let project = match &self.project {
17344 Some(project) => project.clone(),
17345 None => return None,
17346 };
17347
17348 let ranges = self
17349 .selections
17350 .all_adjusted(cx)
17351 .into_iter()
17352 .map(|selection| selection.range())
17353 .collect_vec();
17354
17355 Some(self.perform_format(
17356 project,
17357 FormatTrigger::Manual,
17358 FormatTarget::Ranges(ranges),
17359 window,
17360 cx,
17361 ))
17362 }
17363
17364 fn perform_format(
17365 &mut self,
17366 project: Entity<Project>,
17367 trigger: FormatTrigger,
17368 target: FormatTarget,
17369 window: &mut Window,
17370 cx: &mut Context<Self>,
17371 ) -> Task<Result<()>> {
17372 let buffer = self.buffer.clone();
17373 let (buffers, target) = match target {
17374 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17375 FormatTarget::Ranges(selection_ranges) => {
17376 let multi_buffer = buffer.read(cx);
17377 let snapshot = multi_buffer.read(cx);
17378 let mut buffers = HashSet::default();
17379 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17380 BTreeMap::new();
17381 for selection_range in selection_ranges {
17382 for (buffer, buffer_range, _) in
17383 snapshot.range_to_buffer_ranges(selection_range)
17384 {
17385 let buffer_id = buffer.remote_id();
17386 let start = buffer.anchor_before(buffer_range.start);
17387 let end = buffer.anchor_after(buffer_range.end);
17388 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17389 buffer_id_to_ranges
17390 .entry(buffer_id)
17391 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17392 .or_insert_with(|| vec![start..end]);
17393 }
17394 }
17395 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17396 }
17397 };
17398
17399 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17400 let selections_prev = transaction_id_prev
17401 .and_then(|transaction_id_prev| {
17402 // default to selections as they were after the last edit, if we have them,
17403 // instead of how they are now.
17404 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17405 // will take you back to where you made the last edit, instead of staying where you scrolled
17406 self.selection_history
17407 .transaction(transaction_id_prev)
17408 .map(|t| t.0.clone())
17409 })
17410 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17411
17412 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17413 let format = project.update(cx, |project, cx| {
17414 project.format(buffers, target, true, trigger, cx)
17415 });
17416
17417 cx.spawn_in(window, async move |editor, cx| {
17418 let transaction = futures::select_biased! {
17419 transaction = format.log_err().fuse() => transaction,
17420 () = timeout => {
17421 log::warn!("timed out waiting for formatting");
17422 None
17423 }
17424 };
17425
17426 buffer
17427 .update(cx, |buffer, cx| {
17428 if let Some(transaction) = transaction
17429 && !buffer.is_singleton()
17430 {
17431 buffer.push_transaction(&transaction.0, cx);
17432 }
17433 cx.notify();
17434 })
17435 .ok();
17436
17437 if let Some(transaction_id_now) =
17438 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17439 {
17440 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17441 if has_new_transaction {
17442 _ = editor.update(cx, |editor, _| {
17443 editor
17444 .selection_history
17445 .insert_transaction(transaction_id_now, selections_prev);
17446 });
17447 }
17448 }
17449
17450 Ok(())
17451 })
17452 }
17453
17454 fn organize_imports(
17455 &mut self,
17456 _: &OrganizeImports,
17457 window: &mut Window,
17458 cx: &mut Context<Self>,
17459 ) -> Option<Task<Result<()>>> {
17460 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17461 let project = match &self.project {
17462 Some(project) => project.clone(),
17463 None => return None,
17464 };
17465 Some(self.perform_code_action_kind(
17466 project,
17467 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17468 window,
17469 cx,
17470 ))
17471 }
17472
17473 fn perform_code_action_kind(
17474 &mut self,
17475 project: Entity<Project>,
17476 kind: CodeActionKind,
17477 window: &mut Window,
17478 cx: &mut Context<Self>,
17479 ) -> Task<Result<()>> {
17480 let buffer = self.buffer.clone();
17481 let buffers = buffer.read(cx).all_buffers();
17482 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17483 let apply_action = project.update(cx, |project, cx| {
17484 project.apply_code_action_kind(buffers, kind, true, cx)
17485 });
17486 cx.spawn_in(window, async move |_, cx| {
17487 let transaction = futures::select_biased! {
17488 () = timeout => {
17489 log::warn!("timed out waiting for executing code action");
17490 None
17491 }
17492 transaction = apply_action.log_err().fuse() => transaction,
17493 };
17494 buffer
17495 .update(cx, |buffer, cx| {
17496 // check if we need this
17497 if let Some(transaction) = transaction
17498 && !buffer.is_singleton()
17499 {
17500 buffer.push_transaction(&transaction.0, cx);
17501 }
17502 cx.notify();
17503 })
17504 .ok();
17505 Ok(())
17506 })
17507 }
17508
17509 pub fn restart_language_server(
17510 &mut self,
17511 _: &RestartLanguageServer,
17512 _: &mut Window,
17513 cx: &mut Context<Self>,
17514 ) {
17515 if let Some(project) = self.project.clone() {
17516 self.buffer.update(cx, |multi_buffer, cx| {
17517 project.update(cx, |project, cx| {
17518 project.restart_language_servers_for_buffers(
17519 multi_buffer.all_buffers().into_iter().collect(),
17520 HashSet::default(),
17521 cx,
17522 );
17523 });
17524 })
17525 }
17526 }
17527
17528 pub fn stop_language_server(
17529 &mut self,
17530 _: &StopLanguageServer,
17531 _: &mut Window,
17532 cx: &mut Context<Self>,
17533 ) {
17534 if let Some(project) = self.project.clone() {
17535 self.buffer.update(cx, |multi_buffer, cx| {
17536 project.update(cx, |project, cx| {
17537 project.stop_language_servers_for_buffers(
17538 multi_buffer.all_buffers().into_iter().collect(),
17539 HashSet::default(),
17540 cx,
17541 );
17542 cx.emit(project::Event::RefreshInlayHints);
17543 });
17544 });
17545 }
17546 }
17547
17548 fn cancel_language_server_work(
17549 workspace: &mut Workspace,
17550 _: &actions::CancelLanguageServerWork,
17551 _: &mut Window,
17552 cx: &mut Context<Workspace>,
17553 ) {
17554 let project = workspace.project();
17555 let buffers = workspace
17556 .active_item(cx)
17557 .and_then(|item| item.act_as::<Editor>(cx))
17558 .map_or(HashSet::default(), |editor| {
17559 editor.read(cx).buffer.read(cx).all_buffers()
17560 });
17561 project.update(cx, |project, cx| {
17562 project.cancel_language_server_work_for_buffers(buffers, cx);
17563 });
17564 }
17565
17566 fn show_character_palette(
17567 &mut self,
17568 _: &ShowCharacterPalette,
17569 window: &mut Window,
17570 _: &mut Context<Self>,
17571 ) {
17572 window.show_character_palette();
17573 }
17574
17575 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17576 if !self.diagnostics_enabled() {
17577 return;
17578 }
17579
17580 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17581 let buffer = self.buffer.read(cx).snapshot(cx);
17582 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17583 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17584 let is_valid = buffer
17585 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17586 .any(|entry| {
17587 entry.diagnostic.is_primary
17588 && !entry.range.is_empty()
17589 && entry.range.start == primary_range_start
17590 && entry.diagnostic.message == active_diagnostics.active_message
17591 });
17592
17593 if !is_valid {
17594 self.dismiss_diagnostics(cx);
17595 }
17596 }
17597 }
17598
17599 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17600 match &self.active_diagnostics {
17601 ActiveDiagnostic::Group(group) => Some(group),
17602 _ => None,
17603 }
17604 }
17605
17606 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17607 if !self.diagnostics_enabled() {
17608 return;
17609 }
17610 self.dismiss_diagnostics(cx);
17611 self.active_diagnostics = ActiveDiagnostic::All;
17612 }
17613
17614 fn activate_diagnostics(
17615 &mut self,
17616 buffer_id: BufferId,
17617 diagnostic: DiagnosticEntryRef<'_, usize>,
17618 window: &mut Window,
17619 cx: &mut Context<Self>,
17620 ) {
17621 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17622 return;
17623 }
17624 self.dismiss_diagnostics(cx);
17625 let snapshot = self.snapshot(window, cx);
17626 let buffer = self.buffer.read(cx).snapshot(cx);
17627 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17628 return;
17629 };
17630
17631 let diagnostic_group = buffer
17632 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17633 .collect::<Vec<_>>();
17634
17635 let blocks =
17636 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17637
17638 let blocks = self.display_map.update(cx, |display_map, cx| {
17639 display_map.insert_blocks(blocks, cx).into_iter().collect()
17640 });
17641 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17642 active_range: buffer.anchor_before(diagnostic.range.start)
17643 ..buffer.anchor_after(diagnostic.range.end),
17644 active_message: diagnostic.diagnostic.message.clone(),
17645 group_id: diagnostic.diagnostic.group_id,
17646 blocks,
17647 });
17648 cx.notify();
17649 }
17650
17651 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17652 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17653 return;
17654 };
17655
17656 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17657 if let ActiveDiagnostic::Group(group) = prev {
17658 self.display_map.update(cx, |display_map, cx| {
17659 display_map.remove_blocks(group.blocks, cx);
17660 });
17661 cx.notify();
17662 }
17663 }
17664
17665 /// Disable inline diagnostics rendering for this editor.
17666 pub fn disable_inline_diagnostics(&mut self) {
17667 self.inline_diagnostics_enabled = false;
17668 self.inline_diagnostics_update = Task::ready(());
17669 self.inline_diagnostics.clear();
17670 }
17671
17672 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17673 self.diagnostics_enabled = false;
17674 self.dismiss_diagnostics(cx);
17675 self.inline_diagnostics_update = Task::ready(());
17676 self.inline_diagnostics.clear();
17677 }
17678
17679 pub fn disable_word_completions(&mut self) {
17680 self.word_completions_enabled = false;
17681 }
17682
17683 pub fn diagnostics_enabled(&self) -> bool {
17684 self.diagnostics_enabled && self.mode.is_full()
17685 }
17686
17687 pub fn inline_diagnostics_enabled(&self) -> bool {
17688 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17689 }
17690
17691 pub fn show_inline_diagnostics(&self) -> bool {
17692 self.show_inline_diagnostics
17693 }
17694
17695 pub fn toggle_inline_diagnostics(
17696 &mut self,
17697 _: &ToggleInlineDiagnostics,
17698 window: &mut Window,
17699 cx: &mut Context<Editor>,
17700 ) {
17701 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17702 self.refresh_inline_diagnostics(false, window, cx);
17703 }
17704
17705 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17706 self.diagnostics_max_severity = severity;
17707 self.display_map.update(cx, |display_map, _| {
17708 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17709 });
17710 }
17711
17712 pub fn toggle_diagnostics(
17713 &mut self,
17714 _: &ToggleDiagnostics,
17715 window: &mut Window,
17716 cx: &mut Context<Editor>,
17717 ) {
17718 if !self.diagnostics_enabled() {
17719 return;
17720 }
17721
17722 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17723 EditorSettings::get_global(cx)
17724 .diagnostics_max_severity
17725 .filter(|severity| severity != &DiagnosticSeverity::Off)
17726 .unwrap_or(DiagnosticSeverity::Hint)
17727 } else {
17728 DiagnosticSeverity::Off
17729 };
17730 self.set_max_diagnostics_severity(new_severity, cx);
17731 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17732 self.active_diagnostics = ActiveDiagnostic::None;
17733 self.inline_diagnostics_update = Task::ready(());
17734 self.inline_diagnostics.clear();
17735 } else {
17736 self.refresh_inline_diagnostics(false, window, cx);
17737 }
17738
17739 cx.notify();
17740 }
17741
17742 pub fn toggle_minimap(
17743 &mut self,
17744 _: &ToggleMinimap,
17745 window: &mut Window,
17746 cx: &mut Context<Editor>,
17747 ) {
17748 if self.supports_minimap(cx) {
17749 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17750 }
17751 }
17752
17753 fn refresh_inline_diagnostics(
17754 &mut self,
17755 debounce: bool,
17756 window: &mut Window,
17757 cx: &mut Context<Self>,
17758 ) {
17759 let max_severity = ProjectSettings::get_global(cx)
17760 .diagnostics
17761 .inline
17762 .max_severity
17763 .unwrap_or(self.diagnostics_max_severity);
17764
17765 if !self.inline_diagnostics_enabled()
17766 || !self.show_inline_diagnostics
17767 || max_severity == DiagnosticSeverity::Off
17768 {
17769 self.inline_diagnostics_update = Task::ready(());
17770 self.inline_diagnostics.clear();
17771 return;
17772 }
17773
17774 let debounce_ms = ProjectSettings::get_global(cx)
17775 .diagnostics
17776 .inline
17777 .update_debounce_ms;
17778 let debounce = if debounce && debounce_ms > 0 {
17779 Some(Duration::from_millis(debounce_ms))
17780 } else {
17781 None
17782 };
17783 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17784 if let Some(debounce) = debounce {
17785 cx.background_executor().timer(debounce).await;
17786 }
17787 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17788 editor
17789 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17790 .ok()
17791 }) else {
17792 return;
17793 };
17794
17795 let new_inline_diagnostics = cx
17796 .background_spawn(async move {
17797 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17798 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17799 let message = diagnostic_entry
17800 .diagnostic
17801 .message
17802 .split_once('\n')
17803 .map(|(line, _)| line)
17804 .map(SharedString::new)
17805 .unwrap_or_else(|| {
17806 SharedString::new(&*diagnostic_entry.diagnostic.message)
17807 });
17808 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17809 let (Ok(i) | Err(i)) = inline_diagnostics
17810 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17811 inline_diagnostics.insert(
17812 i,
17813 (
17814 start_anchor,
17815 InlineDiagnostic {
17816 message,
17817 group_id: diagnostic_entry.diagnostic.group_id,
17818 start: diagnostic_entry.range.start.to_point(&snapshot),
17819 is_primary: diagnostic_entry.diagnostic.is_primary,
17820 severity: diagnostic_entry.diagnostic.severity,
17821 },
17822 ),
17823 );
17824 }
17825 inline_diagnostics
17826 })
17827 .await;
17828
17829 editor
17830 .update(cx, |editor, cx| {
17831 editor.inline_diagnostics = new_inline_diagnostics;
17832 cx.notify();
17833 })
17834 .ok();
17835 });
17836 }
17837
17838 fn pull_diagnostics(
17839 &mut self,
17840 buffer_id: Option<BufferId>,
17841 window: &Window,
17842 cx: &mut Context<Self>,
17843 ) -> Option<()> {
17844 if !self.mode().is_full() {
17845 return None;
17846 }
17847 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17848 .diagnostics
17849 .lsp_pull_diagnostics;
17850 if !pull_diagnostics_settings.enabled {
17851 return None;
17852 }
17853 let project = self.project()?.downgrade();
17854 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17855 let mut buffers = self.buffer.read(cx).all_buffers();
17856 if let Some(buffer_id) = buffer_id {
17857 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17858 }
17859
17860 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17861 cx.background_executor().timer(debounce).await;
17862
17863 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17864 buffers
17865 .into_iter()
17866 .filter_map(|buffer| {
17867 project
17868 .update(cx, |project, cx| {
17869 project.lsp_store().update(cx, |lsp_store, cx| {
17870 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17871 })
17872 })
17873 .ok()
17874 })
17875 .collect::<FuturesUnordered<_>>()
17876 }) else {
17877 return;
17878 };
17879
17880 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17881 match pull_task {
17882 Ok(()) => {
17883 if editor
17884 .update_in(cx, |editor, window, cx| {
17885 editor.update_diagnostics_state(window, cx);
17886 })
17887 .is_err()
17888 {
17889 return;
17890 }
17891 }
17892 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17893 }
17894 }
17895 });
17896
17897 Some(())
17898 }
17899
17900 pub fn set_selections_from_remote(
17901 &mut self,
17902 selections: Vec<Selection<Anchor>>,
17903 pending_selection: Option<Selection<Anchor>>,
17904 window: &mut Window,
17905 cx: &mut Context<Self>,
17906 ) {
17907 let old_cursor_position = self.selections.newest_anchor().head();
17908 self.selections.change_with(cx, |s| {
17909 s.select_anchors(selections);
17910 if let Some(pending_selection) = pending_selection {
17911 s.set_pending(pending_selection, SelectMode::Character);
17912 } else {
17913 s.clear_pending();
17914 }
17915 });
17916 self.selections_did_change(
17917 false,
17918 &old_cursor_position,
17919 SelectionEffects::default(),
17920 window,
17921 cx,
17922 );
17923 }
17924
17925 pub fn transact(
17926 &mut self,
17927 window: &mut Window,
17928 cx: &mut Context<Self>,
17929 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17930 ) -> Option<TransactionId> {
17931 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17932 this.start_transaction_at(Instant::now(), window, cx);
17933 update(this, window, cx);
17934 this.end_transaction_at(Instant::now(), cx)
17935 })
17936 }
17937
17938 pub fn start_transaction_at(
17939 &mut self,
17940 now: Instant,
17941 window: &mut Window,
17942 cx: &mut Context<Self>,
17943 ) -> Option<TransactionId> {
17944 self.end_selection(window, cx);
17945 if let Some(tx_id) = self
17946 .buffer
17947 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17948 {
17949 self.selection_history
17950 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
17951 cx.emit(EditorEvent::TransactionBegun {
17952 transaction_id: tx_id,
17953 });
17954 Some(tx_id)
17955 } else {
17956 None
17957 }
17958 }
17959
17960 pub fn end_transaction_at(
17961 &mut self,
17962 now: Instant,
17963 cx: &mut Context<Self>,
17964 ) -> Option<TransactionId> {
17965 if let Some(transaction_id) = self
17966 .buffer
17967 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17968 {
17969 if let Some((_, end_selections)) =
17970 self.selection_history.transaction_mut(transaction_id)
17971 {
17972 *end_selections = Some(self.selections.disjoint_anchors_arc());
17973 } else {
17974 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17975 }
17976
17977 cx.emit(EditorEvent::Edited { transaction_id });
17978 Some(transaction_id)
17979 } else {
17980 None
17981 }
17982 }
17983
17984 pub fn modify_transaction_selection_history(
17985 &mut self,
17986 transaction_id: TransactionId,
17987 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17988 ) -> bool {
17989 self.selection_history
17990 .transaction_mut(transaction_id)
17991 .map(modify)
17992 .is_some()
17993 }
17994
17995 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17996 if self.selection_mark_mode {
17997 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17998 s.move_with(|_, sel| {
17999 sel.collapse_to(sel.head(), SelectionGoal::None);
18000 });
18001 })
18002 }
18003 self.selection_mark_mode = true;
18004 cx.notify();
18005 }
18006
18007 pub fn swap_selection_ends(
18008 &mut self,
18009 _: &actions::SwapSelectionEnds,
18010 window: &mut Window,
18011 cx: &mut Context<Self>,
18012 ) {
18013 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18014 s.move_with(|_, sel| {
18015 if sel.start != sel.end {
18016 sel.reversed = !sel.reversed
18017 }
18018 });
18019 });
18020 self.request_autoscroll(Autoscroll::newest(), cx);
18021 cx.notify();
18022 }
18023
18024 pub fn toggle_focus(
18025 workspace: &mut Workspace,
18026 _: &actions::ToggleFocus,
18027 window: &mut Window,
18028 cx: &mut Context<Workspace>,
18029 ) {
18030 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18031 return;
18032 };
18033 workspace.activate_item(&item, true, true, window, cx);
18034 }
18035
18036 pub fn toggle_fold(
18037 &mut self,
18038 _: &actions::ToggleFold,
18039 window: &mut Window,
18040 cx: &mut Context<Self>,
18041 ) {
18042 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18043 let selection = self.selections.newest::<Point>(cx);
18044
18045 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18046 let range = if selection.is_empty() {
18047 let point = selection.head().to_display_point(&display_map);
18048 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18049 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18050 .to_point(&display_map);
18051 start..end
18052 } else {
18053 selection.range()
18054 };
18055 if display_map.folds_in_range(range).next().is_some() {
18056 self.unfold_lines(&Default::default(), window, cx)
18057 } else {
18058 self.fold(&Default::default(), window, cx)
18059 }
18060 } else {
18061 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18062 let buffer_ids: HashSet<_> = self
18063 .selections
18064 .disjoint_anchor_ranges()
18065 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18066 .collect();
18067
18068 let should_unfold = buffer_ids
18069 .iter()
18070 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18071
18072 for buffer_id in buffer_ids {
18073 if should_unfold {
18074 self.unfold_buffer(buffer_id, cx);
18075 } else {
18076 self.fold_buffer(buffer_id, cx);
18077 }
18078 }
18079 }
18080 }
18081
18082 pub fn toggle_fold_recursive(
18083 &mut self,
18084 _: &actions::ToggleFoldRecursive,
18085 window: &mut Window,
18086 cx: &mut Context<Self>,
18087 ) {
18088 let selection = self.selections.newest::<Point>(cx);
18089
18090 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18091 let range = if selection.is_empty() {
18092 let point = selection.head().to_display_point(&display_map);
18093 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18094 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18095 .to_point(&display_map);
18096 start..end
18097 } else {
18098 selection.range()
18099 };
18100 if display_map.folds_in_range(range).next().is_some() {
18101 self.unfold_recursive(&Default::default(), window, cx)
18102 } else {
18103 self.fold_recursive(&Default::default(), window, cx)
18104 }
18105 }
18106
18107 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18108 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18109 let mut to_fold = Vec::new();
18110 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18111 let selections = self.selections.all_adjusted(cx);
18112
18113 for selection in selections {
18114 let range = selection.range().sorted();
18115 let buffer_start_row = range.start.row;
18116
18117 if range.start.row != range.end.row {
18118 let mut found = false;
18119 let mut row = range.start.row;
18120 while row <= range.end.row {
18121 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18122 {
18123 found = true;
18124 row = crease.range().end.row + 1;
18125 to_fold.push(crease);
18126 } else {
18127 row += 1
18128 }
18129 }
18130 if found {
18131 continue;
18132 }
18133 }
18134
18135 for row in (0..=range.start.row).rev() {
18136 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18137 && crease.range().end.row >= buffer_start_row
18138 {
18139 to_fold.push(crease);
18140 if row <= range.start.row {
18141 break;
18142 }
18143 }
18144 }
18145 }
18146
18147 self.fold_creases(to_fold, true, window, cx);
18148 } else {
18149 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18150 let buffer_ids = self
18151 .selections
18152 .disjoint_anchor_ranges()
18153 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18154 .collect::<HashSet<_>>();
18155 for buffer_id in buffer_ids {
18156 self.fold_buffer(buffer_id, cx);
18157 }
18158 }
18159 }
18160
18161 pub fn toggle_fold_all(
18162 &mut self,
18163 _: &actions::ToggleFoldAll,
18164 window: &mut Window,
18165 cx: &mut Context<Self>,
18166 ) {
18167 if self.buffer.read(cx).is_singleton() {
18168 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18169 let has_folds = display_map
18170 .folds_in_range(0..display_map.buffer_snapshot().len())
18171 .next()
18172 .is_some();
18173
18174 if has_folds {
18175 self.unfold_all(&actions::UnfoldAll, window, cx);
18176 } else {
18177 self.fold_all(&actions::FoldAll, window, cx);
18178 }
18179 } else {
18180 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18181 let should_unfold = buffer_ids
18182 .iter()
18183 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18184
18185 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18186 editor
18187 .update_in(cx, |editor, _, cx| {
18188 for buffer_id in buffer_ids {
18189 if should_unfold {
18190 editor.unfold_buffer(buffer_id, cx);
18191 } else {
18192 editor.fold_buffer(buffer_id, cx);
18193 }
18194 }
18195 })
18196 .ok();
18197 });
18198 }
18199 }
18200
18201 fn fold_at_level(
18202 &mut self,
18203 fold_at: &FoldAtLevel,
18204 window: &mut Window,
18205 cx: &mut Context<Self>,
18206 ) {
18207 if !self.buffer.read(cx).is_singleton() {
18208 return;
18209 }
18210
18211 let fold_at_level = fold_at.0;
18212 let snapshot = self.buffer.read(cx).snapshot(cx);
18213 let mut to_fold = Vec::new();
18214 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18215
18216 let row_ranges_to_keep: Vec<Range<u32>> = self
18217 .selections
18218 .all::<Point>(cx)
18219 .into_iter()
18220 .map(|sel| sel.start.row..sel.end.row)
18221 .collect();
18222
18223 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18224 while start_row < end_row {
18225 match self
18226 .snapshot(window, cx)
18227 .crease_for_buffer_row(MultiBufferRow(start_row))
18228 {
18229 Some(crease) => {
18230 let nested_start_row = crease.range().start.row + 1;
18231 let nested_end_row = crease.range().end.row;
18232
18233 if current_level < fold_at_level {
18234 stack.push((nested_start_row, nested_end_row, current_level + 1));
18235 } else if current_level == fold_at_level {
18236 // Fold iff there is no selection completely contained within the fold region
18237 if !row_ranges_to_keep.iter().any(|selection| {
18238 selection.end >= nested_start_row
18239 && selection.start <= nested_end_row
18240 }) {
18241 to_fold.push(crease);
18242 }
18243 }
18244
18245 start_row = nested_end_row + 1;
18246 }
18247 None => start_row += 1,
18248 }
18249 }
18250 }
18251
18252 self.fold_creases(to_fold, true, window, cx);
18253 }
18254
18255 pub fn fold_at_level_1(
18256 &mut self,
18257 _: &actions::FoldAtLevel1,
18258 window: &mut Window,
18259 cx: &mut Context<Self>,
18260 ) {
18261 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18262 }
18263
18264 pub fn fold_at_level_2(
18265 &mut self,
18266 _: &actions::FoldAtLevel2,
18267 window: &mut Window,
18268 cx: &mut Context<Self>,
18269 ) {
18270 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18271 }
18272
18273 pub fn fold_at_level_3(
18274 &mut self,
18275 _: &actions::FoldAtLevel3,
18276 window: &mut Window,
18277 cx: &mut Context<Self>,
18278 ) {
18279 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18280 }
18281
18282 pub fn fold_at_level_4(
18283 &mut self,
18284 _: &actions::FoldAtLevel4,
18285 window: &mut Window,
18286 cx: &mut Context<Self>,
18287 ) {
18288 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18289 }
18290
18291 pub fn fold_at_level_5(
18292 &mut self,
18293 _: &actions::FoldAtLevel5,
18294 window: &mut Window,
18295 cx: &mut Context<Self>,
18296 ) {
18297 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18298 }
18299
18300 pub fn fold_at_level_6(
18301 &mut self,
18302 _: &actions::FoldAtLevel6,
18303 window: &mut Window,
18304 cx: &mut Context<Self>,
18305 ) {
18306 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18307 }
18308
18309 pub fn fold_at_level_7(
18310 &mut self,
18311 _: &actions::FoldAtLevel7,
18312 window: &mut Window,
18313 cx: &mut Context<Self>,
18314 ) {
18315 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18316 }
18317
18318 pub fn fold_at_level_8(
18319 &mut self,
18320 _: &actions::FoldAtLevel8,
18321 window: &mut Window,
18322 cx: &mut Context<Self>,
18323 ) {
18324 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18325 }
18326
18327 pub fn fold_at_level_9(
18328 &mut self,
18329 _: &actions::FoldAtLevel9,
18330 window: &mut Window,
18331 cx: &mut Context<Self>,
18332 ) {
18333 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18334 }
18335
18336 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18337 if self.buffer.read(cx).is_singleton() {
18338 let mut fold_ranges = Vec::new();
18339 let snapshot = self.buffer.read(cx).snapshot(cx);
18340
18341 for row in 0..snapshot.max_row().0 {
18342 if let Some(foldable_range) = self
18343 .snapshot(window, cx)
18344 .crease_for_buffer_row(MultiBufferRow(row))
18345 {
18346 fold_ranges.push(foldable_range);
18347 }
18348 }
18349
18350 self.fold_creases(fold_ranges, true, window, cx);
18351 } else {
18352 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18353 editor
18354 .update_in(cx, |editor, _, cx| {
18355 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18356 editor.fold_buffer(buffer_id, cx);
18357 }
18358 })
18359 .ok();
18360 });
18361 }
18362 }
18363
18364 pub fn fold_function_bodies(
18365 &mut self,
18366 _: &actions::FoldFunctionBodies,
18367 window: &mut Window,
18368 cx: &mut Context<Self>,
18369 ) {
18370 let snapshot = self.buffer.read(cx).snapshot(cx);
18371
18372 let ranges = snapshot
18373 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18374 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18375 .collect::<Vec<_>>();
18376
18377 let creases = ranges
18378 .into_iter()
18379 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18380 .collect();
18381
18382 self.fold_creases(creases, true, window, cx);
18383 }
18384
18385 pub fn fold_recursive(
18386 &mut self,
18387 _: &actions::FoldRecursive,
18388 window: &mut Window,
18389 cx: &mut Context<Self>,
18390 ) {
18391 let mut to_fold = Vec::new();
18392 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18393 let selections = self.selections.all_adjusted(cx);
18394
18395 for selection in selections {
18396 let range = selection.range().sorted();
18397 let buffer_start_row = range.start.row;
18398
18399 if range.start.row != range.end.row {
18400 let mut found = false;
18401 for row in range.start.row..=range.end.row {
18402 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18403 found = true;
18404 to_fold.push(crease);
18405 }
18406 }
18407 if found {
18408 continue;
18409 }
18410 }
18411
18412 for row in (0..=range.start.row).rev() {
18413 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18414 if crease.range().end.row >= buffer_start_row {
18415 to_fold.push(crease);
18416 } else {
18417 break;
18418 }
18419 }
18420 }
18421 }
18422
18423 self.fold_creases(to_fold, true, window, cx);
18424 }
18425
18426 pub fn fold_at(
18427 &mut self,
18428 buffer_row: MultiBufferRow,
18429 window: &mut Window,
18430 cx: &mut Context<Self>,
18431 ) {
18432 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18433
18434 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18435 let autoscroll = self
18436 .selections
18437 .all::<Point>(cx)
18438 .iter()
18439 .any(|selection| crease.range().overlaps(&selection.range()));
18440
18441 self.fold_creases(vec![crease], autoscroll, window, cx);
18442 }
18443 }
18444
18445 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18446 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18447 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18448 let buffer = display_map.buffer_snapshot();
18449 let selections = self.selections.all::<Point>(cx);
18450 let ranges = selections
18451 .iter()
18452 .map(|s| {
18453 let range = s.display_range(&display_map).sorted();
18454 let mut start = range.start.to_point(&display_map);
18455 let mut end = range.end.to_point(&display_map);
18456 start.column = 0;
18457 end.column = buffer.line_len(MultiBufferRow(end.row));
18458 start..end
18459 })
18460 .collect::<Vec<_>>();
18461
18462 self.unfold_ranges(&ranges, true, true, cx);
18463 } else {
18464 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18465 let buffer_ids = self
18466 .selections
18467 .disjoint_anchor_ranges()
18468 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18469 .collect::<HashSet<_>>();
18470 for buffer_id in buffer_ids {
18471 self.unfold_buffer(buffer_id, cx);
18472 }
18473 }
18474 }
18475
18476 pub fn unfold_recursive(
18477 &mut self,
18478 _: &UnfoldRecursive,
18479 _window: &mut Window,
18480 cx: &mut Context<Self>,
18481 ) {
18482 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18483 let selections = self.selections.all::<Point>(cx);
18484 let ranges = selections
18485 .iter()
18486 .map(|s| {
18487 let mut range = s.display_range(&display_map).sorted();
18488 *range.start.column_mut() = 0;
18489 *range.end.column_mut() = display_map.line_len(range.end.row());
18490 let start = range.start.to_point(&display_map);
18491 let end = range.end.to_point(&display_map);
18492 start..end
18493 })
18494 .collect::<Vec<_>>();
18495
18496 self.unfold_ranges(&ranges, true, true, cx);
18497 }
18498
18499 pub fn unfold_at(
18500 &mut self,
18501 buffer_row: MultiBufferRow,
18502 _window: &mut Window,
18503 cx: &mut Context<Self>,
18504 ) {
18505 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18506
18507 let intersection_range = Point::new(buffer_row.0, 0)
18508 ..Point::new(
18509 buffer_row.0,
18510 display_map.buffer_snapshot().line_len(buffer_row),
18511 );
18512
18513 let autoscroll = self
18514 .selections
18515 .all::<Point>(cx)
18516 .iter()
18517 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18518
18519 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18520 }
18521
18522 pub fn unfold_all(
18523 &mut self,
18524 _: &actions::UnfoldAll,
18525 _window: &mut Window,
18526 cx: &mut Context<Self>,
18527 ) {
18528 if self.buffer.read(cx).is_singleton() {
18529 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18530 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18531 } else {
18532 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18533 editor
18534 .update(cx, |editor, cx| {
18535 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18536 editor.unfold_buffer(buffer_id, cx);
18537 }
18538 })
18539 .ok();
18540 });
18541 }
18542 }
18543
18544 pub fn fold_selected_ranges(
18545 &mut self,
18546 _: &FoldSelectedRanges,
18547 window: &mut Window,
18548 cx: &mut Context<Self>,
18549 ) {
18550 let selections = self.selections.all_adjusted(cx);
18551 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18552 let ranges = selections
18553 .into_iter()
18554 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18555 .collect::<Vec<_>>();
18556 self.fold_creases(ranges, true, window, cx);
18557 }
18558
18559 pub fn fold_ranges<T: ToOffset + Clone>(
18560 &mut self,
18561 ranges: Vec<Range<T>>,
18562 auto_scroll: bool,
18563 window: &mut Window,
18564 cx: &mut Context<Self>,
18565 ) {
18566 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18567 let ranges = ranges
18568 .into_iter()
18569 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18570 .collect::<Vec<_>>();
18571 self.fold_creases(ranges, auto_scroll, window, cx);
18572 }
18573
18574 pub fn fold_creases<T: ToOffset + Clone>(
18575 &mut self,
18576 creases: Vec<Crease<T>>,
18577 auto_scroll: bool,
18578 _window: &mut Window,
18579 cx: &mut Context<Self>,
18580 ) {
18581 if creases.is_empty() {
18582 return;
18583 }
18584
18585 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18586
18587 if auto_scroll {
18588 self.request_autoscroll(Autoscroll::fit(), cx);
18589 }
18590
18591 cx.notify();
18592
18593 self.scrollbar_marker_state.dirty = true;
18594 self.folds_did_change(cx);
18595 }
18596
18597 /// Removes any folds whose ranges intersect any of the given ranges.
18598 pub fn unfold_ranges<T: ToOffset + Clone>(
18599 &mut self,
18600 ranges: &[Range<T>],
18601 inclusive: bool,
18602 auto_scroll: bool,
18603 cx: &mut Context<Self>,
18604 ) {
18605 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18606 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18607 });
18608 self.folds_did_change(cx);
18609 }
18610
18611 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18612 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18613 return;
18614 }
18615 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18616 self.display_map.update(cx, |display_map, cx| {
18617 display_map.fold_buffers([buffer_id], cx)
18618 });
18619 cx.emit(EditorEvent::BufferFoldToggled {
18620 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18621 folded: true,
18622 });
18623 cx.notify();
18624 }
18625
18626 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18627 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18628 return;
18629 }
18630 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18631 self.display_map.update(cx, |display_map, cx| {
18632 display_map.unfold_buffers([buffer_id], cx);
18633 });
18634 cx.emit(EditorEvent::BufferFoldToggled {
18635 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18636 folded: false,
18637 });
18638 cx.notify();
18639 }
18640
18641 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18642 self.display_map.read(cx).is_buffer_folded(buffer)
18643 }
18644
18645 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18646 self.display_map.read(cx).folded_buffers()
18647 }
18648
18649 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18650 self.display_map.update(cx, |display_map, cx| {
18651 display_map.disable_header_for_buffer(buffer_id, cx);
18652 });
18653 cx.notify();
18654 }
18655
18656 /// Removes any folds with the given ranges.
18657 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18658 &mut self,
18659 ranges: &[Range<T>],
18660 type_id: TypeId,
18661 auto_scroll: bool,
18662 cx: &mut Context<Self>,
18663 ) {
18664 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18665 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18666 });
18667 self.folds_did_change(cx);
18668 }
18669
18670 fn remove_folds_with<T: ToOffset + Clone>(
18671 &mut self,
18672 ranges: &[Range<T>],
18673 auto_scroll: bool,
18674 cx: &mut Context<Self>,
18675 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18676 ) {
18677 if ranges.is_empty() {
18678 return;
18679 }
18680
18681 let mut buffers_affected = HashSet::default();
18682 let multi_buffer = self.buffer().read(cx);
18683 for range in ranges {
18684 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18685 buffers_affected.insert(buffer.read(cx).remote_id());
18686 };
18687 }
18688
18689 self.display_map.update(cx, update);
18690
18691 if auto_scroll {
18692 self.request_autoscroll(Autoscroll::fit(), cx);
18693 }
18694
18695 cx.notify();
18696 self.scrollbar_marker_state.dirty = true;
18697 self.active_indent_guides_state.dirty = true;
18698 }
18699
18700 pub fn update_renderer_widths(
18701 &mut self,
18702 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18703 cx: &mut Context<Self>,
18704 ) -> bool {
18705 self.display_map
18706 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18707 }
18708
18709 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18710 self.display_map.read(cx).fold_placeholder.clone()
18711 }
18712
18713 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18714 self.buffer.update(cx, |buffer, cx| {
18715 buffer.set_all_diff_hunks_expanded(cx);
18716 });
18717 }
18718
18719 pub fn expand_all_diff_hunks(
18720 &mut self,
18721 _: &ExpandAllDiffHunks,
18722 _window: &mut Window,
18723 cx: &mut Context<Self>,
18724 ) {
18725 self.buffer.update(cx, |buffer, cx| {
18726 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18727 });
18728 }
18729
18730 pub fn toggle_selected_diff_hunks(
18731 &mut self,
18732 _: &ToggleSelectedDiffHunks,
18733 _window: &mut Window,
18734 cx: &mut Context<Self>,
18735 ) {
18736 let ranges: Vec<_> = self
18737 .selections
18738 .disjoint_anchors()
18739 .iter()
18740 .map(|s| s.range())
18741 .collect();
18742 self.toggle_diff_hunks_in_ranges(ranges, cx);
18743 }
18744
18745 pub fn diff_hunks_in_ranges<'a>(
18746 &'a self,
18747 ranges: &'a [Range<Anchor>],
18748 buffer: &'a MultiBufferSnapshot,
18749 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18750 ranges.iter().flat_map(move |range| {
18751 let end_excerpt_id = range.end.excerpt_id;
18752 let range = range.to_point(buffer);
18753 let mut peek_end = range.end;
18754 if range.end.row < buffer.max_row().0 {
18755 peek_end = Point::new(range.end.row + 1, 0);
18756 }
18757 buffer
18758 .diff_hunks_in_range(range.start..peek_end)
18759 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18760 })
18761 }
18762
18763 pub fn has_stageable_diff_hunks_in_ranges(
18764 &self,
18765 ranges: &[Range<Anchor>],
18766 snapshot: &MultiBufferSnapshot,
18767 ) -> bool {
18768 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18769 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18770 }
18771
18772 pub fn toggle_staged_selected_diff_hunks(
18773 &mut self,
18774 _: &::git::ToggleStaged,
18775 _: &mut Window,
18776 cx: &mut Context<Self>,
18777 ) {
18778 let snapshot = self.buffer.read(cx).snapshot(cx);
18779 let ranges: Vec<_> = self
18780 .selections
18781 .disjoint_anchors()
18782 .iter()
18783 .map(|s| s.range())
18784 .collect();
18785 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18786 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18787 }
18788
18789 pub fn set_render_diff_hunk_controls(
18790 &mut self,
18791 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18792 cx: &mut Context<Self>,
18793 ) {
18794 self.render_diff_hunk_controls = render_diff_hunk_controls;
18795 cx.notify();
18796 }
18797
18798 pub fn stage_and_next(
18799 &mut self,
18800 _: &::git::StageAndNext,
18801 window: &mut Window,
18802 cx: &mut Context<Self>,
18803 ) {
18804 self.do_stage_or_unstage_and_next(true, window, cx);
18805 }
18806
18807 pub fn unstage_and_next(
18808 &mut self,
18809 _: &::git::UnstageAndNext,
18810 window: &mut Window,
18811 cx: &mut Context<Self>,
18812 ) {
18813 self.do_stage_or_unstage_and_next(false, window, cx);
18814 }
18815
18816 pub fn stage_or_unstage_diff_hunks(
18817 &mut self,
18818 stage: bool,
18819 ranges: Vec<Range<Anchor>>,
18820 cx: &mut Context<Self>,
18821 ) {
18822 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18823 cx.spawn(async move |this, cx| {
18824 task.await?;
18825 this.update(cx, |this, cx| {
18826 let snapshot = this.buffer.read(cx).snapshot(cx);
18827 let chunk_by = this
18828 .diff_hunks_in_ranges(&ranges, &snapshot)
18829 .chunk_by(|hunk| hunk.buffer_id);
18830 for (buffer_id, hunks) in &chunk_by {
18831 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18832 }
18833 })
18834 })
18835 .detach_and_log_err(cx);
18836 }
18837
18838 fn save_buffers_for_ranges_if_needed(
18839 &mut self,
18840 ranges: &[Range<Anchor>],
18841 cx: &mut Context<Editor>,
18842 ) -> Task<Result<()>> {
18843 let multibuffer = self.buffer.read(cx);
18844 let snapshot = multibuffer.read(cx);
18845 let buffer_ids: HashSet<_> = ranges
18846 .iter()
18847 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18848 .collect();
18849 drop(snapshot);
18850
18851 let mut buffers = HashSet::default();
18852 for buffer_id in buffer_ids {
18853 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18854 let buffer = buffer_entity.read(cx);
18855 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18856 {
18857 buffers.insert(buffer_entity);
18858 }
18859 }
18860 }
18861
18862 if let Some(project) = &self.project {
18863 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18864 } else {
18865 Task::ready(Ok(()))
18866 }
18867 }
18868
18869 fn do_stage_or_unstage_and_next(
18870 &mut self,
18871 stage: bool,
18872 window: &mut Window,
18873 cx: &mut Context<Self>,
18874 ) {
18875 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18876
18877 if ranges.iter().any(|range| range.start != range.end) {
18878 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18879 return;
18880 }
18881
18882 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18883 let snapshot = self.snapshot(window, cx);
18884 let position = self.selections.newest::<Point>(cx).head();
18885 let mut row = snapshot
18886 .buffer_snapshot()
18887 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
18888 .find(|hunk| hunk.row_range.start.0 > position.row)
18889 .map(|hunk| hunk.row_range.start);
18890
18891 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18892 // Outside of the project diff editor, wrap around to the beginning.
18893 if !all_diff_hunks_expanded {
18894 row = row.or_else(|| {
18895 snapshot
18896 .buffer_snapshot()
18897 .diff_hunks_in_range(Point::zero()..position)
18898 .find(|hunk| hunk.row_range.end.0 < position.row)
18899 .map(|hunk| hunk.row_range.start)
18900 });
18901 }
18902
18903 if let Some(row) = row {
18904 let destination = Point::new(row.0, 0);
18905 let autoscroll = Autoscroll::center();
18906
18907 self.unfold_ranges(&[destination..destination], false, false, cx);
18908 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18909 s.select_ranges([destination..destination]);
18910 });
18911 }
18912 }
18913
18914 fn do_stage_or_unstage(
18915 &self,
18916 stage: bool,
18917 buffer_id: BufferId,
18918 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18919 cx: &mut App,
18920 ) -> Option<()> {
18921 let project = self.project()?;
18922 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18923 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18924 let buffer_snapshot = buffer.read(cx).snapshot();
18925 let file_exists = buffer_snapshot
18926 .file()
18927 .is_some_and(|file| file.disk_state().exists());
18928 diff.update(cx, |diff, cx| {
18929 diff.stage_or_unstage_hunks(
18930 stage,
18931 &hunks
18932 .map(|hunk| buffer_diff::DiffHunk {
18933 buffer_range: hunk.buffer_range,
18934 diff_base_byte_range: hunk.diff_base_byte_range,
18935 secondary_status: hunk.secondary_status,
18936 range: Point::zero()..Point::zero(), // unused
18937 })
18938 .collect::<Vec<_>>(),
18939 &buffer_snapshot,
18940 file_exists,
18941 cx,
18942 )
18943 });
18944 None
18945 }
18946
18947 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18948 let ranges: Vec<_> = self
18949 .selections
18950 .disjoint_anchors()
18951 .iter()
18952 .map(|s| s.range())
18953 .collect();
18954 self.buffer
18955 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18956 }
18957
18958 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18959 self.buffer.update(cx, |buffer, cx| {
18960 let ranges = vec![Anchor::min()..Anchor::max()];
18961 if !buffer.all_diff_hunks_expanded()
18962 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18963 {
18964 buffer.collapse_diff_hunks(ranges, cx);
18965 true
18966 } else {
18967 false
18968 }
18969 })
18970 }
18971
18972 fn toggle_diff_hunks_in_ranges(
18973 &mut self,
18974 ranges: Vec<Range<Anchor>>,
18975 cx: &mut Context<Editor>,
18976 ) {
18977 self.buffer.update(cx, |buffer, cx| {
18978 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18979 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18980 })
18981 }
18982
18983 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18984 self.buffer.update(cx, |buffer, cx| {
18985 let snapshot = buffer.snapshot(cx);
18986 let excerpt_id = range.end.excerpt_id;
18987 let point_range = range.to_point(&snapshot);
18988 let expand = !buffer.single_hunk_is_expanded(range, cx);
18989 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18990 })
18991 }
18992
18993 pub(crate) fn apply_all_diff_hunks(
18994 &mut self,
18995 _: &ApplyAllDiffHunks,
18996 window: &mut Window,
18997 cx: &mut Context<Self>,
18998 ) {
18999 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19000
19001 let buffers = self.buffer.read(cx).all_buffers();
19002 for branch_buffer in buffers {
19003 branch_buffer.update(cx, |branch_buffer, cx| {
19004 branch_buffer.merge_into_base(Vec::new(), cx);
19005 });
19006 }
19007
19008 if let Some(project) = self.project.clone() {
19009 self.save(
19010 SaveOptions {
19011 format: true,
19012 autosave: false,
19013 },
19014 project,
19015 window,
19016 cx,
19017 )
19018 .detach_and_log_err(cx);
19019 }
19020 }
19021
19022 pub(crate) fn apply_selected_diff_hunks(
19023 &mut self,
19024 _: &ApplyDiffHunk,
19025 window: &mut Window,
19026 cx: &mut Context<Self>,
19027 ) {
19028 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19029 let snapshot = self.snapshot(window, cx);
19030 let hunks = snapshot.hunks_for_ranges(
19031 self.selections
19032 .all(cx)
19033 .into_iter()
19034 .map(|selection| selection.range()),
19035 );
19036 let mut ranges_by_buffer = HashMap::default();
19037 self.transact(window, cx, |editor, _window, cx| {
19038 for hunk in hunks {
19039 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19040 ranges_by_buffer
19041 .entry(buffer.clone())
19042 .or_insert_with(Vec::new)
19043 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19044 }
19045 }
19046
19047 for (buffer, ranges) in ranges_by_buffer {
19048 buffer.update(cx, |buffer, cx| {
19049 buffer.merge_into_base(ranges, cx);
19050 });
19051 }
19052 });
19053
19054 if let Some(project) = self.project.clone() {
19055 self.save(
19056 SaveOptions {
19057 format: true,
19058 autosave: false,
19059 },
19060 project,
19061 window,
19062 cx,
19063 )
19064 .detach_and_log_err(cx);
19065 }
19066 }
19067
19068 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19069 if hovered != self.gutter_hovered {
19070 self.gutter_hovered = hovered;
19071 cx.notify();
19072 }
19073 }
19074
19075 pub fn insert_blocks(
19076 &mut self,
19077 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19078 autoscroll: Option<Autoscroll>,
19079 cx: &mut Context<Self>,
19080 ) -> Vec<CustomBlockId> {
19081 let blocks = self
19082 .display_map
19083 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19084 if let Some(autoscroll) = autoscroll {
19085 self.request_autoscroll(autoscroll, cx);
19086 }
19087 cx.notify();
19088 blocks
19089 }
19090
19091 pub fn resize_blocks(
19092 &mut self,
19093 heights: HashMap<CustomBlockId, u32>,
19094 autoscroll: Option<Autoscroll>,
19095 cx: &mut Context<Self>,
19096 ) {
19097 self.display_map
19098 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19099 if let Some(autoscroll) = autoscroll {
19100 self.request_autoscroll(autoscroll, cx);
19101 }
19102 cx.notify();
19103 }
19104
19105 pub fn replace_blocks(
19106 &mut self,
19107 renderers: HashMap<CustomBlockId, RenderBlock>,
19108 autoscroll: Option<Autoscroll>,
19109 cx: &mut Context<Self>,
19110 ) {
19111 self.display_map
19112 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19113 if let Some(autoscroll) = autoscroll {
19114 self.request_autoscroll(autoscroll, cx);
19115 }
19116 cx.notify();
19117 }
19118
19119 pub fn remove_blocks(
19120 &mut self,
19121 block_ids: HashSet<CustomBlockId>,
19122 autoscroll: Option<Autoscroll>,
19123 cx: &mut Context<Self>,
19124 ) {
19125 self.display_map.update(cx, |display_map, cx| {
19126 display_map.remove_blocks(block_ids, cx)
19127 });
19128 if let Some(autoscroll) = autoscroll {
19129 self.request_autoscroll(autoscroll, cx);
19130 }
19131 cx.notify();
19132 }
19133
19134 pub fn row_for_block(
19135 &self,
19136 block_id: CustomBlockId,
19137 cx: &mut Context<Self>,
19138 ) -> Option<DisplayRow> {
19139 self.display_map
19140 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19141 }
19142
19143 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19144 self.focused_block = Some(focused_block);
19145 }
19146
19147 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19148 self.focused_block.take()
19149 }
19150
19151 pub fn insert_creases(
19152 &mut self,
19153 creases: impl IntoIterator<Item = Crease<Anchor>>,
19154 cx: &mut Context<Self>,
19155 ) -> Vec<CreaseId> {
19156 self.display_map
19157 .update(cx, |map, cx| map.insert_creases(creases, cx))
19158 }
19159
19160 pub fn remove_creases(
19161 &mut self,
19162 ids: impl IntoIterator<Item = CreaseId>,
19163 cx: &mut Context<Self>,
19164 ) -> Vec<(CreaseId, Range<Anchor>)> {
19165 self.display_map
19166 .update(cx, |map, cx| map.remove_creases(ids, cx))
19167 }
19168
19169 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19170 self.display_map
19171 .update(cx, |map, cx| map.snapshot(cx))
19172 .longest_row()
19173 }
19174
19175 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19176 self.display_map
19177 .update(cx, |map, cx| map.snapshot(cx))
19178 .max_point()
19179 }
19180
19181 pub fn text(&self, cx: &App) -> String {
19182 self.buffer.read(cx).read(cx).text()
19183 }
19184
19185 pub fn is_empty(&self, cx: &App) -> bool {
19186 self.buffer.read(cx).read(cx).is_empty()
19187 }
19188
19189 pub fn text_option(&self, cx: &App) -> Option<String> {
19190 let text = self.text(cx);
19191 let text = text.trim();
19192
19193 if text.is_empty() {
19194 return None;
19195 }
19196
19197 Some(text.to_string())
19198 }
19199
19200 pub fn set_text(
19201 &mut self,
19202 text: impl Into<Arc<str>>,
19203 window: &mut Window,
19204 cx: &mut Context<Self>,
19205 ) {
19206 self.transact(window, cx, |this, _, cx| {
19207 this.buffer
19208 .read(cx)
19209 .as_singleton()
19210 .expect("you can only call set_text on editors for singleton buffers")
19211 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19212 });
19213 }
19214
19215 pub fn display_text(&self, cx: &mut App) -> String {
19216 self.display_map
19217 .update(cx, |map, cx| map.snapshot(cx))
19218 .text()
19219 }
19220
19221 fn create_minimap(
19222 &self,
19223 minimap_settings: MinimapSettings,
19224 window: &mut Window,
19225 cx: &mut Context<Self>,
19226 ) -> Option<Entity<Self>> {
19227 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19228 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19229 }
19230
19231 fn initialize_new_minimap(
19232 &self,
19233 minimap_settings: MinimapSettings,
19234 window: &mut Window,
19235 cx: &mut Context<Self>,
19236 ) -> Entity<Self> {
19237 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19238
19239 let mut minimap = Editor::new_internal(
19240 EditorMode::Minimap {
19241 parent: cx.weak_entity(),
19242 },
19243 self.buffer.clone(),
19244 None,
19245 Some(self.display_map.clone()),
19246 window,
19247 cx,
19248 );
19249 minimap.scroll_manager.clone_state(&self.scroll_manager);
19250 minimap.set_text_style_refinement(TextStyleRefinement {
19251 font_size: Some(MINIMAP_FONT_SIZE),
19252 font_weight: Some(MINIMAP_FONT_WEIGHT),
19253 ..Default::default()
19254 });
19255 minimap.update_minimap_configuration(minimap_settings, cx);
19256 cx.new(|_| minimap)
19257 }
19258
19259 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19260 let current_line_highlight = minimap_settings
19261 .current_line_highlight
19262 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19263 self.set_current_line_highlight(Some(current_line_highlight));
19264 }
19265
19266 pub fn minimap(&self) -> Option<&Entity<Self>> {
19267 self.minimap
19268 .as_ref()
19269 .filter(|_| self.minimap_visibility.visible())
19270 }
19271
19272 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19273 let mut wrap_guides = smallvec![];
19274
19275 if self.show_wrap_guides == Some(false) {
19276 return wrap_guides;
19277 }
19278
19279 let settings = self.buffer.read(cx).language_settings(cx);
19280 if settings.show_wrap_guides {
19281 match self.soft_wrap_mode(cx) {
19282 SoftWrap::Column(soft_wrap) => {
19283 wrap_guides.push((soft_wrap as usize, true));
19284 }
19285 SoftWrap::Bounded(soft_wrap) => {
19286 wrap_guides.push((soft_wrap as usize, true));
19287 }
19288 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19289 }
19290 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19291 }
19292
19293 wrap_guides
19294 }
19295
19296 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19297 let settings = self.buffer.read(cx).language_settings(cx);
19298 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19299 match mode {
19300 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19301 SoftWrap::None
19302 }
19303 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19304 language_settings::SoftWrap::PreferredLineLength => {
19305 SoftWrap::Column(settings.preferred_line_length)
19306 }
19307 language_settings::SoftWrap::Bounded => {
19308 SoftWrap::Bounded(settings.preferred_line_length)
19309 }
19310 }
19311 }
19312
19313 pub fn set_soft_wrap_mode(
19314 &mut self,
19315 mode: language_settings::SoftWrap,
19316
19317 cx: &mut Context<Self>,
19318 ) {
19319 self.soft_wrap_mode_override = Some(mode);
19320 cx.notify();
19321 }
19322
19323 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19324 self.hard_wrap = hard_wrap;
19325 cx.notify();
19326 }
19327
19328 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19329 self.text_style_refinement = Some(style);
19330 }
19331
19332 /// called by the Element so we know what style we were most recently rendered with.
19333 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19334 // We intentionally do not inform the display map about the minimap style
19335 // so that wrapping is not recalculated and stays consistent for the editor
19336 // and its linked minimap.
19337 if !self.mode.is_minimap() {
19338 let font = style.text.font();
19339 let font_size = style.text.font_size.to_pixels(window.rem_size());
19340 let display_map = self
19341 .placeholder_display_map
19342 .as_ref()
19343 .filter(|_| self.is_empty(cx))
19344 .unwrap_or(&self.display_map);
19345
19346 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19347 }
19348 self.style = Some(style);
19349 }
19350
19351 pub fn style(&self) -> Option<&EditorStyle> {
19352 self.style.as_ref()
19353 }
19354
19355 // Called by the element. This method is not designed to be called outside of the editor
19356 // element's layout code because it does not notify when rewrapping is computed synchronously.
19357 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19358 if self.is_empty(cx) {
19359 self.placeholder_display_map
19360 .as_ref()
19361 .map_or(false, |display_map| {
19362 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19363 })
19364 } else {
19365 self.display_map
19366 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19367 }
19368 }
19369
19370 pub fn set_soft_wrap(&mut self) {
19371 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19372 }
19373
19374 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19375 if self.soft_wrap_mode_override.is_some() {
19376 self.soft_wrap_mode_override.take();
19377 } else {
19378 let soft_wrap = match self.soft_wrap_mode(cx) {
19379 SoftWrap::GitDiff => return,
19380 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19381 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19382 language_settings::SoftWrap::None
19383 }
19384 };
19385 self.soft_wrap_mode_override = Some(soft_wrap);
19386 }
19387 cx.notify();
19388 }
19389
19390 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19391 let Some(workspace) = self.workspace() else {
19392 return;
19393 };
19394 let fs = workspace.read(cx).app_state().fs.clone();
19395 let current_show = TabBarSettings::get_global(cx).show;
19396 update_settings_file(fs, cx, move |setting, _| {
19397 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19398 });
19399 }
19400
19401 pub fn toggle_indent_guides(
19402 &mut self,
19403 _: &ToggleIndentGuides,
19404 _: &mut Window,
19405 cx: &mut Context<Self>,
19406 ) {
19407 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19408 self.buffer
19409 .read(cx)
19410 .language_settings(cx)
19411 .indent_guides
19412 .enabled
19413 });
19414 self.show_indent_guides = Some(!currently_enabled);
19415 cx.notify();
19416 }
19417
19418 fn should_show_indent_guides(&self) -> Option<bool> {
19419 self.show_indent_guides
19420 }
19421
19422 pub fn toggle_line_numbers(
19423 &mut self,
19424 _: &ToggleLineNumbers,
19425 _: &mut Window,
19426 cx: &mut Context<Self>,
19427 ) {
19428 let mut editor_settings = EditorSettings::get_global(cx).clone();
19429 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19430 EditorSettings::override_global(editor_settings, cx);
19431 }
19432
19433 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19434 if let Some(show_line_numbers) = self.show_line_numbers {
19435 return show_line_numbers;
19436 }
19437 EditorSettings::get_global(cx).gutter.line_numbers
19438 }
19439
19440 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19441 self.use_relative_line_numbers
19442 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19443 }
19444
19445 pub fn toggle_relative_line_numbers(
19446 &mut self,
19447 _: &ToggleRelativeLineNumbers,
19448 _: &mut Window,
19449 cx: &mut Context<Self>,
19450 ) {
19451 let is_relative = self.should_use_relative_line_numbers(cx);
19452 self.set_relative_line_number(Some(!is_relative), cx)
19453 }
19454
19455 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19456 self.use_relative_line_numbers = is_relative;
19457 cx.notify();
19458 }
19459
19460 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19461 self.show_gutter = show_gutter;
19462 cx.notify();
19463 }
19464
19465 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19466 self.show_scrollbars = ScrollbarAxes {
19467 horizontal: show,
19468 vertical: show,
19469 };
19470 cx.notify();
19471 }
19472
19473 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19474 self.show_scrollbars.vertical = show;
19475 cx.notify();
19476 }
19477
19478 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19479 self.show_scrollbars.horizontal = show;
19480 cx.notify();
19481 }
19482
19483 pub fn set_minimap_visibility(
19484 &mut self,
19485 minimap_visibility: MinimapVisibility,
19486 window: &mut Window,
19487 cx: &mut Context<Self>,
19488 ) {
19489 if self.minimap_visibility != minimap_visibility {
19490 if minimap_visibility.visible() && self.minimap.is_none() {
19491 let minimap_settings = EditorSettings::get_global(cx).minimap;
19492 self.minimap =
19493 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19494 }
19495 self.minimap_visibility = minimap_visibility;
19496 cx.notify();
19497 }
19498 }
19499
19500 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19501 self.set_show_scrollbars(false, cx);
19502 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19503 }
19504
19505 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19506 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19507 }
19508
19509 /// Normally the text in full mode and auto height editors is padded on the
19510 /// left side by roughly half a character width for improved hit testing.
19511 ///
19512 /// Use this method to disable this for cases where this is not wanted (e.g.
19513 /// if you want to align the editor text with some other text above or below)
19514 /// or if you want to add this padding to single-line editors.
19515 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19516 self.offset_content = offset_content;
19517 cx.notify();
19518 }
19519
19520 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19521 self.show_line_numbers = Some(show_line_numbers);
19522 cx.notify();
19523 }
19524
19525 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19526 self.disable_expand_excerpt_buttons = true;
19527 cx.notify();
19528 }
19529
19530 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19531 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19532 cx.notify();
19533 }
19534
19535 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19536 self.show_code_actions = Some(show_code_actions);
19537 cx.notify();
19538 }
19539
19540 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19541 self.show_runnables = Some(show_runnables);
19542 cx.notify();
19543 }
19544
19545 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19546 self.show_breakpoints = Some(show_breakpoints);
19547 cx.notify();
19548 }
19549
19550 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19551 if self.display_map.read(cx).masked != masked {
19552 self.display_map.update(cx, |map, _| map.masked = masked);
19553 }
19554 cx.notify()
19555 }
19556
19557 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19558 self.show_wrap_guides = Some(show_wrap_guides);
19559 cx.notify();
19560 }
19561
19562 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19563 self.show_indent_guides = Some(show_indent_guides);
19564 cx.notify();
19565 }
19566
19567 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19568 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19569 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19570 && let Some(dir) = file.abs_path(cx).parent()
19571 {
19572 return Some(dir.to_owned());
19573 }
19574 }
19575
19576 None
19577 }
19578
19579 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19580 self.active_excerpt(cx)?
19581 .1
19582 .read(cx)
19583 .file()
19584 .and_then(|f| f.as_local())
19585 }
19586
19587 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19588 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19589 let buffer = buffer.read(cx);
19590 if let Some(project_path) = buffer.project_path(cx) {
19591 let project = self.project()?.read(cx);
19592 project.absolute_path(&project_path, cx)
19593 } else {
19594 buffer
19595 .file()
19596 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19597 }
19598 })
19599 }
19600
19601 pub fn reveal_in_finder(
19602 &mut self,
19603 _: &RevealInFileManager,
19604 _window: &mut Window,
19605 cx: &mut Context<Self>,
19606 ) {
19607 if let Some(target) = self.target_file(cx) {
19608 cx.reveal_path(&target.abs_path(cx));
19609 }
19610 }
19611
19612 pub fn copy_path(
19613 &mut self,
19614 _: &zed_actions::workspace::CopyPath,
19615 _window: &mut Window,
19616 cx: &mut Context<Self>,
19617 ) {
19618 if let Some(path) = self.target_file_abs_path(cx)
19619 && let Some(path) = path.to_str()
19620 {
19621 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19622 } else {
19623 cx.propagate();
19624 }
19625 }
19626
19627 pub fn copy_relative_path(
19628 &mut self,
19629 _: &zed_actions::workspace::CopyRelativePath,
19630 _window: &mut Window,
19631 cx: &mut Context<Self>,
19632 ) {
19633 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19634 let project = self.project()?.read(cx);
19635 let path = buffer.read(cx).file()?.path();
19636 let path = path.display(project.path_style(cx));
19637 Some(path)
19638 }) {
19639 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19640 } else {
19641 cx.propagate();
19642 }
19643 }
19644
19645 /// Returns the project path for the editor's buffer, if any buffer is
19646 /// opened in the editor.
19647 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19648 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19649 buffer.read(cx).project_path(cx)
19650 } else {
19651 None
19652 }
19653 }
19654
19655 // Returns true if the editor handled a go-to-line request
19656 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19657 maybe!({
19658 let breakpoint_store = self.breakpoint_store.as_ref()?;
19659
19660 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19661 else {
19662 self.clear_row_highlights::<ActiveDebugLine>();
19663 return None;
19664 };
19665
19666 let position = active_stack_frame.position;
19667 let buffer_id = position.buffer_id?;
19668 let snapshot = self
19669 .project
19670 .as_ref()?
19671 .read(cx)
19672 .buffer_for_id(buffer_id, cx)?
19673 .read(cx)
19674 .snapshot();
19675
19676 let mut handled = false;
19677 for (id, ExcerptRange { context, .. }) in
19678 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19679 {
19680 if context.start.cmp(&position, &snapshot).is_ge()
19681 || context.end.cmp(&position, &snapshot).is_lt()
19682 {
19683 continue;
19684 }
19685 let snapshot = self.buffer.read(cx).snapshot(cx);
19686 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19687
19688 handled = true;
19689 self.clear_row_highlights::<ActiveDebugLine>();
19690
19691 self.go_to_line::<ActiveDebugLine>(
19692 multibuffer_anchor,
19693 Some(cx.theme().colors().editor_debugger_active_line_background),
19694 window,
19695 cx,
19696 );
19697
19698 cx.notify();
19699 }
19700
19701 handled.then_some(())
19702 })
19703 .is_some()
19704 }
19705
19706 pub fn copy_file_name_without_extension(
19707 &mut self,
19708 _: &CopyFileNameWithoutExtension,
19709 _: &mut Window,
19710 cx: &mut Context<Self>,
19711 ) {
19712 if let Some(file) = self.target_file(cx)
19713 && let Some(file_stem) = file.path().file_stem()
19714 {
19715 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19716 }
19717 }
19718
19719 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19720 if let Some(file) = self.target_file(cx)
19721 && let Some(name) = file.path().file_name()
19722 {
19723 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19724 }
19725 }
19726
19727 pub fn toggle_git_blame(
19728 &mut self,
19729 _: &::git::Blame,
19730 window: &mut Window,
19731 cx: &mut Context<Self>,
19732 ) {
19733 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19734
19735 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19736 self.start_git_blame(true, window, cx);
19737 }
19738
19739 cx.notify();
19740 }
19741
19742 pub fn toggle_git_blame_inline(
19743 &mut self,
19744 _: &ToggleGitBlameInline,
19745 window: &mut Window,
19746 cx: &mut Context<Self>,
19747 ) {
19748 self.toggle_git_blame_inline_internal(true, window, cx);
19749 cx.notify();
19750 }
19751
19752 pub fn open_git_blame_commit(
19753 &mut self,
19754 _: &OpenGitBlameCommit,
19755 window: &mut Window,
19756 cx: &mut Context<Self>,
19757 ) {
19758 self.open_git_blame_commit_internal(window, cx);
19759 }
19760
19761 fn open_git_blame_commit_internal(
19762 &mut self,
19763 window: &mut Window,
19764 cx: &mut Context<Self>,
19765 ) -> Option<()> {
19766 let blame = self.blame.as_ref()?;
19767 let snapshot = self.snapshot(window, cx);
19768 let cursor = self.selections.newest::<Point>(cx).head();
19769 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
19770 let (_, blame_entry) = blame
19771 .update(cx, |blame, cx| {
19772 blame
19773 .blame_for_rows(
19774 &[RowInfo {
19775 buffer_id: Some(buffer.remote_id()),
19776 buffer_row: Some(point.row),
19777 ..Default::default()
19778 }],
19779 cx,
19780 )
19781 .next()
19782 })
19783 .flatten()?;
19784 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19785 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19786 let workspace = self.workspace()?.downgrade();
19787 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19788 None
19789 }
19790
19791 pub fn git_blame_inline_enabled(&self) -> bool {
19792 self.git_blame_inline_enabled
19793 }
19794
19795 pub fn toggle_selection_menu(
19796 &mut self,
19797 _: &ToggleSelectionMenu,
19798 _: &mut Window,
19799 cx: &mut Context<Self>,
19800 ) {
19801 self.show_selection_menu = self
19802 .show_selection_menu
19803 .map(|show_selections_menu| !show_selections_menu)
19804 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19805
19806 cx.notify();
19807 }
19808
19809 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19810 self.show_selection_menu
19811 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19812 }
19813
19814 fn start_git_blame(
19815 &mut self,
19816 user_triggered: bool,
19817 window: &mut Window,
19818 cx: &mut Context<Self>,
19819 ) {
19820 if let Some(project) = self.project() {
19821 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19822 && buffer.read(cx).file().is_none()
19823 {
19824 return;
19825 }
19826
19827 let focused = self.focus_handle(cx).contains_focused(window, cx);
19828
19829 let project = project.clone();
19830 let blame = cx
19831 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19832 self.blame_subscription =
19833 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19834 self.blame = Some(blame);
19835 }
19836 }
19837
19838 fn toggle_git_blame_inline_internal(
19839 &mut self,
19840 user_triggered: bool,
19841 window: &mut Window,
19842 cx: &mut Context<Self>,
19843 ) {
19844 if self.git_blame_inline_enabled {
19845 self.git_blame_inline_enabled = false;
19846 self.show_git_blame_inline = false;
19847 self.show_git_blame_inline_delay_task.take();
19848 } else {
19849 self.git_blame_inline_enabled = true;
19850 self.start_git_blame_inline(user_triggered, window, cx);
19851 }
19852
19853 cx.notify();
19854 }
19855
19856 fn start_git_blame_inline(
19857 &mut self,
19858 user_triggered: bool,
19859 window: &mut Window,
19860 cx: &mut Context<Self>,
19861 ) {
19862 self.start_git_blame(user_triggered, window, cx);
19863
19864 if ProjectSettings::get_global(cx)
19865 .git
19866 .inline_blame_delay()
19867 .is_some()
19868 {
19869 self.start_inline_blame_timer(window, cx);
19870 } else {
19871 self.show_git_blame_inline = true
19872 }
19873 }
19874
19875 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19876 self.blame.as_ref()
19877 }
19878
19879 pub fn show_git_blame_gutter(&self) -> bool {
19880 self.show_git_blame_gutter
19881 }
19882
19883 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19884 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19885 }
19886
19887 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19888 self.show_git_blame_inline
19889 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19890 && !self.newest_selection_head_on_empty_line(cx)
19891 && self.has_blame_entries(cx)
19892 }
19893
19894 fn has_blame_entries(&self, cx: &App) -> bool {
19895 self.blame()
19896 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19897 }
19898
19899 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19900 let cursor_anchor = self.selections.newest_anchor().head();
19901
19902 let snapshot = self.buffer.read(cx).snapshot(cx);
19903 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19904
19905 snapshot.line_len(buffer_row) == 0
19906 }
19907
19908 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19909 let buffer_and_selection = maybe!({
19910 let selection = self.selections.newest::<Point>(cx);
19911 let selection_range = selection.range();
19912
19913 let multi_buffer = self.buffer().read(cx);
19914 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19915 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19916
19917 let (buffer, range, _) = if selection.reversed {
19918 buffer_ranges.first()
19919 } else {
19920 buffer_ranges.last()
19921 }?;
19922
19923 let selection = text::ToPoint::to_point(&range.start, buffer).row
19924 ..text::ToPoint::to_point(&range.end, buffer).row;
19925 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19926 });
19927
19928 let Some((buffer, selection)) = buffer_and_selection else {
19929 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19930 };
19931
19932 let Some(project) = self.project() else {
19933 return Task::ready(Err(anyhow!("editor does not have project")));
19934 };
19935
19936 project.update(cx, |project, cx| {
19937 project.get_permalink_to_line(&buffer, selection, cx)
19938 })
19939 }
19940
19941 pub fn copy_permalink_to_line(
19942 &mut self,
19943 _: &CopyPermalinkToLine,
19944 window: &mut Window,
19945 cx: &mut Context<Self>,
19946 ) {
19947 let permalink_task = self.get_permalink_to_line(cx);
19948 let workspace = self.workspace();
19949
19950 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19951 Ok(permalink) => {
19952 cx.update(|_, cx| {
19953 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19954 })
19955 .ok();
19956 }
19957 Err(err) => {
19958 let message = format!("Failed to copy permalink: {err}");
19959
19960 anyhow::Result::<()>::Err(err).log_err();
19961
19962 if let Some(workspace) = workspace {
19963 workspace
19964 .update_in(cx, |workspace, _, cx| {
19965 struct CopyPermalinkToLine;
19966
19967 workspace.show_toast(
19968 Toast::new(
19969 NotificationId::unique::<CopyPermalinkToLine>(),
19970 message,
19971 ),
19972 cx,
19973 )
19974 })
19975 .ok();
19976 }
19977 }
19978 })
19979 .detach();
19980 }
19981
19982 pub fn copy_file_location(
19983 &mut self,
19984 _: &CopyFileLocation,
19985 _: &mut Window,
19986 cx: &mut Context<Self>,
19987 ) {
19988 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19989 if let Some(file) = self.target_file(cx) {
19990 let path = file.path().display(file.path_style(cx));
19991 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19992 }
19993 }
19994
19995 pub fn open_permalink_to_line(
19996 &mut self,
19997 _: &OpenPermalinkToLine,
19998 window: &mut Window,
19999 cx: &mut Context<Self>,
20000 ) {
20001 let permalink_task = self.get_permalink_to_line(cx);
20002 let workspace = self.workspace();
20003
20004 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20005 Ok(permalink) => {
20006 cx.update(|_, cx| {
20007 cx.open_url(permalink.as_ref());
20008 })
20009 .ok();
20010 }
20011 Err(err) => {
20012 let message = format!("Failed to open permalink: {err}");
20013
20014 anyhow::Result::<()>::Err(err).log_err();
20015
20016 if let Some(workspace) = workspace {
20017 workspace
20018 .update(cx, |workspace, cx| {
20019 struct OpenPermalinkToLine;
20020
20021 workspace.show_toast(
20022 Toast::new(
20023 NotificationId::unique::<OpenPermalinkToLine>(),
20024 message,
20025 ),
20026 cx,
20027 )
20028 })
20029 .ok();
20030 }
20031 }
20032 })
20033 .detach();
20034 }
20035
20036 pub fn insert_uuid_v4(
20037 &mut self,
20038 _: &InsertUuidV4,
20039 window: &mut Window,
20040 cx: &mut Context<Self>,
20041 ) {
20042 self.insert_uuid(UuidVersion::V4, window, cx);
20043 }
20044
20045 pub fn insert_uuid_v7(
20046 &mut self,
20047 _: &InsertUuidV7,
20048 window: &mut Window,
20049 cx: &mut Context<Self>,
20050 ) {
20051 self.insert_uuid(UuidVersion::V7, window, cx);
20052 }
20053
20054 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20055 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20056 self.transact(window, cx, |this, window, cx| {
20057 let edits = this
20058 .selections
20059 .all::<Point>(cx)
20060 .into_iter()
20061 .map(|selection| {
20062 let uuid = match version {
20063 UuidVersion::V4 => uuid::Uuid::new_v4(),
20064 UuidVersion::V7 => uuid::Uuid::now_v7(),
20065 };
20066
20067 (selection.range(), uuid.to_string())
20068 });
20069 this.edit(edits, cx);
20070 this.refresh_edit_prediction(true, false, window, cx);
20071 });
20072 }
20073
20074 pub fn open_selections_in_multibuffer(
20075 &mut self,
20076 _: &OpenSelectionsInMultibuffer,
20077 window: &mut Window,
20078 cx: &mut Context<Self>,
20079 ) {
20080 let multibuffer = self.buffer.read(cx);
20081
20082 let Some(buffer) = multibuffer.as_singleton() else {
20083 return;
20084 };
20085
20086 let Some(workspace) = self.workspace() else {
20087 return;
20088 };
20089
20090 let title = multibuffer.title(cx).to_string();
20091
20092 let locations = self
20093 .selections
20094 .all_anchors(cx)
20095 .iter()
20096 .map(|selection| {
20097 (
20098 buffer.clone(),
20099 (selection.start.text_anchor..selection.end.text_anchor)
20100 .to_point(buffer.read(cx)),
20101 )
20102 })
20103 .into_group_map();
20104
20105 cx.spawn_in(window, async move |_, cx| {
20106 workspace.update_in(cx, |workspace, window, cx| {
20107 Self::open_locations_in_multibuffer(
20108 workspace,
20109 locations,
20110 format!("Selections for '{title}'"),
20111 false,
20112 MultibufferSelectionMode::All,
20113 window,
20114 cx,
20115 );
20116 })
20117 })
20118 .detach();
20119 }
20120
20121 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20122 /// last highlight added will be used.
20123 ///
20124 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20125 pub fn highlight_rows<T: 'static>(
20126 &mut self,
20127 range: Range<Anchor>,
20128 color: Hsla,
20129 options: RowHighlightOptions,
20130 cx: &mut Context<Self>,
20131 ) {
20132 let snapshot = self.buffer().read(cx).snapshot(cx);
20133 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20134 let ix = row_highlights.binary_search_by(|highlight| {
20135 Ordering::Equal
20136 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20137 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20138 });
20139
20140 if let Err(mut ix) = ix {
20141 let index = post_inc(&mut self.highlight_order);
20142
20143 // If this range intersects with the preceding highlight, then merge it with
20144 // the preceding highlight. Otherwise insert a new highlight.
20145 let mut merged = false;
20146 if ix > 0 {
20147 let prev_highlight = &mut row_highlights[ix - 1];
20148 if prev_highlight
20149 .range
20150 .end
20151 .cmp(&range.start, &snapshot)
20152 .is_ge()
20153 {
20154 ix -= 1;
20155 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20156 prev_highlight.range.end = range.end;
20157 }
20158 merged = true;
20159 prev_highlight.index = index;
20160 prev_highlight.color = color;
20161 prev_highlight.options = options;
20162 }
20163 }
20164
20165 if !merged {
20166 row_highlights.insert(
20167 ix,
20168 RowHighlight {
20169 range,
20170 index,
20171 color,
20172 options,
20173 type_id: TypeId::of::<T>(),
20174 },
20175 );
20176 }
20177
20178 // If any of the following highlights intersect with this one, merge them.
20179 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20180 let highlight = &row_highlights[ix];
20181 if next_highlight
20182 .range
20183 .start
20184 .cmp(&highlight.range.end, &snapshot)
20185 .is_le()
20186 {
20187 if next_highlight
20188 .range
20189 .end
20190 .cmp(&highlight.range.end, &snapshot)
20191 .is_gt()
20192 {
20193 row_highlights[ix].range.end = next_highlight.range.end;
20194 }
20195 row_highlights.remove(ix + 1);
20196 } else {
20197 break;
20198 }
20199 }
20200 }
20201 }
20202
20203 /// Remove any highlighted row ranges of the given type that intersect the
20204 /// given ranges.
20205 pub fn remove_highlighted_rows<T: 'static>(
20206 &mut self,
20207 ranges_to_remove: Vec<Range<Anchor>>,
20208 cx: &mut Context<Self>,
20209 ) {
20210 let snapshot = self.buffer().read(cx).snapshot(cx);
20211 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20212 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20213 row_highlights.retain(|highlight| {
20214 while let Some(range_to_remove) = ranges_to_remove.peek() {
20215 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20216 Ordering::Less | Ordering::Equal => {
20217 ranges_to_remove.next();
20218 }
20219 Ordering::Greater => {
20220 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20221 Ordering::Less | Ordering::Equal => {
20222 return false;
20223 }
20224 Ordering::Greater => break,
20225 }
20226 }
20227 }
20228 }
20229
20230 true
20231 })
20232 }
20233
20234 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20235 pub fn clear_row_highlights<T: 'static>(&mut self) {
20236 self.highlighted_rows.remove(&TypeId::of::<T>());
20237 }
20238
20239 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20240 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20241 self.highlighted_rows
20242 .get(&TypeId::of::<T>())
20243 .map_or(&[] as &[_], |vec| vec.as_slice())
20244 .iter()
20245 .map(|highlight| (highlight.range.clone(), highlight.color))
20246 }
20247
20248 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20249 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20250 /// Allows to ignore certain kinds of highlights.
20251 pub fn highlighted_display_rows(
20252 &self,
20253 window: &mut Window,
20254 cx: &mut App,
20255 ) -> BTreeMap<DisplayRow, LineHighlight> {
20256 let snapshot = self.snapshot(window, cx);
20257 let mut used_highlight_orders = HashMap::default();
20258 self.highlighted_rows
20259 .iter()
20260 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20261 .fold(
20262 BTreeMap::<DisplayRow, LineHighlight>::new(),
20263 |mut unique_rows, highlight| {
20264 let start = highlight.range.start.to_display_point(&snapshot);
20265 let end = highlight.range.end.to_display_point(&snapshot);
20266 let start_row = start.row().0;
20267 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20268 && end.column() == 0
20269 {
20270 end.row().0.saturating_sub(1)
20271 } else {
20272 end.row().0
20273 };
20274 for row in start_row..=end_row {
20275 let used_index =
20276 used_highlight_orders.entry(row).or_insert(highlight.index);
20277 if highlight.index >= *used_index {
20278 *used_index = highlight.index;
20279 unique_rows.insert(
20280 DisplayRow(row),
20281 LineHighlight {
20282 include_gutter: highlight.options.include_gutter,
20283 border: None,
20284 background: highlight.color.into(),
20285 type_id: Some(highlight.type_id),
20286 },
20287 );
20288 }
20289 }
20290 unique_rows
20291 },
20292 )
20293 }
20294
20295 pub fn highlighted_display_row_for_autoscroll(
20296 &self,
20297 snapshot: &DisplaySnapshot,
20298 ) -> Option<DisplayRow> {
20299 self.highlighted_rows
20300 .values()
20301 .flat_map(|highlighted_rows| highlighted_rows.iter())
20302 .filter_map(|highlight| {
20303 if highlight.options.autoscroll {
20304 Some(highlight.range.start.to_display_point(snapshot).row())
20305 } else {
20306 None
20307 }
20308 })
20309 .min()
20310 }
20311
20312 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20313 self.highlight_background::<SearchWithinRange>(
20314 ranges,
20315 |colors| colors.colors().editor_document_highlight_read_background,
20316 cx,
20317 )
20318 }
20319
20320 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20321 self.breadcrumb_header = Some(new_header);
20322 }
20323
20324 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20325 self.clear_background_highlights::<SearchWithinRange>(cx);
20326 }
20327
20328 pub fn highlight_background<T: 'static>(
20329 &mut self,
20330 ranges: &[Range<Anchor>],
20331 color_fetcher: fn(&Theme) -> Hsla,
20332 cx: &mut Context<Self>,
20333 ) {
20334 self.background_highlights.insert(
20335 HighlightKey::Type(TypeId::of::<T>()),
20336 (color_fetcher, Arc::from(ranges)),
20337 );
20338 self.scrollbar_marker_state.dirty = true;
20339 cx.notify();
20340 }
20341
20342 pub fn highlight_background_key<T: 'static>(
20343 &mut self,
20344 key: usize,
20345 ranges: &[Range<Anchor>],
20346 color_fetcher: fn(&Theme) -> Hsla,
20347 cx: &mut Context<Self>,
20348 ) {
20349 self.background_highlights.insert(
20350 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20351 (color_fetcher, Arc::from(ranges)),
20352 );
20353 self.scrollbar_marker_state.dirty = true;
20354 cx.notify();
20355 }
20356
20357 pub fn clear_background_highlights<T: 'static>(
20358 &mut self,
20359 cx: &mut Context<Self>,
20360 ) -> Option<BackgroundHighlight> {
20361 let text_highlights = self
20362 .background_highlights
20363 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20364 if !text_highlights.1.is_empty() {
20365 self.scrollbar_marker_state.dirty = true;
20366 cx.notify();
20367 }
20368 Some(text_highlights)
20369 }
20370
20371 pub fn highlight_gutter<T: 'static>(
20372 &mut self,
20373 ranges: impl Into<Vec<Range<Anchor>>>,
20374 color_fetcher: fn(&App) -> Hsla,
20375 cx: &mut Context<Self>,
20376 ) {
20377 self.gutter_highlights
20378 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20379 cx.notify();
20380 }
20381
20382 pub fn clear_gutter_highlights<T: 'static>(
20383 &mut self,
20384 cx: &mut Context<Self>,
20385 ) -> Option<GutterHighlight> {
20386 cx.notify();
20387 self.gutter_highlights.remove(&TypeId::of::<T>())
20388 }
20389
20390 pub fn insert_gutter_highlight<T: 'static>(
20391 &mut self,
20392 range: Range<Anchor>,
20393 color_fetcher: fn(&App) -> Hsla,
20394 cx: &mut Context<Self>,
20395 ) {
20396 let snapshot = self.buffer().read(cx).snapshot(cx);
20397 let mut highlights = self
20398 .gutter_highlights
20399 .remove(&TypeId::of::<T>())
20400 .map(|(_, highlights)| highlights)
20401 .unwrap_or_default();
20402 let ix = highlights.binary_search_by(|highlight| {
20403 Ordering::Equal
20404 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20405 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20406 });
20407 if let Err(ix) = ix {
20408 highlights.insert(ix, range);
20409 }
20410 self.gutter_highlights
20411 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20412 }
20413
20414 pub fn remove_gutter_highlights<T: 'static>(
20415 &mut self,
20416 ranges_to_remove: Vec<Range<Anchor>>,
20417 cx: &mut Context<Self>,
20418 ) {
20419 let snapshot = self.buffer().read(cx).snapshot(cx);
20420 let Some((color_fetcher, mut gutter_highlights)) =
20421 self.gutter_highlights.remove(&TypeId::of::<T>())
20422 else {
20423 return;
20424 };
20425 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20426 gutter_highlights.retain(|highlight| {
20427 while let Some(range_to_remove) = ranges_to_remove.peek() {
20428 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20429 Ordering::Less | Ordering::Equal => {
20430 ranges_to_remove.next();
20431 }
20432 Ordering::Greater => {
20433 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20434 Ordering::Less | Ordering::Equal => {
20435 return false;
20436 }
20437 Ordering::Greater => break,
20438 }
20439 }
20440 }
20441 }
20442
20443 true
20444 });
20445 self.gutter_highlights
20446 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20447 }
20448
20449 #[cfg(feature = "test-support")]
20450 pub fn all_text_highlights(
20451 &self,
20452 window: &mut Window,
20453 cx: &mut Context<Self>,
20454 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20455 let snapshot = self.snapshot(window, cx);
20456 self.display_map.update(cx, |display_map, _| {
20457 display_map
20458 .all_text_highlights()
20459 .map(|highlight| {
20460 let (style, ranges) = highlight.as_ref();
20461 (
20462 *style,
20463 ranges
20464 .iter()
20465 .map(|range| range.clone().to_display_points(&snapshot))
20466 .collect(),
20467 )
20468 })
20469 .collect()
20470 })
20471 }
20472
20473 #[cfg(feature = "test-support")]
20474 pub fn all_text_background_highlights(
20475 &self,
20476 window: &mut Window,
20477 cx: &mut Context<Self>,
20478 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20479 let snapshot = self.snapshot(window, cx);
20480 let buffer = &snapshot.buffer_snapshot();
20481 let start = buffer.anchor_before(0);
20482 let end = buffer.anchor_after(buffer.len());
20483 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20484 }
20485
20486 #[cfg(any(test, feature = "test-support"))]
20487 pub fn sorted_background_highlights_in_range(
20488 &self,
20489 search_range: Range<Anchor>,
20490 display_snapshot: &DisplaySnapshot,
20491 theme: &Theme,
20492 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20493 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20494 res.sort_by(|a, b| {
20495 a.0.start
20496 .cmp(&b.0.start)
20497 .then_with(|| a.0.end.cmp(&b.0.end))
20498 .then_with(|| a.1.cmp(&b.1))
20499 });
20500 res
20501 }
20502
20503 #[cfg(feature = "test-support")]
20504 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20505 let snapshot = self.buffer().read(cx).snapshot(cx);
20506
20507 let highlights = self
20508 .background_highlights
20509 .get(&HighlightKey::Type(TypeId::of::<
20510 items::BufferSearchHighlights,
20511 >()));
20512
20513 if let Some((_color, ranges)) = highlights {
20514 ranges
20515 .iter()
20516 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20517 .collect_vec()
20518 } else {
20519 vec![]
20520 }
20521 }
20522
20523 fn document_highlights_for_position<'a>(
20524 &'a self,
20525 position: Anchor,
20526 buffer: &'a MultiBufferSnapshot,
20527 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20528 let read_highlights = self
20529 .background_highlights
20530 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20531 .map(|h| &h.1);
20532 let write_highlights = self
20533 .background_highlights
20534 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20535 .map(|h| &h.1);
20536 let left_position = position.bias_left(buffer);
20537 let right_position = position.bias_right(buffer);
20538 read_highlights
20539 .into_iter()
20540 .chain(write_highlights)
20541 .flat_map(move |ranges| {
20542 let start_ix = match ranges.binary_search_by(|probe| {
20543 let cmp = probe.end.cmp(&left_position, buffer);
20544 if cmp.is_ge() {
20545 Ordering::Greater
20546 } else {
20547 Ordering::Less
20548 }
20549 }) {
20550 Ok(i) | Err(i) => i,
20551 };
20552
20553 ranges[start_ix..]
20554 .iter()
20555 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20556 })
20557 }
20558
20559 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20560 self.background_highlights
20561 .get(&HighlightKey::Type(TypeId::of::<T>()))
20562 .is_some_and(|(_, highlights)| !highlights.is_empty())
20563 }
20564
20565 /// Returns all background highlights for a given range.
20566 ///
20567 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20568 pub fn background_highlights_in_range(
20569 &self,
20570 search_range: Range<Anchor>,
20571 display_snapshot: &DisplaySnapshot,
20572 theme: &Theme,
20573 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20574 let mut results = Vec::new();
20575 for (color_fetcher, ranges) in self.background_highlights.values() {
20576 let color = color_fetcher(theme);
20577 let start_ix = match ranges.binary_search_by(|probe| {
20578 let cmp = probe
20579 .end
20580 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20581 if cmp.is_gt() {
20582 Ordering::Greater
20583 } else {
20584 Ordering::Less
20585 }
20586 }) {
20587 Ok(i) | Err(i) => i,
20588 };
20589 for range in &ranges[start_ix..] {
20590 if range
20591 .start
20592 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20593 .is_ge()
20594 {
20595 break;
20596 }
20597
20598 let start = range.start.to_display_point(display_snapshot);
20599 let end = range.end.to_display_point(display_snapshot);
20600 results.push((start..end, color))
20601 }
20602 }
20603 results
20604 }
20605
20606 pub fn gutter_highlights_in_range(
20607 &self,
20608 search_range: Range<Anchor>,
20609 display_snapshot: &DisplaySnapshot,
20610 cx: &App,
20611 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20612 let mut results = Vec::new();
20613 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20614 let color = color_fetcher(cx);
20615 let start_ix = match ranges.binary_search_by(|probe| {
20616 let cmp = probe
20617 .end
20618 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20619 if cmp.is_gt() {
20620 Ordering::Greater
20621 } else {
20622 Ordering::Less
20623 }
20624 }) {
20625 Ok(i) | Err(i) => i,
20626 };
20627 for range in &ranges[start_ix..] {
20628 if range
20629 .start
20630 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20631 .is_ge()
20632 {
20633 break;
20634 }
20635
20636 let start = range.start.to_display_point(display_snapshot);
20637 let end = range.end.to_display_point(display_snapshot);
20638 results.push((start..end, color))
20639 }
20640 }
20641 results
20642 }
20643
20644 /// Get the text ranges corresponding to the redaction query
20645 pub fn redacted_ranges(
20646 &self,
20647 search_range: Range<Anchor>,
20648 display_snapshot: &DisplaySnapshot,
20649 cx: &App,
20650 ) -> Vec<Range<DisplayPoint>> {
20651 display_snapshot
20652 .buffer_snapshot()
20653 .redacted_ranges(search_range, |file| {
20654 if let Some(file) = file {
20655 file.is_private()
20656 && EditorSettings::get(
20657 Some(SettingsLocation {
20658 worktree_id: file.worktree_id(cx),
20659 path: file.path().as_ref(),
20660 }),
20661 cx,
20662 )
20663 .redact_private_values
20664 } else {
20665 false
20666 }
20667 })
20668 .map(|range| {
20669 range.start.to_display_point(display_snapshot)
20670 ..range.end.to_display_point(display_snapshot)
20671 })
20672 .collect()
20673 }
20674
20675 pub fn highlight_text_key<T: 'static>(
20676 &mut self,
20677 key: usize,
20678 ranges: Vec<Range<Anchor>>,
20679 style: HighlightStyle,
20680 cx: &mut Context<Self>,
20681 ) {
20682 self.display_map.update(cx, |map, _| {
20683 map.highlight_text(
20684 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20685 ranges,
20686 style,
20687 );
20688 });
20689 cx.notify();
20690 }
20691
20692 pub fn highlight_text<T: 'static>(
20693 &mut self,
20694 ranges: Vec<Range<Anchor>>,
20695 style: HighlightStyle,
20696 cx: &mut Context<Self>,
20697 ) {
20698 self.display_map.update(cx, |map, _| {
20699 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20700 });
20701 cx.notify();
20702 }
20703
20704 pub(crate) fn highlight_inlays<T: 'static>(
20705 &mut self,
20706 highlights: Vec<InlayHighlight>,
20707 style: HighlightStyle,
20708 cx: &mut Context<Self>,
20709 ) {
20710 self.display_map.update(cx, |map, _| {
20711 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20712 });
20713 cx.notify();
20714 }
20715
20716 pub fn text_highlights<'a, T: 'static>(
20717 &'a self,
20718 cx: &'a App,
20719 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20720 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20721 }
20722
20723 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20724 let cleared = self
20725 .display_map
20726 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20727 if cleared {
20728 cx.notify();
20729 }
20730 }
20731
20732 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20733 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20734 && self.focus_handle.is_focused(window)
20735 }
20736
20737 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20738 self.show_cursor_when_unfocused = is_enabled;
20739 cx.notify();
20740 }
20741
20742 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20743 cx.notify();
20744 }
20745
20746 fn on_debug_session_event(
20747 &mut self,
20748 _session: Entity<Session>,
20749 event: &SessionEvent,
20750 cx: &mut Context<Self>,
20751 ) {
20752 if let SessionEvent::InvalidateInlineValue = event {
20753 self.refresh_inline_values(cx);
20754 }
20755 }
20756
20757 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20758 let Some(project) = self.project.clone() else {
20759 return;
20760 };
20761
20762 if !self.inline_value_cache.enabled {
20763 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20764 self.splice_inlays(&inlays, Vec::new(), cx);
20765 return;
20766 }
20767
20768 let current_execution_position = self
20769 .highlighted_rows
20770 .get(&TypeId::of::<ActiveDebugLine>())
20771 .and_then(|lines| lines.last().map(|line| line.range.end));
20772
20773 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20774 let inline_values = editor
20775 .update(cx, |editor, cx| {
20776 let Some(current_execution_position) = current_execution_position else {
20777 return Some(Task::ready(Ok(Vec::new())));
20778 };
20779
20780 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20781 let snapshot = buffer.snapshot(cx);
20782
20783 let excerpt = snapshot.excerpt_containing(
20784 current_execution_position..current_execution_position,
20785 )?;
20786
20787 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20788 })?;
20789
20790 let range =
20791 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20792
20793 project.inline_values(buffer, range, cx)
20794 })
20795 .ok()
20796 .flatten()?
20797 .await
20798 .context("refreshing debugger inlays")
20799 .log_err()?;
20800
20801 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20802
20803 for (buffer_id, inline_value) in inline_values
20804 .into_iter()
20805 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20806 {
20807 buffer_inline_values
20808 .entry(buffer_id)
20809 .or_default()
20810 .push(inline_value);
20811 }
20812
20813 editor
20814 .update(cx, |editor, cx| {
20815 let snapshot = editor.buffer.read(cx).snapshot(cx);
20816 let mut new_inlays = Vec::default();
20817
20818 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20819 let buffer_id = buffer_snapshot.remote_id();
20820 buffer_inline_values
20821 .get(&buffer_id)
20822 .into_iter()
20823 .flatten()
20824 .for_each(|hint| {
20825 let inlay = Inlay::debugger(
20826 post_inc(&mut editor.next_inlay_id),
20827 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20828 hint.text(),
20829 );
20830 if !inlay.text().chars().contains(&'\n') {
20831 new_inlays.push(inlay);
20832 }
20833 });
20834 }
20835
20836 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20837 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20838
20839 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20840 })
20841 .ok()?;
20842 Some(())
20843 });
20844 }
20845
20846 fn on_buffer_event(
20847 &mut self,
20848 multibuffer: &Entity<MultiBuffer>,
20849 event: &multi_buffer::Event,
20850 window: &mut Window,
20851 cx: &mut Context<Self>,
20852 ) {
20853 match event {
20854 multi_buffer::Event::Edited {
20855 singleton_buffer_edited,
20856 edited_buffer,
20857 } => {
20858 self.scrollbar_marker_state.dirty = true;
20859 self.active_indent_guides_state.dirty = true;
20860 self.refresh_active_diagnostics(cx);
20861 self.refresh_code_actions(window, cx);
20862 self.refresh_selected_text_highlights(true, window, cx);
20863 self.refresh_single_line_folds(window, cx);
20864 refresh_matching_bracket_highlights(self, cx);
20865 if self.has_active_edit_prediction() {
20866 self.update_visible_edit_prediction(window, cx);
20867 }
20868 if let Some(project) = self.project.as_ref()
20869 && let Some(edited_buffer) = edited_buffer
20870 {
20871 project.update(cx, |project, cx| {
20872 self.registered_buffers
20873 .entry(edited_buffer.read(cx).remote_id())
20874 .or_insert_with(|| {
20875 project.register_buffer_with_language_servers(edited_buffer, cx)
20876 });
20877 });
20878 }
20879 cx.emit(EditorEvent::BufferEdited);
20880 cx.emit(SearchEvent::MatchesInvalidated);
20881
20882 if let Some(buffer) = edited_buffer {
20883 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20884 }
20885
20886 if *singleton_buffer_edited {
20887 if let Some(buffer) = edited_buffer
20888 && buffer.read(cx).file().is_none()
20889 {
20890 cx.emit(EditorEvent::TitleChanged);
20891 }
20892 if let Some(project) = &self.project {
20893 #[allow(clippy::mutable_key_type)]
20894 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20895 multibuffer
20896 .all_buffers()
20897 .into_iter()
20898 .filter_map(|buffer| {
20899 buffer.update(cx, |buffer, cx| {
20900 let language = buffer.language()?;
20901 let should_discard = project.update(cx, |project, cx| {
20902 project.is_local()
20903 && !project.has_language_servers_for(buffer, cx)
20904 });
20905 should_discard.not().then_some(language.clone())
20906 })
20907 })
20908 .collect::<HashSet<_>>()
20909 });
20910 if !languages_affected.is_empty() {
20911 self.refresh_inlay_hints(
20912 InlayHintRefreshReason::BufferEdited(languages_affected),
20913 cx,
20914 );
20915 }
20916 }
20917 }
20918
20919 let Some(project) = &self.project else { return };
20920 let (telemetry, is_via_ssh) = {
20921 let project = project.read(cx);
20922 let telemetry = project.client().telemetry().clone();
20923 let is_via_ssh = project.is_via_remote_server();
20924 (telemetry, is_via_ssh)
20925 };
20926 refresh_linked_ranges(self, window, cx);
20927 telemetry.log_edit_event("editor", is_via_ssh);
20928 }
20929 multi_buffer::Event::ExcerptsAdded {
20930 buffer,
20931 predecessor,
20932 excerpts,
20933 } => {
20934 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20935 let buffer_id = buffer.read(cx).remote_id();
20936 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20937 && let Some(project) = &self.project
20938 {
20939 update_uncommitted_diff_for_buffer(
20940 cx.entity(),
20941 project,
20942 [buffer.clone()],
20943 self.buffer.clone(),
20944 cx,
20945 )
20946 .detach();
20947 }
20948 if self.active_diagnostics != ActiveDiagnostic::All {
20949 self.update_lsp_data(false, Some(buffer_id), window, cx);
20950 }
20951 cx.emit(EditorEvent::ExcerptsAdded {
20952 buffer: buffer.clone(),
20953 predecessor: *predecessor,
20954 excerpts: excerpts.clone(),
20955 });
20956 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20957 }
20958 multi_buffer::Event::ExcerptsRemoved {
20959 ids,
20960 removed_buffer_ids,
20961 } => {
20962 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20963 let buffer = self.buffer.read(cx);
20964 self.registered_buffers
20965 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20966 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20967 cx.emit(EditorEvent::ExcerptsRemoved {
20968 ids: ids.clone(),
20969 removed_buffer_ids: removed_buffer_ids.clone(),
20970 });
20971 }
20972 multi_buffer::Event::ExcerptsEdited {
20973 excerpt_ids,
20974 buffer_ids,
20975 } => {
20976 self.display_map.update(cx, |map, cx| {
20977 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20978 });
20979 cx.emit(EditorEvent::ExcerptsEdited {
20980 ids: excerpt_ids.clone(),
20981 });
20982 }
20983 multi_buffer::Event::ExcerptsExpanded { ids } => {
20984 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20985 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20986 }
20987 multi_buffer::Event::Reparsed(buffer_id) => {
20988 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20989 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20990
20991 cx.emit(EditorEvent::Reparsed(*buffer_id));
20992 }
20993 multi_buffer::Event::DiffHunksToggled => {
20994 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20995 }
20996 multi_buffer::Event::LanguageChanged(buffer_id) => {
20997 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20998 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20999 cx.emit(EditorEvent::Reparsed(*buffer_id));
21000 cx.notify();
21001 }
21002 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21003 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21004 multi_buffer::Event::FileHandleChanged
21005 | multi_buffer::Event::Reloaded
21006 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21007 multi_buffer::Event::DiagnosticsUpdated => {
21008 self.update_diagnostics_state(window, cx);
21009 }
21010 _ => {}
21011 };
21012 }
21013
21014 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21015 if !self.diagnostics_enabled() {
21016 return;
21017 }
21018 self.refresh_active_diagnostics(cx);
21019 self.refresh_inline_diagnostics(true, window, cx);
21020 self.scrollbar_marker_state.dirty = true;
21021 cx.notify();
21022 }
21023
21024 pub fn start_temporary_diff_override(&mut self) {
21025 self.load_diff_task.take();
21026 self.temporary_diff_override = true;
21027 }
21028
21029 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21030 self.temporary_diff_override = false;
21031 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21032 self.buffer.update(cx, |buffer, cx| {
21033 buffer.set_all_diff_hunks_collapsed(cx);
21034 });
21035
21036 if let Some(project) = self.project.clone() {
21037 self.load_diff_task = Some(
21038 update_uncommitted_diff_for_buffer(
21039 cx.entity(),
21040 &project,
21041 self.buffer.read(cx).all_buffers(),
21042 self.buffer.clone(),
21043 cx,
21044 )
21045 .shared(),
21046 );
21047 }
21048 }
21049
21050 fn on_display_map_changed(
21051 &mut self,
21052 _: Entity<DisplayMap>,
21053 _: &mut Window,
21054 cx: &mut Context<Self>,
21055 ) {
21056 cx.notify();
21057 }
21058
21059 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21060 if self.diagnostics_enabled() {
21061 let new_severity = EditorSettings::get_global(cx)
21062 .diagnostics_max_severity
21063 .unwrap_or(DiagnosticSeverity::Hint);
21064 self.set_max_diagnostics_severity(new_severity, cx);
21065 }
21066 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21067 self.update_edit_prediction_settings(cx);
21068 self.refresh_edit_prediction(true, false, window, cx);
21069 self.refresh_inline_values(cx);
21070 self.refresh_inlay_hints(
21071 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21072 self.selections.newest_anchor().head(),
21073 &self.buffer.read(cx).snapshot(cx),
21074 cx,
21075 )),
21076 cx,
21077 );
21078
21079 let old_cursor_shape = self.cursor_shape;
21080 let old_show_breadcrumbs = self.show_breadcrumbs;
21081
21082 {
21083 let editor_settings = EditorSettings::get_global(cx);
21084 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21085 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21086 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21087 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21088 }
21089
21090 if old_cursor_shape != self.cursor_shape {
21091 cx.emit(EditorEvent::CursorShapeChanged);
21092 }
21093
21094 if old_show_breadcrumbs != self.show_breadcrumbs {
21095 cx.emit(EditorEvent::BreadcrumbsChanged);
21096 }
21097
21098 let project_settings = ProjectSettings::get_global(cx);
21099 self.serialize_dirty_buffers =
21100 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
21101
21102 if self.mode.is_full() {
21103 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21104 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21105 if self.show_inline_diagnostics != show_inline_diagnostics {
21106 self.show_inline_diagnostics = show_inline_diagnostics;
21107 self.refresh_inline_diagnostics(false, window, cx);
21108 }
21109
21110 if self.git_blame_inline_enabled != inline_blame_enabled {
21111 self.toggle_git_blame_inline_internal(false, window, cx);
21112 }
21113
21114 let minimap_settings = EditorSettings::get_global(cx).minimap;
21115 if self.minimap_visibility != MinimapVisibility::Disabled {
21116 if self.minimap_visibility.settings_visibility()
21117 != minimap_settings.minimap_enabled()
21118 {
21119 self.set_minimap_visibility(
21120 MinimapVisibility::for_mode(self.mode(), cx),
21121 window,
21122 cx,
21123 );
21124 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21125 minimap_entity.update(cx, |minimap_editor, cx| {
21126 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21127 })
21128 }
21129 }
21130 }
21131
21132 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21133 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21134 }) {
21135 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
21136 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21137 }
21138 self.refresh_colors(false, None, window, cx);
21139 }
21140
21141 cx.notify();
21142 }
21143
21144 pub fn set_searchable(&mut self, searchable: bool) {
21145 self.searchable = searchable;
21146 }
21147
21148 pub fn searchable(&self) -> bool {
21149 self.searchable
21150 }
21151
21152 fn open_proposed_changes_editor(
21153 &mut self,
21154 _: &OpenProposedChangesEditor,
21155 window: &mut Window,
21156 cx: &mut Context<Self>,
21157 ) {
21158 let Some(workspace) = self.workspace() else {
21159 cx.propagate();
21160 return;
21161 };
21162
21163 let selections = self.selections.all::<usize>(cx);
21164 let multi_buffer = self.buffer.read(cx);
21165 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
21166 let mut new_selections_by_buffer = HashMap::default();
21167 for selection in selections {
21168 for (buffer, range, _) in
21169 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
21170 {
21171 let mut range = range.to_point(buffer);
21172 range.start.column = 0;
21173 range.end.column = buffer.line_len(range.end.row);
21174 new_selections_by_buffer
21175 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
21176 .or_insert(Vec::new())
21177 .push(range)
21178 }
21179 }
21180
21181 let proposed_changes_buffers = new_selections_by_buffer
21182 .into_iter()
21183 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
21184 .collect::<Vec<_>>();
21185 let proposed_changes_editor = cx.new(|cx| {
21186 ProposedChangesEditor::new(
21187 "Proposed changes",
21188 proposed_changes_buffers,
21189 self.project.clone(),
21190 window,
21191 cx,
21192 )
21193 });
21194
21195 window.defer(cx, move |window, cx| {
21196 workspace.update(cx, |workspace, cx| {
21197 workspace.active_pane().update(cx, |pane, cx| {
21198 pane.add_item(
21199 Box::new(proposed_changes_editor),
21200 true,
21201 true,
21202 None,
21203 window,
21204 cx,
21205 );
21206 });
21207 });
21208 });
21209 }
21210
21211 pub fn open_excerpts_in_split(
21212 &mut self,
21213 _: &OpenExcerptsSplit,
21214 window: &mut Window,
21215 cx: &mut Context<Self>,
21216 ) {
21217 self.open_excerpts_common(None, true, window, cx)
21218 }
21219
21220 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21221 self.open_excerpts_common(None, false, window, cx)
21222 }
21223
21224 fn open_excerpts_common(
21225 &mut self,
21226 jump_data: Option<JumpData>,
21227 split: bool,
21228 window: &mut Window,
21229 cx: &mut Context<Self>,
21230 ) {
21231 let Some(workspace) = self.workspace() else {
21232 cx.propagate();
21233 return;
21234 };
21235
21236 if self.buffer.read(cx).is_singleton() {
21237 cx.propagate();
21238 return;
21239 }
21240
21241 let mut new_selections_by_buffer = HashMap::default();
21242 match &jump_data {
21243 Some(JumpData::MultiBufferPoint {
21244 excerpt_id,
21245 position,
21246 anchor,
21247 line_offset_from_top,
21248 }) => {
21249 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21250 if let Some(buffer) = multi_buffer_snapshot
21251 .buffer_id_for_excerpt(*excerpt_id)
21252 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21253 {
21254 let buffer_snapshot = buffer.read(cx).snapshot();
21255 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21256 language::ToPoint::to_point(anchor, &buffer_snapshot)
21257 } else {
21258 buffer_snapshot.clip_point(*position, Bias::Left)
21259 };
21260 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21261 new_selections_by_buffer.insert(
21262 buffer,
21263 (
21264 vec![jump_to_offset..jump_to_offset],
21265 Some(*line_offset_from_top),
21266 ),
21267 );
21268 }
21269 }
21270 Some(JumpData::MultiBufferRow {
21271 row,
21272 line_offset_from_top,
21273 }) => {
21274 let point = MultiBufferPoint::new(row.0, 0);
21275 if let Some((buffer, buffer_point, _)) =
21276 self.buffer.read(cx).point_to_buffer_point(point, cx)
21277 {
21278 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21279 new_selections_by_buffer
21280 .entry(buffer)
21281 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21282 .0
21283 .push(buffer_offset..buffer_offset)
21284 }
21285 }
21286 None => {
21287 let selections = self.selections.all::<usize>(cx);
21288 let multi_buffer = self.buffer.read(cx);
21289 for selection in selections {
21290 for (snapshot, range, _, anchor) in multi_buffer
21291 .snapshot(cx)
21292 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21293 {
21294 if let Some(anchor) = anchor {
21295 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21296 else {
21297 continue;
21298 };
21299 let offset = text::ToOffset::to_offset(
21300 &anchor.text_anchor,
21301 &buffer_handle.read(cx).snapshot(),
21302 );
21303 let range = offset..offset;
21304 new_selections_by_buffer
21305 .entry(buffer_handle)
21306 .or_insert((Vec::new(), None))
21307 .0
21308 .push(range)
21309 } else {
21310 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21311 else {
21312 continue;
21313 };
21314 new_selections_by_buffer
21315 .entry(buffer_handle)
21316 .or_insert((Vec::new(), None))
21317 .0
21318 .push(range)
21319 }
21320 }
21321 }
21322 }
21323 }
21324
21325 new_selections_by_buffer
21326 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21327
21328 if new_selections_by_buffer.is_empty() {
21329 return;
21330 }
21331
21332 // We defer the pane interaction because we ourselves are a workspace item
21333 // and activating a new item causes the pane to call a method on us reentrantly,
21334 // which panics if we're on the stack.
21335 window.defer(cx, move |window, cx| {
21336 workspace.update(cx, |workspace, cx| {
21337 let pane = if split {
21338 workspace.adjacent_pane(window, cx)
21339 } else {
21340 workspace.active_pane().clone()
21341 };
21342
21343 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21344 let editor = buffer
21345 .read(cx)
21346 .file()
21347 .is_none()
21348 .then(|| {
21349 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21350 // so `workspace.open_project_item` will never find them, always opening a new editor.
21351 // Instead, we try to activate the existing editor in the pane first.
21352 let (editor, pane_item_index) =
21353 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21354 let editor = item.downcast::<Editor>()?;
21355 let singleton_buffer =
21356 editor.read(cx).buffer().read(cx).as_singleton()?;
21357 if singleton_buffer == buffer {
21358 Some((editor, i))
21359 } else {
21360 None
21361 }
21362 })?;
21363 pane.update(cx, |pane, cx| {
21364 pane.activate_item(pane_item_index, true, true, window, cx)
21365 });
21366 Some(editor)
21367 })
21368 .flatten()
21369 .unwrap_or_else(|| {
21370 workspace.open_project_item::<Self>(
21371 pane.clone(),
21372 buffer,
21373 true,
21374 true,
21375 window,
21376 cx,
21377 )
21378 });
21379
21380 editor.update(cx, |editor, cx| {
21381 let autoscroll = match scroll_offset {
21382 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21383 None => Autoscroll::newest(),
21384 };
21385 let nav_history = editor.nav_history.take();
21386 editor.change_selections(
21387 SelectionEffects::scroll(autoscroll),
21388 window,
21389 cx,
21390 |s| {
21391 s.select_ranges(ranges);
21392 },
21393 );
21394 editor.nav_history = nav_history;
21395 });
21396 }
21397 })
21398 });
21399 }
21400
21401 // For now, don't allow opening excerpts in buffers that aren't backed by
21402 // regular project files.
21403 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21404 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21405 }
21406
21407 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21408 let snapshot = self.buffer.read(cx).read(cx);
21409 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21410 Some(
21411 ranges
21412 .iter()
21413 .map(move |range| {
21414 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21415 })
21416 .collect(),
21417 )
21418 }
21419
21420 fn selection_replacement_ranges(
21421 &self,
21422 range: Range<OffsetUtf16>,
21423 cx: &mut App,
21424 ) -> Vec<Range<OffsetUtf16>> {
21425 let selections = self.selections.all::<OffsetUtf16>(cx);
21426 let newest_selection = selections
21427 .iter()
21428 .max_by_key(|selection| selection.id)
21429 .unwrap();
21430 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21431 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21432 let snapshot = self.buffer.read(cx).read(cx);
21433 selections
21434 .into_iter()
21435 .map(|mut selection| {
21436 selection.start.0 =
21437 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21438 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21439 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21440 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21441 })
21442 .collect()
21443 }
21444
21445 fn report_editor_event(
21446 &self,
21447 reported_event: ReportEditorEvent,
21448 file_extension: Option<String>,
21449 cx: &App,
21450 ) {
21451 if cfg!(any(test, feature = "test-support")) {
21452 return;
21453 }
21454
21455 let Some(project) = &self.project else { return };
21456
21457 // If None, we are in a file without an extension
21458 let file = self
21459 .buffer
21460 .read(cx)
21461 .as_singleton()
21462 .and_then(|b| b.read(cx).file());
21463 let file_extension = file_extension.or(file
21464 .as_ref()
21465 .and_then(|file| Path::new(file.file_name(cx)).extension())
21466 .and_then(|e| e.to_str())
21467 .map(|a| a.to_string()));
21468
21469 let vim_mode = vim_enabled(cx);
21470
21471 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21472 let copilot_enabled = edit_predictions_provider
21473 == language::language_settings::EditPredictionProvider::Copilot;
21474 let copilot_enabled_for_language = self
21475 .buffer
21476 .read(cx)
21477 .language_settings(cx)
21478 .show_edit_predictions;
21479
21480 let project = project.read(cx);
21481 let event_type = reported_event.event_type();
21482
21483 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21484 telemetry::event!(
21485 event_type,
21486 type = if auto_saved {"autosave"} else {"manual"},
21487 file_extension,
21488 vim_mode,
21489 copilot_enabled,
21490 copilot_enabled_for_language,
21491 edit_predictions_provider,
21492 is_via_ssh = project.is_via_remote_server(),
21493 );
21494 } else {
21495 telemetry::event!(
21496 event_type,
21497 file_extension,
21498 vim_mode,
21499 copilot_enabled,
21500 copilot_enabled_for_language,
21501 edit_predictions_provider,
21502 is_via_ssh = project.is_via_remote_server(),
21503 );
21504 };
21505 }
21506
21507 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21508 /// with each line being an array of {text, highlight} objects.
21509 fn copy_highlight_json(
21510 &mut self,
21511 _: &CopyHighlightJson,
21512 window: &mut Window,
21513 cx: &mut Context<Self>,
21514 ) {
21515 #[derive(Serialize)]
21516 struct Chunk<'a> {
21517 text: String,
21518 highlight: Option<&'a str>,
21519 }
21520
21521 let snapshot = self.buffer.read(cx).snapshot(cx);
21522 let range = self
21523 .selected_text_range(false, window, cx)
21524 .and_then(|selection| {
21525 if selection.range.is_empty() {
21526 None
21527 } else {
21528 Some(selection.range)
21529 }
21530 })
21531 .unwrap_or_else(|| 0..snapshot.len());
21532
21533 let chunks = snapshot.chunks(range, true);
21534 let mut lines = Vec::new();
21535 let mut line: VecDeque<Chunk> = VecDeque::new();
21536
21537 let Some(style) = self.style.as_ref() else {
21538 return;
21539 };
21540
21541 for chunk in chunks {
21542 let highlight = chunk
21543 .syntax_highlight_id
21544 .and_then(|id| id.name(&style.syntax));
21545 let mut chunk_lines = chunk.text.split('\n').peekable();
21546 while let Some(text) = chunk_lines.next() {
21547 let mut merged_with_last_token = false;
21548 if let Some(last_token) = line.back_mut()
21549 && last_token.highlight == highlight
21550 {
21551 last_token.text.push_str(text);
21552 merged_with_last_token = true;
21553 }
21554
21555 if !merged_with_last_token {
21556 line.push_back(Chunk {
21557 text: text.into(),
21558 highlight,
21559 });
21560 }
21561
21562 if chunk_lines.peek().is_some() {
21563 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21564 line.pop_front();
21565 }
21566 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21567 line.pop_back();
21568 }
21569
21570 lines.push(mem::take(&mut line));
21571 }
21572 }
21573 }
21574
21575 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21576 return;
21577 };
21578 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21579 }
21580
21581 pub fn open_context_menu(
21582 &mut self,
21583 _: &OpenContextMenu,
21584 window: &mut Window,
21585 cx: &mut Context<Self>,
21586 ) {
21587 self.request_autoscroll(Autoscroll::newest(), cx);
21588 let position = self.selections.newest_display(cx).start;
21589 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21590 }
21591
21592 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
21593 &self.inlay_hint_cache
21594 }
21595
21596 pub fn replay_insert_event(
21597 &mut self,
21598 text: &str,
21599 relative_utf16_range: Option<Range<isize>>,
21600 window: &mut Window,
21601 cx: &mut Context<Self>,
21602 ) {
21603 if !self.input_enabled {
21604 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21605 return;
21606 }
21607 if let Some(relative_utf16_range) = relative_utf16_range {
21608 let selections = self.selections.all::<OffsetUtf16>(cx);
21609 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21610 let new_ranges = selections.into_iter().map(|range| {
21611 let start = OffsetUtf16(
21612 range
21613 .head()
21614 .0
21615 .saturating_add_signed(relative_utf16_range.start),
21616 );
21617 let end = OffsetUtf16(
21618 range
21619 .head()
21620 .0
21621 .saturating_add_signed(relative_utf16_range.end),
21622 );
21623 start..end
21624 });
21625 s.select_ranges(new_ranges);
21626 });
21627 }
21628
21629 self.handle_input(text, window, cx);
21630 }
21631
21632 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
21633 let Some(provider) = self.semantics_provider.as_ref() else {
21634 return false;
21635 };
21636
21637 let mut supports = false;
21638 self.buffer().update(cx, |this, cx| {
21639 this.for_each_buffer(|buffer| {
21640 supports |= provider.supports_inlay_hints(buffer, cx);
21641 });
21642 });
21643
21644 supports
21645 }
21646
21647 pub fn is_focused(&self, window: &Window) -> bool {
21648 self.focus_handle.is_focused(window)
21649 }
21650
21651 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21652 cx.emit(EditorEvent::Focused);
21653
21654 if let Some(descendant) = self
21655 .last_focused_descendant
21656 .take()
21657 .and_then(|descendant| descendant.upgrade())
21658 {
21659 window.focus(&descendant);
21660 } else {
21661 if let Some(blame) = self.blame.as_ref() {
21662 blame.update(cx, GitBlame::focus)
21663 }
21664
21665 self.blink_manager.update(cx, BlinkManager::enable);
21666 self.show_cursor_names(window, cx);
21667 self.buffer.update(cx, |buffer, cx| {
21668 buffer.finalize_last_transaction(cx);
21669 if self.leader_id.is_none() {
21670 buffer.set_active_selections(
21671 &self.selections.disjoint_anchors_arc(),
21672 self.selections.line_mode(),
21673 self.cursor_shape,
21674 cx,
21675 );
21676 }
21677 });
21678 }
21679 }
21680
21681 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21682 cx.emit(EditorEvent::FocusedIn)
21683 }
21684
21685 fn handle_focus_out(
21686 &mut self,
21687 event: FocusOutEvent,
21688 _window: &mut Window,
21689 cx: &mut Context<Self>,
21690 ) {
21691 if event.blurred != self.focus_handle {
21692 self.last_focused_descendant = Some(event.blurred);
21693 }
21694 self.selection_drag_state = SelectionDragState::None;
21695 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21696 }
21697
21698 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21699 self.blink_manager.update(cx, BlinkManager::disable);
21700 self.buffer
21701 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21702
21703 if let Some(blame) = self.blame.as_ref() {
21704 blame.update(cx, GitBlame::blur)
21705 }
21706 if !self.hover_state.focused(window, cx) {
21707 hide_hover(self, cx);
21708 }
21709 if !self
21710 .context_menu
21711 .borrow()
21712 .as_ref()
21713 .is_some_and(|context_menu| context_menu.focused(window, cx))
21714 {
21715 self.hide_context_menu(window, cx);
21716 }
21717 self.take_active_edit_prediction(cx);
21718 cx.emit(EditorEvent::Blurred);
21719 cx.notify();
21720 }
21721
21722 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21723 let mut pending: String = window
21724 .pending_input_keystrokes()
21725 .into_iter()
21726 .flatten()
21727 .filter_map(|keystroke| {
21728 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21729 keystroke.key_char.clone()
21730 } else {
21731 None
21732 }
21733 })
21734 .collect();
21735
21736 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21737 pending = "".to_string();
21738 }
21739
21740 let existing_pending = self
21741 .text_highlights::<PendingInput>(cx)
21742 .map(|(_, ranges)| ranges.to_vec());
21743 if existing_pending.is_none() && pending.is_empty() {
21744 return;
21745 }
21746 let transaction =
21747 self.transact(window, cx, |this, window, cx| {
21748 let selections = this.selections.all::<usize>(cx);
21749 let edits = selections
21750 .iter()
21751 .map(|selection| (selection.end..selection.end, pending.clone()));
21752 this.edit(edits, cx);
21753 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21754 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21755 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21756 }));
21757 });
21758 if let Some(existing_ranges) = existing_pending {
21759 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21760 this.edit(edits, cx);
21761 }
21762 });
21763
21764 let snapshot = self.snapshot(window, cx);
21765 let ranges = self
21766 .selections
21767 .all::<usize>(cx)
21768 .into_iter()
21769 .map(|selection| {
21770 snapshot.buffer_snapshot().anchor_after(selection.end)
21771 ..snapshot
21772 .buffer_snapshot()
21773 .anchor_before(selection.end + pending.len())
21774 })
21775 .collect();
21776
21777 if pending.is_empty() {
21778 self.clear_highlights::<PendingInput>(cx);
21779 } else {
21780 self.highlight_text::<PendingInput>(
21781 ranges,
21782 HighlightStyle {
21783 underline: Some(UnderlineStyle {
21784 thickness: px(1.),
21785 color: None,
21786 wavy: false,
21787 }),
21788 ..Default::default()
21789 },
21790 cx,
21791 );
21792 }
21793
21794 self.ime_transaction = self.ime_transaction.or(transaction);
21795 if let Some(transaction) = self.ime_transaction {
21796 self.buffer.update(cx, |buffer, cx| {
21797 buffer.group_until_transaction(transaction, cx);
21798 });
21799 }
21800
21801 if self.text_highlights::<PendingInput>(cx).is_none() {
21802 self.ime_transaction.take();
21803 }
21804 }
21805
21806 pub fn register_action_renderer(
21807 &mut self,
21808 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21809 ) -> Subscription {
21810 let id = self.next_editor_action_id.post_inc();
21811 self.editor_actions
21812 .borrow_mut()
21813 .insert(id, Box::new(listener));
21814
21815 let editor_actions = self.editor_actions.clone();
21816 Subscription::new(move || {
21817 editor_actions.borrow_mut().remove(&id);
21818 })
21819 }
21820
21821 pub fn register_action<A: Action>(
21822 &mut self,
21823 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21824 ) -> Subscription {
21825 let id = self.next_editor_action_id.post_inc();
21826 let listener = Arc::new(listener);
21827 self.editor_actions.borrow_mut().insert(
21828 id,
21829 Box::new(move |_, window, _| {
21830 let listener = listener.clone();
21831 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21832 let action = action.downcast_ref().unwrap();
21833 if phase == DispatchPhase::Bubble {
21834 listener(action, window, cx)
21835 }
21836 })
21837 }),
21838 );
21839
21840 let editor_actions = self.editor_actions.clone();
21841 Subscription::new(move || {
21842 editor_actions.borrow_mut().remove(&id);
21843 })
21844 }
21845
21846 pub fn file_header_size(&self) -> u32 {
21847 FILE_HEADER_HEIGHT
21848 }
21849
21850 pub fn restore(
21851 &mut self,
21852 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21853 window: &mut Window,
21854 cx: &mut Context<Self>,
21855 ) {
21856 let workspace = self.workspace();
21857 let project = self.project();
21858 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21859 let mut tasks = Vec::new();
21860 for (buffer_id, changes) in revert_changes {
21861 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21862 buffer.update(cx, |buffer, cx| {
21863 buffer.edit(
21864 changes
21865 .into_iter()
21866 .map(|(range, text)| (range, text.to_string())),
21867 None,
21868 cx,
21869 );
21870 });
21871
21872 if let Some(project) =
21873 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21874 {
21875 project.update(cx, |project, cx| {
21876 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21877 })
21878 }
21879 }
21880 }
21881 tasks
21882 });
21883 cx.spawn_in(window, async move |_, cx| {
21884 for (buffer, task) in save_tasks {
21885 let result = task.await;
21886 if result.is_err() {
21887 let Some(path) = buffer
21888 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21889 .ok()
21890 else {
21891 continue;
21892 };
21893 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21894 let Some(task) = cx
21895 .update_window_entity(workspace, |workspace, window, cx| {
21896 workspace
21897 .open_path_preview(path, None, false, false, false, window, cx)
21898 })
21899 .ok()
21900 else {
21901 continue;
21902 };
21903 task.await.log_err();
21904 }
21905 }
21906 }
21907 })
21908 .detach();
21909 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21910 selections.refresh()
21911 });
21912 }
21913
21914 pub fn to_pixel_point(
21915 &self,
21916 source: multi_buffer::Anchor,
21917 editor_snapshot: &EditorSnapshot,
21918 window: &mut Window,
21919 ) -> Option<gpui::Point<Pixels>> {
21920 let source_point = source.to_display_point(editor_snapshot);
21921 self.display_to_pixel_point(source_point, editor_snapshot, window)
21922 }
21923
21924 pub fn display_to_pixel_point(
21925 &self,
21926 source: DisplayPoint,
21927 editor_snapshot: &EditorSnapshot,
21928 window: &mut Window,
21929 ) -> Option<gpui::Point<Pixels>> {
21930 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21931 let text_layout_details = self.text_layout_details(window);
21932 let scroll_top = text_layout_details
21933 .scroll_anchor
21934 .scroll_position(editor_snapshot)
21935 .y;
21936
21937 if source.row().as_f64() < scroll_top.floor() {
21938 return None;
21939 }
21940 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21941 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
21942 Some(gpui::Point::new(source_x, source_y))
21943 }
21944
21945 pub fn has_visible_completions_menu(&self) -> bool {
21946 !self.edit_prediction_preview_is_active()
21947 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21948 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21949 })
21950 }
21951
21952 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21953 if self.mode.is_minimap() {
21954 return;
21955 }
21956 self.addons
21957 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21958 }
21959
21960 pub fn unregister_addon<T: Addon>(&mut self) {
21961 self.addons.remove(&std::any::TypeId::of::<T>());
21962 }
21963
21964 pub fn addon<T: Addon>(&self) -> Option<&T> {
21965 let type_id = std::any::TypeId::of::<T>();
21966 self.addons
21967 .get(&type_id)
21968 .and_then(|item| item.to_any().downcast_ref::<T>())
21969 }
21970
21971 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21972 let type_id = std::any::TypeId::of::<T>();
21973 self.addons
21974 .get_mut(&type_id)
21975 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21976 }
21977
21978 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21979 let text_layout_details = self.text_layout_details(window);
21980 let style = &text_layout_details.editor_style;
21981 let font_id = window.text_system().resolve_font(&style.text.font());
21982 let font_size = style.text.font_size.to_pixels(window.rem_size());
21983 let line_height = style.text.line_height_in_pixels(window.rem_size());
21984 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21985 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21986
21987 CharacterDimensions {
21988 em_width,
21989 em_advance,
21990 line_height,
21991 }
21992 }
21993
21994 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21995 self.load_diff_task.clone()
21996 }
21997
21998 fn read_metadata_from_db(
21999 &mut self,
22000 item_id: u64,
22001 workspace_id: WorkspaceId,
22002 window: &mut Window,
22003 cx: &mut Context<Editor>,
22004 ) {
22005 if self.buffer_kind(cx) == ItemBufferKind::Singleton
22006 && !self.mode.is_minimap()
22007 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
22008 {
22009 let buffer_snapshot = OnceCell::new();
22010
22011 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
22012 && !folds.is_empty()
22013 {
22014 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22015 self.fold_ranges(
22016 folds
22017 .into_iter()
22018 .map(|(start, end)| {
22019 snapshot.clip_offset(start, Bias::Left)
22020 ..snapshot.clip_offset(end, Bias::Right)
22021 })
22022 .collect(),
22023 false,
22024 window,
22025 cx,
22026 );
22027 }
22028
22029 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22030 && !selections.is_empty()
22031 {
22032 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22033 // skip adding the initial selection to selection history
22034 self.selection_history.mode = SelectionHistoryMode::Skipping;
22035 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22036 s.select_ranges(selections.into_iter().map(|(start, end)| {
22037 snapshot.clip_offset(start, Bias::Left)
22038 ..snapshot.clip_offset(end, Bias::Right)
22039 }));
22040 });
22041 self.selection_history.mode = SelectionHistoryMode::Normal;
22042 };
22043 }
22044
22045 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22046 }
22047
22048 fn update_lsp_data(
22049 &mut self,
22050 ignore_cache: bool,
22051 for_buffer: Option<BufferId>,
22052 window: &mut Window,
22053 cx: &mut Context<'_, Self>,
22054 ) {
22055 self.pull_diagnostics(for_buffer, window, cx);
22056 self.refresh_colors(ignore_cache, for_buffer, window, cx);
22057 }
22058}
22059
22060fn edit_for_markdown_paste<'a>(
22061 buffer: &MultiBufferSnapshot,
22062 range: Range<usize>,
22063 to_insert: &'a str,
22064 url: Option<url::Url>,
22065) -> (Range<usize>, Cow<'a, str>) {
22066 if url.is_none() {
22067 return (range, Cow::Borrowed(to_insert));
22068 };
22069
22070 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22071
22072 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22073 Cow::Borrowed(to_insert)
22074 } else {
22075 Cow::Owned(format!("[{old_text}]({to_insert})"))
22076 };
22077 (range, new_text)
22078}
22079
22080fn vim_enabled(cx: &App) -> bool {
22081 vim_mode_setting::VimModeSetting::try_get(cx)
22082 .map(|vim_mode| vim_mode.0)
22083 .unwrap_or(false)
22084}
22085
22086fn process_completion_for_edit(
22087 completion: &Completion,
22088 intent: CompletionIntent,
22089 buffer: &Entity<Buffer>,
22090 cursor_position: &text::Anchor,
22091 cx: &mut Context<Editor>,
22092) -> CompletionEdit {
22093 let buffer = buffer.read(cx);
22094 let buffer_snapshot = buffer.snapshot();
22095 let (snippet, new_text) = if completion.is_snippet() {
22096 let mut snippet_source = completion.new_text.clone();
22097 // Workaround for typescript language server issues so that methods don't expand within
22098 // strings and functions with type expressions. The previous point is used because the query
22099 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22100 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22101 let previous_point = if previous_point.column > 0 {
22102 cursor_position.to_previous_offset(&buffer_snapshot)
22103 } else {
22104 cursor_position.to_offset(&buffer_snapshot)
22105 };
22106 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22107 && scope.prefers_label_for_snippet_in_completion()
22108 && let Some(label) = completion.label()
22109 && matches!(
22110 completion.kind(),
22111 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22112 )
22113 {
22114 snippet_source = label;
22115 }
22116 match Snippet::parse(&snippet_source).log_err() {
22117 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22118 None => (None, completion.new_text.clone()),
22119 }
22120 } else {
22121 (None, completion.new_text.clone())
22122 };
22123
22124 let mut range_to_replace = {
22125 let replace_range = &completion.replace_range;
22126 if let CompletionSource::Lsp {
22127 insert_range: Some(insert_range),
22128 ..
22129 } = &completion.source
22130 {
22131 debug_assert_eq!(
22132 insert_range.start, replace_range.start,
22133 "insert_range and replace_range should start at the same position"
22134 );
22135 debug_assert!(
22136 insert_range
22137 .start
22138 .cmp(cursor_position, &buffer_snapshot)
22139 .is_le(),
22140 "insert_range should start before or at cursor position"
22141 );
22142 debug_assert!(
22143 replace_range
22144 .start
22145 .cmp(cursor_position, &buffer_snapshot)
22146 .is_le(),
22147 "replace_range should start before or at cursor position"
22148 );
22149
22150 let should_replace = match intent {
22151 CompletionIntent::CompleteWithInsert => false,
22152 CompletionIntent::CompleteWithReplace => true,
22153 CompletionIntent::Complete | CompletionIntent::Compose => {
22154 let insert_mode =
22155 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22156 .completions
22157 .lsp_insert_mode;
22158 match insert_mode {
22159 LspInsertMode::Insert => false,
22160 LspInsertMode::Replace => true,
22161 LspInsertMode::ReplaceSubsequence => {
22162 let mut text_to_replace = buffer.chars_for_range(
22163 buffer.anchor_before(replace_range.start)
22164 ..buffer.anchor_after(replace_range.end),
22165 );
22166 let mut current_needle = text_to_replace.next();
22167 for haystack_ch in completion.label.text.chars() {
22168 if let Some(needle_ch) = current_needle
22169 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22170 {
22171 current_needle = text_to_replace.next();
22172 }
22173 }
22174 current_needle.is_none()
22175 }
22176 LspInsertMode::ReplaceSuffix => {
22177 if replace_range
22178 .end
22179 .cmp(cursor_position, &buffer_snapshot)
22180 .is_gt()
22181 {
22182 let range_after_cursor = *cursor_position..replace_range.end;
22183 let text_after_cursor = buffer
22184 .text_for_range(
22185 buffer.anchor_before(range_after_cursor.start)
22186 ..buffer.anchor_after(range_after_cursor.end),
22187 )
22188 .collect::<String>()
22189 .to_ascii_lowercase();
22190 completion
22191 .label
22192 .text
22193 .to_ascii_lowercase()
22194 .ends_with(&text_after_cursor)
22195 } else {
22196 true
22197 }
22198 }
22199 }
22200 }
22201 };
22202
22203 if should_replace {
22204 replace_range.clone()
22205 } else {
22206 insert_range.clone()
22207 }
22208 } else {
22209 replace_range.clone()
22210 }
22211 };
22212
22213 if range_to_replace
22214 .end
22215 .cmp(cursor_position, &buffer_snapshot)
22216 .is_lt()
22217 {
22218 range_to_replace.end = *cursor_position;
22219 }
22220
22221 CompletionEdit {
22222 new_text,
22223 replace_range: range_to_replace.to_offset(buffer),
22224 snippet,
22225 }
22226}
22227
22228struct CompletionEdit {
22229 new_text: String,
22230 replace_range: Range<usize>,
22231 snippet: Option<Snippet>,
22232}
22233
22234fn insert_extra_newline_brackets(
22235 buffer: &MultiBufferSnapshot,
22236 range: Range<usize>,
22237 language: &language::LanguageScope,
22238) -> bool {
22239 let leading_whitespace_len = buffer
22240 .reversed_chars_at(range.start)
22241 .take_while(|c| c.is_whitespace() && *c != '\n')
22242 .map(|c| c.len_utf8())
22243 .sum::<usize>();
22244 let trailing_whitespace_len = buffer
22245 .chars_at(range.end)
22246 .take_while(|c| c.is_whitespace() && *c != '\n')
22247 .map(|c| c.len_utf8())
22248 .sum::<usize>();
22249 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22250
22251 language.brackets().any(|(pair, enabled)| {
22252 let pair_start = pair.start.trim_end();
22253 let pair_end = pair.end.trim_start();
22254
22255 enabled
22256 && pair.newline
22257 && buffer.contains_str_at(range.end, pair_end)
22258 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22259 })
22260}
22261
22262fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22263 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22264 [(buffer, range, _)] => (*buffer, range.clone()),
22265 _ => return false,
22266 };
22267 let pair = {
22268 let mut result: Option<BracketMatch> = None;
22269
22270 for pair in buffer
22271 .all_bracket_ranges(range.clone())
22272 .filter(move |pair| {
22273 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22274 })
22275 {
22276 let len = pair.close_range.end - pair.open_range.start;
22277
22278 if let Some(existing) = &result {
22279 let existing_len = existing.close_range.end - existing.open_range.start;
22280 if len > existing_len {
22281 continue;
22282 }
22283 }
22284
22285 result = Some(pair);
22286 }
22287
22288 result
22289 };
22290 let Some(pair) = pair else {
22291 return false;
22292 };
22293 pair.newline_only
22294 && buffer
22295 .chars_for_range(pair.open_range.end..range.start)
22296 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22297 .all(|c| c.is_whitespace() && c != '\n')
22298}
22299
22300fn update_uncommitted_diff_for_buffer(
22301 editor: Entity<Editor>,
22302 project: &Entity<Project>,
22303 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22304 buffer: Entity<MultiBuffer>,
22305 cx: &mut App,
22306) -> Task<()> {
22307 let mut tasks = Vec::new();
22308 project.update(cx, |project, cx| {
22309 for buffer in buffers {
22310 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22311 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22312 }
22313 }
22314 });
22315 cx.spawn(async move |cx| {
22316 let diffs = future::join_all(tasks).await;
22317 if editor
22318 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22319 .unwrap_or(false)
22320 {
22321 return;
22322 }
22323
22324 buffer
22325 .update(cx, |buffer, cx| {
22326 for diff in diffs.into_iter().flatten() {
22327 buffer.add_diff(diff, cx);
22328 }
22329 })
22330 .ok();
22331 })
22332}
22333
22334fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22335 let tab_size = tab_size.get() as usize;
22336 let mut width = offset;
22337
22338 for ch in text.chars() {
22339 width += if ch == '\t' {
22340 tab_size - (width % tab_size)
22341 } else {
22342 1
22343 };
22344 }
22345
22346 width - offset
22347}
22348
22349#[cfg(test)]
22350mod tests {
22351 use super::*;
22352
22353 #[test]
22354 fn test_string_size_with_expanded_tabs() {
22355 let nz = |val| NonZeroU32::new(val).unwrap();
22356 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22357 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22358 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22359 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22360 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22361 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22362 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22363 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22364 }
22365}
22366
22367/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22368struct WordBreakingTokenizer<'a> {
22369 input: &'a str,
22370}
22371
22372impl<'a> WordBreakingTokenizer<'a> {
22373 fn new(input: &'a str) -> Self {
22374 Self { input }
22375 }
22376}
22377
22378fn is_char_ideographic(ch: char) -> bool {
22379 use unicode_script::Script::*;
22380 use unicode_script::UnicodeScript;
22381 matches!(ch.script(), Han | Tangut | Yi)
22382}
22383
22384fn is_grapheme_ideographic(text: &str) -> bool {
22385 text.chars().any(is_char_ideographic)
22386}
22387
22388fn is_grapheme_whitespace(text: &str) -> bool {
22389 text.chars().any(|x| x.is_whitespace())
22390}
22391
22392fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22393 text.chars()
22394 .next()
22395 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22396}
22397
22398#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22399enum WordBreakToken<'a> {
22400 Word { token: &'a str, grapheme_len: usize },
22401 InlineWhitespace { token: &'a str, grapheme_len: usize },
22402 Newline,
22403}
22404
22405impl<'a> Iterator for WordBreakingTokenizer<'a> {
22406 /// Yields a span, the count of graphemes in the token, and whether it was
22407 /// whitespace. Note that it also breaks at word boundaries.
22408 type Item = WordBreakToken<'a>;
22409
22410 fn next(&mut self) -> Option<Self::Item> {
22411 use unicode_segmentation::UnicodeSegmentation;
22412 if self.input.is_empty() {
22413 return None;
22414 }
22415
22416 let mut iter = self.input.graphemes(true).peekable();
22417 let mut offset = 0;
22418 let mut grapheme_len = 0;
22419 if let Some(first_grapheme) = iter.next() {
22420 let is_newline = first_grapheme == "\n";
22421 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22422 offset += first_grapheme.len();
22423 grapheme_len += 1;
22424 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22425 if let Some(grapheme) = iter.peek().copied()
22426 && should_stay_with_preceding_ideograph(grapheme)
22427 {
22428 offset += grapheme.len();
22429 grapheme_len += 1;
22430 }
22431 } else {
22432 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22433 let mut next_word_bound = words.peek().copied();
22434 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22435 next_word_bound = words.next();
22436 }
22437 while let Some(grapheme) = iter.peek().copied() {
22438 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22439 break;
22440 };
22441 if is_grapheme_whitespace(grapheme) != is_whitespace
22442 || (grapheme == "\n") != is_newline
22443 {
22444 break;
22445 };
22446 offset += grapheme.len();
22447 grapheme_len += 1;
22448 iter.next();
22449 }
22450 }
22451 let token = &self.input[..offset];
22452 self.input = &self.input[offset..];
22453 if token == "\n" {
22454 Some(WordBreakToken::Newline)
22455 } else if is_whitespace {
22456 Some(WordBreakToken::InlineWhitespace {
22457 token,
22458 grapheme_len,
22459 })
22460 } else {
22461 Some(WordBreakToken::Word {
22462 token,
22463 grapheme_len,
22464 })
22465 }
22466 } else {
22467 None
22468 }
22469 }
22470}
22471
22472#[test]
22473fn test_word_breaking_tokenizer() {
22474 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22475 ("", &[]),
22476 (" ", &[whitespace(" ", 2)]),
22477 ("Ʒ", &[word("Ʒ", 1)]),
22478 ("Ǽ", &[word("Ǽ", 1)]),
22479 ("⋑", &[word("⋑", 1)]),
22480 ("⋑⋑", &[word("⋑⋑", 2)]),
22481 (
22482 "原理,进而",
22483 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22484 ),
22485 (
22486 "hello world",
22487 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22488 ),
22489 (
22490 "hello, world",
22491 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22492 ),
22493 (
22494 " hello world",
22495 &[
22496 whitespace(" ", 2),
22497 word("hello", 5),
22498 whitespace(" ", 1),
22499 word("world", 5),
22500 ],
22501 ),
22502 (
22503 "这是什么 \n 钢笔",
22504 &[
22505 word("这", 1),
22506 word("是", 1),
22507 word("什", 1),
22508 word("么", 1),
22509 whitespace(" ", 1),
22510 newline(),
22511 whitespace(" ", 1),
22512 word("钢", 1),
22513 word("笔", 1),
22514 ],
22515 ),
22516 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22517 ];
22518
22519 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22520 WordBreakToken::Word {
22521 token,
22522 grapheme_len,
22523 }
22524 }
22525
22526 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22527 WordBreakToken::InlineWhitespace {
22528 token,
22529 grapheme_len,
22530 }
22531 }
22532
22533 fn newline() -> WordBreakToken<'static> {
22534 WordBreakToken::Newline
22535 }
22536
22537 for (input, result) in tests {
22538 assert_eq!(
22539 WordBreakingTokenizer::new(input)
22540 .collect::<Vec<_>>()
22541 .as_slice(),
22542 *result,
22543 );
22544 }
22545}
22546
22547fn wrap_with_prefix(
22548 first_line_prefix: String,
22549 subsequent_lines_prefix: String,
22550 unwrapped_text: String,
22551 wrap_column: usize,
22552 tab_size: NonZeroU32,
22553 preserve_existing_whitespace: bool,
22554) -> String {
22555 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22556 let subsequent_lines_prefix_len =
22557 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22558 let mut wrapped_text = String::new();
22559 let mut current_line = first_line_prefix;
22560 let mut is_first_line = true;
22561
22562 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22563 let mut current_line_len = first_line_prefix_len;
22564 let mut in_whitespace = false;
22565 for token in tokenizer {
22566 let have_preceding_whitespace = in_whitespace;
22567 match token {
22568 WordBreakToken::Word {
22569 token,
22570 grapheme_len,
22571 } => {
22572 in_whitespace = false;
22573 let current_prefix_len = if is_first_line {
22574 first_line_prefix_len
22575 } else {
22576 subsequent_lines_prefix_len
22577 };
22578 if current_line_len + grapheme_len > wrap_column
22579 && current_line_len != current_prefix_len
22580 {
22581 wrapped_text.push_str(current_line.trim_end());
22582 wrapped_text.push('\n');
22583 is_first_line = false;
22584 current_line = subsequent_lines_prefix.clone();
22585 current_line_len = subsequent_lines_prefix_len;
22586 }
22587 current_line.push_str(token);
22588 current_line_len += grapheme_len;
22589 }
22590 WordBreakToken::InlineWhitespace {
22591 mut token,
22592 mut grapheme_len,
22593 } => {
22594 in_whitespace = true;
22595 if have_preceding_whitespace && !preserve_existing_whitespace {
22596 continue;
22597 }
22598 if !preserve_existing_whitespace {
22599 // Keep a single whitespace grapheme as-is
22600 if let Some(first) =
22601 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22602 {
22603 token = first;
22604 } else {
22605 token = " ";
22606 }
22607 grapheme_len = 1;
22608 }
22609 let current_prefix_len = if is_first_line {
22610 first_line_prefix_len
22611 } else {
22612 subsequent_lines_prefix_len
22613 };
22614 if current_line_len + grapheme_len > wrap_column {
22615 wrapped_text.push_str(current_line.trim_end());
22616 wrapped_text.push('\n');
22617 is_first_line = false;
22618 current_line = subsequent_lines_prefix.clone();
22619 current_line_len = subsequent_lines_prefix_len;
22620 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22621 current_line.push_str(token);
22622 current_line_len += grapheme_len;
22623 }
22624 }
22625 WordBreakToken::Newline => {
22626 in_whitespace = true;
22627 let current_prefix_len = if is_first_line {
22628 first_line_prefix_len
22629 } else {
22630 subsequent_lines_prefix_len
22631 };
22632 if preserve_existing_whitespace {
22633 wrapped_text.push_str(current_line.trim_end());
22634 wrapped_text.push('\n');
22635 is_first_line = false;
22636 current_line = subsequent_lines_prefix.clone();
22637 current_line_len = subsequent_lines_prefix_len;
22638 } else if have_preceding_whitespace {
22639 continue;
22640 } else if current_line_len + 1 > wrap_column
22641 && current_line_len != current_prefix_len
22642 {
22643 wrapped_text.push_str(current_line.trim_end());
22644 wrapped_text.push('\n');
22645 is_first_line = false;
22646 current_line = subsequent_lines_prefix.clone();
22647 current_line_len = subsequent_lines_prefix_len;
22648 } else if current_line_len != current_prefix_len {
22649 current_line.push(' ');
22650 current_line_len += 1;
22651 }
22652 }
22653 }
22654 }
22655
22656 if !current_line.is_empty() {
22657 wrapped_text.push_str(¤t_line);
22658 }
22659 wrapped_text
22660}
22661
22662#[test]
22663fn test_wrap_with_prefix() {
22664 assert_eq!(
22665 wrap_with_prefix(
22666 "# ".to_string(),
22667 "# ".to_string(),
22668 "abcdefg".to_string(),
22669 4,
22670 NonZeroU32::new(4).unwrap(),
22671 false,
22672 ),
22673 "# abcdefg"
22674 );
22675 assert_eq!(
22676 wrap_with_prefix(
22677 "".to_string(),
22678 "".to_string(),
22679 "\thello world".to_string(),
22680 8,
22681 NonZeroU32::new(4).unwrap(),
22682 false,
22683 ),
22684 "hello\nworld"
22685 );
22686 assert_eq!(
22687 wrap_with_prefix(
22688 "// ".to_string(),
22689 "// ".to_string(),
22690 "xx \nyy zz aa bb cc".to_string(),
22691 12,
22692 NonZeroU32::new(4).unwrap(),
22693 false,
22694 ),
22695 "// xx yy zz\n// aa bb cc"
22696 );
22697 assert_eq!(
22698 wrap_with_prefix(
22699 String::new(),
22700 String::new(),
22701 "这是什么 \n 钢笔".to_string(),
22702 3,
22703 NonZeroU32::new(4).unwrap(),
22704 false,
22705 ),
22706 "这是什\n么 钢\n笔"
22707 );
22708 assert_eq!(
22709 wrap_with_prefix(
22710 String::new(),
22711 String::new(),
22712 format!("foo{}bar", '\u{2009}'), // thin space
22713 80,
22714 NonZeroU32::new(4).unwrap(),
22715 false,
22716 ),
22717 format!("foo{}bar", '\u{2009}')
22718 );
22719}
22720
22721pub trait CollaborationHub {
22722 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22723 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22724 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22725}
22726
22727impl CollaborationHub for Entity<Project> {
22728 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22729 self.read(cx).collaborators()
22730 }
22731
22732 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22733 self.read(cx).user_store().read(cx).participant_indices()
22734 }
22735
22736 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22737 let this = self.read(cx);
22738 let user_ids = this.collaborators().values().map(|c| c.user_id);
22739 this.user_store().read(cx).participant_names(user_ids, cx)
22740 }
22741}
22742
22743pub trait SemanticsProvider {
22744 fn hover(
22745 &self,
22746 buffer: &Entity<Buffer>,
22747 position: text::Anchor,
22748 cx: &mut App,
22749 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22750
22751 fn inline_values(
22752 &self,
22753 buffer_handle: Entity<Buffer>,
22754 range: Range<text::Anchor>,
22755 cx: &mut App,
22756 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22757
22758 fn inlay_hints(
22759 &self,
22760 buffer_handle: Entity<Buffer>,
22761 range: Range<text::Anchor>,
22762 cx: &mut App,
22763 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22764
22765 fn resolve_inlay_hint(
22766 &self,
22767 hint: InlayHint,
22768 buffer_handle: Entity<Buffer>,
22769 server_id: LanguageServerId,
22770 cx: &mut App,
22771 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22772
22773 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22774
22775 fn document_highlights(
22776 &self,
22777 buffer: &Entity<Buffer>,
22778 position: text::Anchor,
22779 cx: &mut App,
22780 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22781
22782 fn definitions(
22783 &self,
22784 buffer: &Entity<Buffer>,
22785 position: text::Anchor,
22786 kind: GotoDefinitionKind,
22787 cx: &mut App,
22788 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22789
22790 fn range_for_rename(
22791 &self,
22792 buffer: &Entity<Buffer>,
22793 position: text::Anchor,
22794 cx: &mut App,
22795 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22796
22797 fn perform_rename(
22798 &self,
22799 buffer: &Entity<Buffer>,
22800 position: text::Anchor,
22801 new_name: String,
22802 cx: &mut App,
22803 ) -> Option<Task<Result<ProjectTransaction>>>;
22804}
22805
22806pub trait CompletionProvider {
22807 fn completions(
22808 &self,
22809 excerpt_id: ExcerptId,
22810 buffer: &Entity<Buffer>,
22811 buffer_position: text::Anchor,
22812 trigger: CompletionContext,
22813 window: &mut Window,
22814 cx: &mut Context<Editor>,
22815 ) -> Task<Result<Vec<CompletionResponse>>>;
22816
22817 fn resolve_completions(
22818 &self,
22819 _buffer: Entity<Buffer>,
22820 _completion_indices: Vec<usize>,
22821 _completions: Rc<RefCell<Box<[Completion]>>>,
22822 _cx: &mut Context<Editor>,
22823 ) -> Task<Result<bool>> {
22824 Task::ready(Ok(false))
22825 }
22826
22827 fn apply_additional_edits_for_completion(
22828 &self,
22829 _buffer: Entity<Buffer>,
22830 _completions: Rc<RefCell<Box<[Completion]>>>,
22831 _completion_index: usize,
22832 _push_to_history: bool,
22833 _cx: &mut Context<Editor>,
22834 ) -> Task<Result<Option<language::Transaction>>> {
22835 Task::ready(Ok(None))
22836 }
22837
22838 fn is_completion_trigger(
22839 &self,
22840 buffer: &Entity<Buffer>,
22841 position: language::Anchor,
22842 text: &str,
22843 trigger_in_words: bool,
22844 menu_is_open: bool,
22845 cx: &mut Context<Editor>,
22846 ) -> bool;
22847
22848 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22849
22850 fn sort_completions(&self) -> bool {
22851 true
22852 }
22853
22854 fn filter_completions(&self) -> bool {
22855 true
22856 }
22857}
22858
22859pub trait CodeActionProvider {
22860 fn id(&self) -> Arc<str>;
22861
22862 fn code_actions(
22863 &self,
22864 buffer: &Entity<Buffer>,
22865 range: Range<text::Anchor>,
22866 window: &mut Window,
22867 cx: &mut App,
22868 ) -> Task<Result<Vec<CodeAction>>>;
22869
22870 fn apply_code_action(
22871 &self,
22872 buffer_handle: Entity<Buffer>,
22873 action: CodeAction,
22874 excerpt_id: ExcerptId,
22875 push_to_history: bool,
22876 window: &mut Window,
22877 cx: &mut App,
22878 ) -> Task<Result<ProjectTransaction>>;
22879}
22880
22881impl CodeActionProvider for Entity<Project> {
22882 fn id(&self) -> Arc<str> {
22883 "project".into()
22884 }
22885
22886 fn code_actions(
22887 &self,
22888 buffer: &Entity<Buffer>,
22889 range: Range<text::Anchor>,
22890 _window: &mut Window,
22891 cx: &mut App,
22892 ) -> Task<Result<Vec<CodeAction>>> {
22893 self.update(cx, |project, cx| {
22894 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22895 let code_actions = project.code_actions(buffer, range, None, cx);
22896 cx.background_spawn(async move {
22897 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22898 Ok(code_lens_actions
22899 .context("code lens fetch")?
22900 .into_iter()
22901 .flatten()
22902 .chain(
22903 code_actions
22904 .context("code action fetch")?
22905 .into_iter()
22906 .flatten(),
22907 )
22908 .collect())
22909 })
22910 })
22911 }
22912
22913 fn apply_code_action(
22914 &self,
22915 buffer_handle: Entity<Buffer>,
22916 action: CodeAction,
22917 _excerpt_id: ExcerptId,
22918 push_to_history: bool,
22919 _window: &mut Window,
22920 cx: &mut App,
22921 ) -> Task<Result<ProjectTransaction>> {
22922 self.update(cx, |project, cx| {
22923 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22924 })
22925 }
22926}
22927
22928fn snippet_completions(
22929 project: &Project,
22930 buffer: &Entity<Buffer>,
22931 buffer_position: text::Anchor,
22932 cx: &mut App,
22933) -> Task<Result<CompletionResponse>> {
22934 let languages = buffer.read(cx).languages_at(buffer_position);
22935 let snippet_store = project.snippets().read(cx);
22936
22937 let scopes: Vec<_> = languages
22938 .iter()
22939 .filter_map(|language| {
22940 let language_name = language.lsp_id();
22941 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22942
22943 if snippets.is_empty() {
22944 None
22945 } else {
22946 Some((language.default_scope(), snippets))
22947 }
22948 })
22949 .collect();
22950
22951 if scopes.is_empty() {
22952 return Task::ready(Ok(CompletionResponse {
22953 completions: vec![],
22954 display_options: CompletionDisplayOptions::default(),
22955 is_incomplete: false,
22956 }));
22957 }
22958
22959 let snapshot = buffer.read(cx).text_snapshot();
22960 let executor = cx.background_executor().clone();
22961
22962 cx.background_spawn(async move {
22963 let mut is_incomplete = false;
22964 let mut completions: Vec<Completion> = Vec::new();
22965 for (scope, snippets) in scopes.into_iter() {
22966 let classifier =
22967 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
22968
22969 const MAX_WORD_PREFIX_LEN: usize = 128;
22970 let last_word: String = snapshot
22971 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22972 .take(MAX_WORD_PREFIX_LEN)
22973 .take_while(|c| classifier.is_word(*c))
22974 .collect::<String>()
22975 .chars()
22976 .rev()
22977 .collect();
22978
22979 if last_word.is_empty() {
22980 return Ok(CompletionResponse {
22981 completions: vec![],
22982 display_options: CompletionDisplayOptions::default(),
22983 is_incomplete: true,
22984 });
22985 }
22986
22987 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22988 let to_lsp = |point: &text::Anchor| {
22989 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22990 point_to_lsp(end)
22991 };
22992 let lsp_end = to_lsp(&buffer_position);
22993
22994 let candidates = snippets
22995 .iter()
22996 .enumerate()
22997 .flat_map(|(ix, snippet)| {
22998 snippet
22999 .prefix
23000 .iter()
23001 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
23002 })
23003 .collect::<Vec<StringMatchCandidate>>();
23004
23005 const MAX_RESULTS: usize = 100;
23006 let mut matches = fuzzy::match_strings(
23007 &candidates,
23008 &last_word,
23009 last_word.chars().any(|c| c.is_uppercase()),
23010 true,
23011 MAX_RESULTS,
23012 &Default::default(),
23013 executor.clone(),
23014 )
23015 .await;
23016
23017 if matches.len() >= MAX_RESULTS {
23018 is_incomplete = true;
23019 }
23020
23021 // Remove all candidates where the query's start does not match the start of any word in the candidate
23022 if let Some(query_start) = last_word.chars().next() {
23023 matches.retain(|string_match| {
23024 split_words(&string_match.string).any(|word| {
23025 // Check that the first codepoint of the word as lowercase matches the first
23026 // codepoint of the query as lowercase
23027 word.chars()
23028 .flat_map(|codepoint| codepoint.to_lowercase())
23029 .zip(query_start.to_lowercase())
23030 .all(|(word_cp, query_cp)| word_cp == query_cp)
23031 })
23032 });
23033 }
23034
23035 let matched_strings = matches
23036 .into_iter()
23037 .map(|m| m.string)
23038 .collect::<HashSet<_>>();
23039
23040 completions.extend(snippets.iter().filter_map(|snippet| {
23041 let matching_prefix = snippet
23042 .prefix
23043 .iter()
23044 .find(|prefix| matched_strings.contains(*prefix))?;
23045 let start = as_offset - last_word.len();
23046 let start = snapshot.anchor_before(start);
23047 let range = start..buffer_position;
23048 let lsp_start = to_lsp(&start);
23049 let lsp_range = lsp::Range {
23050 start: lsp_start,
23051 end: lsp_end,
23052 };
23053 Some(Completion {
23054 replace_range: range,
23055 new_text: snippet.body.clone(),
23056 source: CompletionSource::Lsp {
23057 insert_range: None,
23058 server_id: LanguageServerId(usize::MAX),
23059 resolved: true,
23060 lsp_completion: Box::new(lsp::CompletionItem {
23061 label: snippet.prefix.first().unwrap().clone(),
23062 kind: Some(CompletionItemKind::SNIPPET),
23063 label_details: snippet.description.as_ref().map(|description| {
23064 lsp::CompletionItemLabelDetails {
23065 detail: Some(description.clone()),
23066 description: None,
23067 }
23068 }),
23069 insert_text_format: Some(InsertTextFormat::SNIPPET),
23070 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23071 lsp::InsertReplaceEdit {
23072 new_text: snippet.body.clone(),
23073 insert: lsp_range,
23074 replace: lsp_range,
23075 },
23076 )),
23077 filter_text: Some(snippet.body.clone()),
23078 sort_text: Some(char::MAX.to_string()),
23079 ..lsp::CompletionItem::default()
23080 }),
23081 lsp_defaults: None,
23082 },
23083 label: CodeLabel {
23084 text: matching_prefix.clone(),
23085 runs: Vec::new(),
23086 filter_range: 0..matching_prefix.len(),
23087 },
23088 icon_path: None,
23089 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23090 single_line: snippet.name.clone().into(),
23091 plain_text: snippet
23092 .description
23093 .clone()
23094 .map(|description| description.into()),
23095 }),
23096 insert_text_mode: None,
23097 confirm: None,
23098 })
23099 }))
23100 }
23101
23102 Ok(CompletionResponse {
23103 completions,
23104 display_options: CompletionDisplayOptions::default(),
23105 is_incomplete,
23106 })
23107 })
23108}
23109
23110impl CompletionProvider for Entity<Project> {
23111 fn completions(
23112 &self,
23113 _excerpt_id: ExcerptId,
23114 buffer: &Entity<Buffer>,
23115 buffer_position: text::Anchor,
23116 options: CompletionContext,
23117 _window: &mut Window,
23118 cx: &mut Context<Editor>,
23119 ) -> Task<Result<Vec<CompletionResponse>>> {
23120 self.update(cx, |project, cx| {
23121 let snippets = snippet_completions(project, buffer, buffer_position, cx);
23122 let project_completions = project.completions(buffer, buffer_position, options, cx);
23123 cx.background_spawn(async move {
23124 let mut responses = project_completions.await?;
23125 let snippets = snippets.await?;
23126 if !snippets.completions.is_empty() {
23127 responses.push(snippets);
23128 }
23129 Ok(responses)
23130 })
23131 })
23132 }
23133
23134 fn resolve_completions(
23135 &self,
23136 buffer: Entity<Buffer>,
23137 completion_indices: Vec<usize>,
23138 completions: Rc<RefCell<Box<[Completion]>>>,
23139 cx: &mut Context<Editor>,
23140 ) -> Task<Result<bool>> {
23141 self.update(cx, |project, cx| {
23142 project.lsp_store().update(cx, |lsp_store, cx| {
23143 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23144 })
23145 })
23146 }
23147
23148 fn apply_additional_edits_for_completion(
23149 &self,
23150 buffer: Entity<Buffer>,
23151 completions: Rc<RefCell<Box<[Completion]>>>,
23152 completion_index: usize,
23153 push_to_history: bool,
23154 cx: &mut Context<Editor>,
23155 ) -> Task<Result<Option<language::Transaction>>> {
23156 self.update(cx, |project, cx| {
23157 project.lsp_store().update(cx, |lsp_store, cx| {
23158 lsp_store.apply_additional_edits_for_completion(
23159 buffer,
23160 completions,
23161 completion_index,
23162 push_to_history,
23163 cx,
23164 )
23165 })
23166 })
23167 }
23168
23169 fn is_completion_trigger(
23170 &self,
23171 buffer: &Entity<Buffer>,
23172 position: language::Anchor,
23173 text: &str,
23174 trigger_in_words: bool,
23175 menu_is_open: bool,
23176 cx: &mut Context<Editor>,
23177 ) -> bool {
23178 let mut chars = text.chars();
23179 let char = if let Some(char) = chars.next() {
23180 char
23181 } else {
23182 return false;
23183 };
23184 if chars.next().is_some() {
23185 return false;
23186 }
23187
23188 let buffer = buffer.read(cx);
23189 let snapshot = buffer.snapshot();
23190 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23191 return false;
23192 }
23193 let classifier = snapshot
23194 .char_classifier_at(position)
23195 .scope_context(Some(CharScopeContext::Completion));
23196 if trigger_in_words && classifier.is_word(char) {
23197 return true;
23198 }
23199
23200 buffer.completion_triggers().contains(text)
23201 }
23202}
23203
23204impl SemanticsProvider for Entity<Project> {
23205 fn hover(
23206 &self,
23207 buffer: &Entity<Buffer>,
23208 position: text::Anchor,
23209 cx: &mut App,
23210 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23211 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23212 }
23213
23214 fn document_highlights(
23215 &self,
23216 buffer: &Entity<Buffer>,
23217 position: text::Anchor,
23218 cx: &mut App,
23219 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23220 Some(self.update(cx, |project, cx| {
23221 project.document_highlights(buffer, position, cx)
23222 }))
23223 }
23224
23225 fn definitions(
23226 &self,
23227 buffer: &Entity<Buffer>,
23228 position: text::Anchor,
23229 kind: GotoDefinitionKind,
23230 cx: &mut App,
23231 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23232 Some(self.update(cx, |project, cx| match kind {
23233 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23234 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23235 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23236 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23237 }))
23238 }
23239
23240 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23241 self.update(cx, |project, cx| {
23242 if project
23243 .active_debug_session(cx)
23244 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23245 {
23246 return true;
23247 }
23248
23249 buffer.update(cx, |buffer, cx| {
23250 project.any_language_server_supports_inlay_hints(buffer, cx)
23251 })
23252 })
23253 }
23254
23255 fn inline_values(
23256 &self,
23257 buffer_handle: Entity<Buffer>,
23258 range: Range<text::Anchor>,
23259 cx: &mut App,
23260 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23261 self.update(cx, |project, cx| {
23262 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23263
23264 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23265 })
23266 }
23267
23268 fn inlay_hints(
23269 &self,
23270 buffer_handle: Entity<Buffer>,
23271 range: Range<text::Anchor>,
23272 cx: &mut App,
23273 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23274 Some(self.update(cx, |project, cx| {
23275 project.inlay_hints(buffer_handle, range, cx)
23276 }))
23277 }
23278
23279 fn resolve_inlay_hint(
23280 &self,
23281 hint: InlayHint,
23282 buffer_handle: Entity<Buffer>,
23283 server_id: LanguageServerId,
23284 cx: &mut App,
23285 ) -> Option<Task<anyhow::Result<InlayHint>>> {
23286 Some(self.update(cx, |project, cx| {
23287 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
23288 }))
23289 }
23290
23291 fn range_for_rename(
23292 &self,
23293 buffer: &Entity<Buffer>,
23294 position: text::Anchor,
23295 cx: &mut App,
23296 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23297 Some(self.update(cx, |project, cx| {
23298 let buffer = buffer.clone();
23299 let task = project.prepare_rename(buffer.clone(), position, cx);
23300 cx.spawn(async move |_, cx| {
23301 Ok(match task.await? {
23302 PrepareRenameResponse::Success(range) => Some(range),
23303 PrepareRenameResponse::InvalidPosition => None,
23304 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23305 // Fallback on using TreeSitter info to determine identifier range
23306 buffer.read_with(cx, |buffer, _| {
23307 let snapshot = buffer.snapshot();
23308 let (range, kind) = snapshot.surrounding_word(position, None);
23309 if kind != Some(CharKind::Word) {
23310 return None;
23311 }
23312 Some(
23313 snapshot.anchor_before(range.start)
23314 ..snapshot.anchor_after(range.end),
23315 )
23316 })?
23317 }
23318 })
23319 })
23320 }))
23321 }
23322
23323 fn perform_rename(
23324 &self,
23325 buffer: &Entity<Buffer>,
23326 position: text::Anchor,
23327 new_name: String,
23328 cx: &mut App,
23329 ) -> Option<Task<Result<ProjectTransaction>>> {
23330 Some(self.update(cx, |project, cx| {
23331 project.perform_rename(buffer.clone(), position, new_name, cx)
23332 }))
23333 }
23334}
23335
23336fn inlay_hint_settings(
23337 location: Anchor,
23338 snapshot: &MultiBufferSnapshot,
23339 cx: &mut Context<Editor>,
23340) -> InlayHintSettings {
23341 let file = snapshot.file_at(location);
23342 let language = snapshot.language_at(location).map(|l| l.name());
23343 language_settings(language, file, cx).inlay_hints
23344}
23345
23346fn consume_contiguous_rows(
23347 contiguous_row_selections: &mut Vec<Selection<Point>>,
23348 selection: &Selection<Point>,
23349 display_map: &DisplaySnapshot,
23350 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23351) -> (MultiBufferRow, MultiBufferRow) {
23352 contiguous_row_selections.push(selection.clone());
23353 let start_row = starting_row(selection, display_map);
23354 let mut end_row = ending_row(selection, display_map);
23355
23356 while let Some(next_selection) = selections.peek() {
23357 if next_selection.start.row <= end_row.0 {
23358 end_row = ending_row(next_selection, display_map);
23359 contiguous_row_selections.push(selections.next().unwrap().clone());
23360 } else {
23361 break;
23362 }
23363 }
23364 (start_row, end_row)
23365}
23366
23367fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23368 if selection.start.column > 0 {
23369 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23370 } else {
23371 MultiBufferRow(selection.start.row)
23372 }
23373}
23374
23375fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23376 if next_selection.end.column > 0 || next_selection.is_empty() {
23377 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23378 } else {
23379 MultiBufferRow(next_selection.end.row)
23380 }
23381}
23382
23383impl EditorSnapshot {
23384 pub fn remote_selections_in_range<'a>(
23385 &'a self,
23386 range: &'a Range<Anchor>,
23387 collaboration_hub: &dyn CollaborationHub,
23388 cx: &'a App,
23389 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23390 let participant_names = collaboration_hub.user_names(cx);
23391 let participant_indices = collaboration_hub.user_participant_indices(cx);
23392 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23393 let collaborators_by_replica_id = collaborators_by_peer_id
23394 .values()
23395 .map(|collaborator| (collaborator.replica_id, collaborator))
23396 .collect::<HashMap<_, _>>();
23397 self.buffer_snapshot()
23398 .selections_in_range(range, false)
23399 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23400 if replica_id == AGENT_REPLICA_ID {
23401 Some(RemoteSelection {
23402 replica_id,
23403 selection,
23404 cursor_shape,
23405 line_mode,
23406 collaborator_id: CollaboratorId::Agent,
23407 user_name: Some("Agent".into()),
23408 color: cx.theme().players().agent(),
23409 })
23410 } else {
23411 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23412 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23413 let user_name = participant_names.get(&collaborator.user_id).cloned();
23414 Some(RemoteSelection {
23415 replica_id,
23416 selection,
23417 cursor_shape,
23418 line_mode,
23419 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23420 user_name,
23421 color: if let Some(index) = participant_index {
23422 cx.theme().players().color_for_participant(index.0)
23423 } else {
23424 cx.theme().players().absent()
23425 },
23426 })
23427 }
23428 })
23429 }
23430
23431 pub fn hunks_for_ranges(
23432 &self,
23433 ranges: impl IntoIterator<Item = Range<Point>>,
23434 ) -> Vec<MultiBufferDiffHunk> {
23435 let mut hunks = Vec::new();
23436 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23437 HashMap::default();
23438 for query_range in ranges {
23439 let query_rows =
23440 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23441 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23442 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23443 ) {
23444 // Include deleted hunks that are adjacent to the query range, because
23445 // otherwise they would be missed.
23446 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23447 if hunk.status().is_deleted() {
23448 intersects_range |= hunk.row_range.start == query_rows.end;
23449 intersects_range |= hunk.row_range.end == query_rows.start;
23450 }
23451 if intersects_range {
23452 if !processed_buffer_rows
23453 .entry(hunk.buffer_id)
23454 .or_default()
23455 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23456 {
23457 continue;
23458 }
23459 hunks.push(hunk);
23460 }
23461 }
23462 }
23463
23464 hunks
23465 }
23466
23467 fn display_diff_hunks_for_rows<'a>(
23468 &'a self,
23469 display_rows: Range<DisplayRow>,
23470 folded_buffers: &'a HashSet<BufferId>,
23471 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23472 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23473 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23474
23475 self.buffer_snapshot()
23476 .diff_hunks_in_range(buffer_start..buffer_end)
23477 .filter_map(|hunk| {
23478 if folded_buffers.contains(&hunk.buffer_id) {
23479 return None;
23480 }
23481
23482 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23483 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23484
23485 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23486 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23487
23488 let display_hunk = if hunk_display_start.column() != 0 {
23489 DisplayDiffHunk::Folded {
23490 display_row: hunk_display_start.row(),
23491 }
23492 } else {
23493 let mut end_row = hunk_display_end.row();
23494 if hunk_display_end.column() > 0 {
23495 end_row.0 += 1;
23496 }
23497 let is_created_file = hunk.is_created_file();
23498 DisplayDiffHunk::Unfolded {
23499 status: hunk.status(),
23500 diff_base_byte_range: hunk.diff_base_byte_range,
23501 display_row_range: hunk_display_start.row()..end_row,
23502 multi_buffer_range: Anchor::range_in_buffer(
23503 hunk.excerpt_id,
23504 hunk.buffer_id,
23505 hunk.buffer_range,
23506 ),
23507 is_created_file,
23508 }
23509 };
23510
23511 Some(display_hunk)
23512 })
23513 }
23514
23515 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23516 self.display_snapshot
23517 .buffer_snapshot()
23518 .language_at(position)
23519 }
23520
23521 pub fn is_focused(&self) -> bool {
23522 self.is_focused
23523 }
23524
23525 pub fn placeholder_text(&self) -> Option<String> {
23526 self.placeholder_display_snapshot
23527 .as_ref()
23528 .map(|display_map| display_map.text())
23529 }
23530
23531 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23532 self.scroll_anchor.scroll_position(&self.display_snapshot)
23533 }
23534
23535 fn gutter_dimensions(
23536 &self,
23537 font_id: FontId,
23538 font_size: Pixels,
23539 max_line_number_width: Pixels,
23540 cx: &App,
23541 ) -> Option<GutterDimensions> {
23542 if !self.show_gutter {
23543 return None;
23544 }
23545
23546 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23547 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23548
23549 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23550 matches!(
23551 ProjectSettings::get_global(cx).git.git_gutter,
23552 GitGutterSetting::TrackedFiles
23553 )
23554 });
23555 let gutter_settings = EditorSettings::get_global(cx).gutter;
23556 let show_line_numbers = self
23557 .show_line_numbers
23558 .unwrap_or(gutter_settings.line_numbers);
23559 let line_gutter_width = if show_line_numbers {
23560 // Avoid flicker-like gutter resizes when the line number gains another digit by
23561 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23562 let min_width_for_number_on_gutter =
23563 ch_advance * gutter_settings.min_line_number_digits as f32;
23564 max_line_number_width.max(min_width_for_number_on_gutter)
23565 } else {
23566 0.0.into()
23567 };
23568
23569 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23570 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23571
23572 let git_blame_entries_width =
23573 self.git_blame_gutter_max_author_length
23574 .map(|max_author_length| {
23575 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23576 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23577
23578 /// The number of characters to dedicate to gaps and margins.
23579 const SPACING_WIDTH: usize = 4;
23580
23581 let max_char_count = max_author_length.min(renderer.max_author_length())
23582 + ::git::SHORT_SHA_LENGTH
23583 + MAX_RELATIVE_TIMESTAMP.len()
23584 + SPACING_WIDTH;
23585
23586 ch_advance * max_char_count
23587 });
23588
23589 let is_singleton = self.buffer_snapshot().is_singleton();
23590
23591 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23592 left_padding += if !is_singleton {
23593 ch_width * 4.0
23594 } else if show_runnables || show_breakpoints {
23595 ch_width * 3.0
23596 } else if show_git_gutter && show_line_numbers {
23597 ch_width * 2.0
23598 } else if show_git_gutter || show_line_numbers {
23599 ch_width
23600 } else {
23601 px(0.)
23602 };
23603
23604 let shows_folds = is_singleton && gutter_settings.folds;
23605
23606 let right_padding = if shows_folds && show_line_numbers {
23607 ch_width * 4.0
23608 } else if shows_folds || (!is_singleton && show_line_numbers) {
23609 ch_width * 3.0
23610 } else if show_line_numbers {
23611 ch_width
23612 } else {
23613 px(0.)
23614 };
23615
23616 Some(GutterDimensions {
23617 left_padding,
23618 right_padding,
23619 width: line_gutter_width + left_padding + right_padding,
23620 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23621 git_blame_entries_width,
23622 })
23623 }
23624
23625 pub fn render_crease_toggle(
23626 &self,
23627 buffer_row: MultiBufferRow,
23628 row_contains_cursor: bool,
23629 editor: Entity<Editor>,
23630 window: &mut Window,
23631 cx: &mut App,
23632 ) -> Option<AnyElement> {
23633 let folded = self.is_line_folded(buffer_row);
23634 let mut is_foldable = false;
23635
23636 if let Some(crease) = self
23637 .crease_snapshot
23638 .query_row(buffer_row, self.buffer_snapshot())
23639 {
23640 is_foldable = true;
23641 match crease {
23642 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23643 if let Some(render_toggle) = render_toggle {
23644 let toggle_callback =
23645 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23646 if folded {
23647 editor.update(cx, |editor, cx| {
23648 editor.fold_at(buffer_row, window, cx)
23649 });
23650 } else {
23651 editor.update(cx, |editor, cx| {
23652 editor.unfold_at(buffer_row, window, cx)
23653 });
23654 }
23655 });
23656 return Some((render_toggle)(
23657 buffer_row,
23658 folded,
23659 toggle_callback,
23660 window,
23661 cx,
23662 ));
23663 }
23664 }
23665 }
23666 }
23667
23668 is_foldable |= self.starts_indent(buffer_row);
23669
23670 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23671 Some(
23672 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23673 .toggle_state(folded)
23674 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23675 if folded {
23676 this.unfold_at(buffer_row, window, cx);
23677 } else {
23678 this.fold_at(buffer_row, window, cx);
23679 }
23680 }))
23681 .into_any_element(),
23682 )
23683 } else {
23684 None
23685 }
23686 }
23687
23688 pub fn render_crease_trailer(
23689 &self,
23690 buffer_row: MultiBufferRow,
23691 window: &mut Window,
23692 cx: &mut App,
23693 ) -> Option<AnyElement> {
23694 let folded = self.is_line_folded(buffer_row);
23695 if let Crease::Inline { render_trailer, .. } = self
23696 .crease_snapshot
23697 .query_row(buffer_row, self.buffer_snapshot())?
23698 {
23699 let render_trailer = render_trailer.as_ref()?;
23700 Some(render_trailer(buffer_row, folded, window, cx))
23701 } else {
23702 None
23703 }
23704 }
23705}
23706
23707impl Deref for EditorSnapshot {
23708 type Target = DisplaySnapshot;
23709
23710 fn deref(&self) -> &Self::Target {
23711 &self.display_snapshot
23712 }
23713}
23714
23715#[derive(Clone, Debug, PartialEq, Eq)]
23716pub enum EditorEvent {
23717 InputIgnored {
23718 text: Arc<str>,
23719 },
23720 InputHandled {
23721 utf16_range_to_replace: Option<Range<isize>>,
23722 text: Arc<str>,
23723 },
23724 ExcerptsAdded {
23725 buffer: Entity<Buffer>,
23726 predecessor: ExcerptId,
23727 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23728 },
23729 ExcerptsRemoved {
23730 ids: Vec<ExcerptId>,
23731 removed_buffer_ids: Vec<BufferId>,
23732 },
23733 BufferFoldToggled {
23734 ids: Vec<ExcerptId>,
23735 folded: bool,
23736 },
23737 ExcerptsEdited {
23738 ids: Vec<ExcerptId>,
23739 },
23740 ExcerptsExpanded {
23741 ids: Vec<ExcerptId>,
23742 },
23743 BufferEdited,
23744 Edited {
23745 transaction_id: clock::Lamport,
23746 },
23747 Reparsed(BufferId),
23748 Focused,
23749 FocusedIn,
23750 Blurred,
23751 DirtyChanged,
23752 Saved,
23753 TitleChanged,
23754 SelectionsChanged {
23755 local: bool,
23756 },
23757 ScrollPositionChanged {
23758 local: bool,
23759 autoscroll: bool,
23760 },
23761 TransactionUndone {
23762 transaction_id: clock::Lamport,
23763 },
23764 TransactionBegun {
23765 transaction_id: clock::Lamport,
23766 },
23767 CursorShapeChanged,
23768 BreadcrumbsChanged,
23769 PushedToNavHistory {
23770 anchor: Anchor,
23771 is_deactivate: bool,
23772 },
23773}
23774
23775impl EventEmitter<EditorEvent> for Editor {}
23776
23777impl Focusable for Editor {
23778 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23779 self.focus_handle.clone()
23780 }
23781}
23782
23783impl Render for Editor {
23784 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23785 let settings = ThemeSettings::get_global(cx);
23786
23787 let mut text_style = match self.mode {
23788 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23789 color: cx.theme().colors().editor_foreground,
23790 font_family: settings.ui_font.family.clone(),
23791 font_features: settings.ui_font.features.clone(),
23792 font_fallbacks: settings.ui_font.fallbacks.clone(),
23793 font_size: rems(0.875).into(),
23794 font_weight: settings.ui_font.weight,
23795 line_height: relative(settings.buffer_line_height.value()),
23796 ..Default::default()
23797 },
23798 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23799 color: cx.theme().colors().editor_foreground,
23800 font_family: settings.buffer_font.family.clone(),
23801 font_features: settings.buffer_font.features.clone(),
23802 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23803 font_size: settings.buffer_font_size(cx).into(),
23804 font_weight: settings.buffer_font.weight,
23805 line_height: relative(settings.buffer_line_height.value()),
23806 ..Default::default()
23807 },
23808 };
23809 if let Some(text_style_refinement) = &self.text_style_refinement {
23810 text_style.refine(text_style_refinement)
23811 }
23812
23813 let background = match self.mode {
23814 EditorMode::SingleLine => cx.theme().system().transparent,
23815 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23816 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23817 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23818 };
23819
23820 EditorElement::new(
23821 &cx.entity(),
23822 EditorStyle {
23823 background,
23824 border: cx.theme().colors().border,
23825 local_player: cx.theme().players().local(),
23826 text: text_style,
23827 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23828 syntax: cx.theme().syntax().clone(),
23829 status: cx.theme().status().clone(),
23830 inlay_hints_style: make_inlay_hints_style(cx),
23831 edit_prediction_styles: make_suggestion_styles(cx),
23832 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23833 show_underlines: self.diagnostics_enabled(),
23834 },
23835 )
23836 }
23837}
23838
23839impl EntityInputHandler for Editor {
23840 fn text_for_range(
23841 &mut self,
23842 range_utf16: Range<usize>,
23843 adjusted_range: &mut Option<Range<usize>>,
23844 _: &mut Window,
23845 cx: &mut Context<Self>,
23846 ) -> Option<String> {
23847 let snapshot = self.buffer.read(cx).read(cx);
23848 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23849 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23850 if (start.0..end.0) != range_utf16 {
23851 adjusted_range.replace(start.0..end.0);
23852 }
23853 Some(snapshot.text_for_range(start..end).collect())
23854 }
23855
23856 fn selected_text_range(
23857 &mut self,
23858 ignore_disabled_input: bool,
23859 _: &mut Window,
23860 cx: &mut Context<Self>,
23861 ) -> Option<UTF16Selection> {
23862 // Prevent the IME menu from appearing when holding down an alphabetic key
23863 // while input is disabled.
23864 if !ignore_disabled_input && !self.input_enabled {
23865 return None;
23866 }
23867
23868 let selection = self.selections.newest::<OffsetUtf16>(cx);
23869 let range = selection.range();
23870
23871 Some(UTF16Selection {
23872 range: range.start.0..range.end.0,
23873 reversed: selection.reversed,
23874 })
23875 }
23876
23877 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23878 let snapshot = self.buffer.read(cx).read(cx);
23879 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23880 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23881 }
23882
23883 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23884 self.clear_highlights::<InputComposition>(cx);
23885 self.ime_transaction.take();
23886 }
23887
23888 fn replace_text_in_range(
23889 &mut self,
23890 range_utf16: Option<Range<usize>>,
23891 text: &str,
23892 window: &mut Window,
23893 cx: &mut Context<Self>,
23894 ) {
23895 if !self.input_enabled {
23896 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23897 return;
23898 }
23899
23900 self.transact(window, cx, |this, window, cx| {
23901 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23902 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23903 Some(this.selection_replacement_ranges(range_utf16, cx))
23904 } else {
23905 this.marked_text_ranges(cx)
23906 };
23907
23908 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23909 let newest_selection_id = this.selections.newest_anchor().id;
23910 this.selections
23911 .all::<OffsetUtf16>(cx)
23912 .iter()
23913 .zip(ranges_to_replace.iter())
23914 .find_map(|(selection, range)| {
23915 if selection.id == newest_selection_id {
23916 Some(
23917 (range.start.0 as isize - selection.head().0 as isize)
23918 ..(range.end.0 as isize - selection.head().0 as isize),
23919 )
23920 } else {
23921 None
23922 }
23923 })
23924 });
23925
23926 cx.emit(EditorEvent::InputHandled {
23927 utf16_range_to_replace: range_to_replace,
23928 text: text.into(),
23929 });
23930
23931 if let Some(new_selected_ranges) = new_selected_ranges {
23932 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23933 selections.select_ranges(new_selected_ranges)
23934 });
23935 this.backspace(&Default::default(), window, cx);
23936 }
23937
23938 this.handle_input(text, window, cx);
23939 });
23940
23941 if let Some(transaction) = self.ime_transaction {
23942 self.buffer.update(cx, |buffer, cx| {
23943 buffer.group_until_transaction(transaction, cx);
23944 });
23945 }
23946
23947 self.unmark_text(window, cx);
23948 }
23949
23950 fn replace_and_mark_text_in_range(
23951 &mut self,
23952 range_utf16: Option<Range<usize>>,
23953 text: &str,
23954 new_selected_range_utf16: Option<Range<usize>>,
23955 window: &mut Window,
23956 cx: &mut Context<Self>,
23957 ) {
23958 if !self.input_enabled {
23959 return;
23960 }
23961
23962 let transaction = self.transact(window, cx, |this, window, cx| {
23963 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23964 let snapshot = this.buffer.read(cx).read(cx);
23965 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23966 for marked_range in &mut marked_ranges {
23967 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23968 marked_range.start.0 += relative_range_utf16.start;
23969 marked_range.start =
23970 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23971 marked_range.end =
23972 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23973 }
23974 }
23975 Some(marked_ranges)
23976 } else if let Some(range_utf16) = range_utf16 {
23977 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23978 Some(this.selection_replacement_ranges(range_utf16, cx))
23979 } else {
23980 None
23981 };
23982
23983 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23984 let newest_selection_id = this.selections.newest_anchor().id;
23985 this.selections
23986 .all::<OffsetUtf16>(cx)
23987 .iter()
23988 .zip(ranges_to_replace.iter())
23989 .find_map(|(selection, range)| {
23990 if selection.id == newest_selection_id {
23991 Some(
23992 (range.start.0 as isize - selection.head().0 as isize)
23993 ..(range.end.0 as isize - selection.head().0 as isize),
23994 )
23995 } else {
23996 None
23997 }
23998 })
23999 });
24000
24001 cx.emit(EditorEvent::InputHandled {
24002 utf16_range_to_replace: range_to_replace,
24003 text: text.into(),
24004 });
24005
24006 if let Some(ranges) = ranges_to_replace {
24007 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24008 s.select_ranges(ranges)
24009 });
24010 }
24011
24012 let marked_ranges = {
24013 let snapshot = this.buffer.read(cx).read(cx);
24014 this.selections
24015 .disjoint_anchors_arc()
24016 .iter()
24017 .map(|selection| {
24018 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24019 })
24020 .collect::<Vec<_>>()
24021 };
24022
24023 if text.is_empty() {
24024 this.unmark_text(window, cx);
24025 } else {
24026 this.highlight_text::<InputComposition>(
24027 marked_ranges.clone(),
24028 HighlightStyle {
24029 underline: Some(UnderlineStyle {
24030 thickness: px(1.),
24031 color: None,
24032 wavy: false,
24033 }),
24034 ..Default::default()
24035 },
24036 cx,
24037 );
24038 }
24039
24040 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24041 let use_autoclose = this.use_autoclose;
24042 let use_auto_surround = this.use_auto_surround;
24043 this.set_use_autoclose(false);
24044 this.set_use_auto_surround(false);
24045 this.handle_input(text, window, cx);
24046 this.set_use_autoclose(use_autoclose);
24047 this.set_use_auto_surround(use_auto_surround);
24048
24049 if let Some(new_selected_range) = new_selected_range_utf16 {
24050 let snapshot = this.buffer.read(cx).read(cx);
24051 let new_selected_ranges = marked_ranges
24052 .into_iter()
24053 .map(|marked_range| {
24054 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24055 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
24056 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
24057 snapshot.clip_offset_utf16(new_start, Bias::Left)
24058 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24059 })
24060 .collect::<Vec<_>>();
24061
24062 drop(snapshot);
24063 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24064 selections.select_ranges(new_selected_ranges)
24065 });
24066 }
24067 });
24068
24069 self.ime_transaction = self.ime_transaction.or(transaction);
24070 if let Some(transaction) = self.ime_transaction {
24071 self.buffer.update(cx, |buffer, cx| {
24072 buffer.group_until_transaction(transaction, cx);
24073 });
24074 }
24075
24076 if self.text_highlights::<InputComposition>(cx).is_none() {
24077 self.ime_transaction.take();
24078 }
24079 }
24080
24081 fn bounds_for_range(
24082 &mut self,
24083 range_utf16: Range<usize>,
24084 element_bounds: gpui::Bounds<Pixels>,
24085 window: &mut Window,
24086 cx: &mut Context<Self>,
24087 ) -> Option<gpui::Bounds<Pixels>> {
24088 let text_layout_details = self.text_layout_details(window);
24089 let CharacterDimensions {
24090 em_width,
24091 em_advance,
24092 line_height,
24093 } = self.character_dimensions(window);
24094
24095 let snapshot = self.snapshot(window, cx);
24096 let scroll_position = snapshot.scroll_position();
24097 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24098
24099 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
24100 let x = Pixels::from(
24101 ScrollOffset::from(
24102 snapshot.x_for_display_point(start, &text_layout_details)
24103 + self.gutter_dimensions.full_width(),
24104 ) - scroll_left,
24105 );
24106 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24107
24108 Some(Bounds {
24109 origin: element_bounds.origin + point(x, y),
24110 size: size(em_width, line_height),
24111 })
24112 }
24113
24114 fn character_index_for_point(
24115 &mut self,
24116 point: gpui::Point<Pixels>,
24117 _window: &mut Window,
24118 _cx: &mut Context<Self>,
24119 ) -> Option<usize> {
24120 let position_map = self.last_position_map.as_ref()?;
24121 if !position_map.text_hitbox.contains(&point) {
24122 return None;
24123 }
24124 let display_point = position_map.point_for_position(point).previous_valid;
24125 let anchor = position_map
24126 .snapshot
24127 .display_point_to_anchor(display_point, Bias::Left);
24128 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24129 Some(utf16_offset.0)
24130 }
24131}
24132
24133trait SelectionExt {
24134 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24135 fn spanned_rows(
24136 &self,
24137 include_end_if_at_line_start: bool,
24138 map: &DisplaySnapshot,
24139 ) -> Range<MultiBufferRow>;
24140}
24141
24142impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24143 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24144 let start = self
24145 .start
24146 .to_point(map.buffer_snapshot())
24147 .to_display_point(map);
24148 let end = self
24149 .end
24150 .to_point(map.buffer_snapshot())
24151 .to_display_point(map);
24152 if self.reversed {
24153 end..start
24154 } else {
24155 start..end
24156 }
24157 }
24158
24159 fn spanned_rows(
24160 &self,
24161 include_end_if_at_line_start: bool,
24162 map: &DisplaySnapshot,
24163 ) -> Range<MultiBufferRow> {
24164 let start = self.start.to_point(map.buffer_snapshot());
24165 let mut end = self.end.to_point(map.buffer_snapshot());
24166 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24167 end.row -= 1;
24168 }
24169
24170 let buffer_start = map.prev_line_boundary(start).0;
24171 let buffer_end = map.next_line_boundary(end).0;
24172 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24173 }
24174}
24175
24176impl<T: InvalidationRegion> InvalidationStack<T> {
24177 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24178 where
24179 S: Clone + ToOffset,
24180 {
24181 while let Some(region) = self.last() {
24182 let all_selections_inside_invalidation_ranges =
24183 if selections.len() == region.ranges().len() {
24184 selections
24185 .iter()
24186 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24187 .all(|(selection, invalidation_range)| {
24188 let head = selection.head().to_offset(buffer);
24189 invalidation_range.start <= head && invalidation_range.end >= head
24190 })
24191 } else {
24192 false
24193 };
24194
24195 if all_selections_inside_invalidation_ranges {
24196 break;
24197 } else {
24198 self.pop();
24199 }
24200 }
24201 }
24202}
24203
24204impl<T> Default for InvalidationStack<T> {
24205 fn default() -> Self {
24206 Self(Default::default())
24207 }
24208}
24209
24210impl<T> Deref for InvalidationStack<T> {
24211 type Target = Vec<T>;
24212
24213 fn deref(&self) -> &Self::Target {
24214 &self.0
24215 }
24216}
24217
24218impl<T> DerefMut for InvalidationStack<T> {
24219 fn deref_mut(&mut self) -> &mut Self::Target {
24220 &mut self.0
24221 }
24222}
24223
24224impl InvalidationRegion for SnippetState {
24225 fn ranges(&self) -> &[Range<Anchor>] {
24226 &self.ranges[self.active_index]
24227 }
24228}
24229
24230fn edit_prediction_edit_text(
24231 current_snapshot: &BufferSnapshot,
24232 edits: &[(Range<Anchor>, String)],
24233 edit_preview: &EditPreview,
24234 include_deletions: bool,
24235 cx: &App,
24236) -> HighlightedText {
24237 let edits = edits
24238 .iter()
24239 .map(|(anchor, text)| {
24240 (
24241 anchor.start.text_anchor..anchor.end.text_anchor,
24242 text.clone(),
24243 )
24244 })
24245 .collect::<Vec<_>>();
24246
24247 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24248}
24249
24250fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
24251 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24252 // Just show the raw edit text with basic styling
24253 let mut text = String::new();
24254 let mut highlights = Vec::new();
24255
24256 let insertion_highlight_style = HighlightStyle {
24257 color: Some(cx.theme().colors().text),
24258 ..Default::default()
24259 };
24260
24261 for (_, edit_text) in edits {
24262 let start_offset = text.len();
24263 text.push_str(edit_text);
24264 let end_offset = text.len();
24265
24266 if start_offset < end_offset {
24267 highlights.push((start_offset..end_offset, insertion_highlight_style));
24268 }
24269 }
24270
24271 HighlightedText {
24272 text: text.into(),
24273 highlights,
24274 }
24275}
24276
24277pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24278 match severity {
24279 lsp::DiagnosticSeverity::ERROR => colors.error,
24280 lsp::DiagnosticSeverity::WARNING => colors.warning,
24281 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24282 lsp::DiagnosticSeverity::HINT => colors.info,
24283 _ => colors.ignored,
24284 }
24285}
24286
24287pub fn styled_runs_for_code_label<'a>(
24288 label: &'a CodeLabel,
24289 syntax_theme: &'a theme::SyntaxTheme,
24290) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24291 let fade_out = HighlightStyle {
24292 fade_out: Some(0.35),
24293 ..Default::default()
24294 };
24295
24296 let mut prev_end = label.filter_range.end;
24297 label
24298 .runs
24299 .iter()
24300 .enumerate()
24301 .flat_map(move |(ix, (range, highlight_id))| {
24302 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24303 style
24304 } else {
24305 return Default::default();
24306 };
24307 let muted_style = style.highlight(fade_out);
24308
24309 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24310 if range.start >= label.filter_range.end {
24311 if range.start > prev_end {
24312 runs.push((prev_end..range.start, fade_out));
24313 }
24314 runs.push((range.clone(), muted_style));
24315 } else if range.end <= label.filter_range.end {
24316 runs.push((range.clone(), style));
24317 } else {
24318 runs.push((range.start..label.filter_range.end, style));
24319 runs.push((label.filter_range.end..range.end, muted_style));
24320 }
24321 prev_end = cmp::max(prev_end, range.end);
24322
24323 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24324 runs.push((prev_end..label.text.len(), fade_out));
24325 }
24326
24327 runs
24328 })
24329}
24330
24331pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24332 let mut prev_index = 0;
24333 let mut prev_codepoint: Option<char> = None;
24334 text.char_indices()
24335 .chain([(text.len(), '\0')])
24336 .filter_map(move |(index, codepoint)| {
24337 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24338 let is_boundary = index == text.len()
24339 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24340 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24341 if is_boundary {
24342 let chunk = &text[prev_index..index];
24343 prev_index = index;
24344 Some(chunk)
24345 } else {
24346 None
24347 }
24348 })
24349}
24350
24351pub trait RangeToAnchorExt: Sized {
24352 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24353
24354 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24355 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24356 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24357 }
24358}
24359
24360impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24361 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24362 let start_offset = self.start.to_offset(snapshot);
24363 let end_offset = self.end.to_offset(snapshot);
24364 if start_offset == end_offset {
24365 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24366 } else {
24367 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24368 }
24369 }
24370}
24371
24372pub trait RowExt {
24373 fn as_f64(&self) -> f64;
24374
24375 fn next_row(&self) -> Self;
24376
24377 fn previous_row(&self) -> Self;
24378
24379 fn minus(&self, other: Self) -> u32;
24380}
24381
24382impl RowExt for DisplayRow {
24383 fn as_f64(&self) -> f64 {
24384 self.0 as _
24385 }
24386
24387 fn next_row(&self) -> Self {
24388 Self(self.0 + 1)
24389 }
24390
24391 fn previous_row(&self) -> Self {
24392 Self(self.0.saturating_sub(1))
24393 }
24394
24395 fn minus(&self, other: Self) -> u32 {
24396 self.0 - other.0
24397 }
24398}
24399
24400impl RowExt for MultiBufferRow {
24401 fn as_f64(&self) -> f64 {
24402 self.0 as _
24403 }
24404
24405 fn next_row(&self) -> Self {
24406 Self(self.0 + 1)
24407 }
24408
24409 fn previous_row(&self) -> Self {
24410 Self(self.0.saturating_sub(1))
24411 }
24412
24413 fn minus(&self, other: Self) -> u32 {
24414 self.0 - other.0
24415 }
24416}
24417
24418trait RowRangeExt {
24419 type Row;
24420
24421 fn len(&self) -> usize;
24422
24423 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24424}
24425
24426impl RowRangeExt for Range<MultiBufferRow> {
24427 type Row = MultiBufferRow;
24428
24429 fn len(&self) -> usize {
24430 (self.end.0 - self.start.0) as usize
24431 }
24432
24433 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24434 (self.start.0..self.end.0).map(MultiBufferRow)
24435 }
24436}
24437
24438impl RowRangeExt for Range<DisplayRow> {
24439 type Row = DisplayRow;
24440
24441 fn len(&self) -> usize {
24442 (self.end.0 - self.start.0) as usize
24443 }
24444
24445 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24446 (self.start.0..self.end.0).map(DisplayRow)
24447 }
24448}
24449
24450/// If select range has more than one line, we
24451/// just point the cursor to range.start.
24452fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24453 if range.start.row == range.end.row {
24454 range
24455 } else {
24456 range.start..range.start
24457 }
24458}
24459pub struct KillRing(ClipboardItem);
24460impl Global for KillRing {}
24461
24462const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24463
24464enum BreakpointPromptEditAction {
24465 Log,
24466 Condition,
24467 HitCondition,
24468}
24469
24470struct BreakpointPromptEditor {
24471 pub(crate) prompt: Entity<Editor>,
24472 editor: WeakEntity<Editor>,
24473 breakpoint_anchor: Anchor,
24474 breakpoint: Breakpoint,
24475 edit_action: BreakpointPromptEditAction,
24476 block_ids: HashSet<CustomBlockId>,
24477 editor_margins: Arc<Mutex<EditorMargins>>,
24478 _subscriptions: Vec<Subscription>,
24479}
24480
24481impl BreakpointPromptEditor {
24482 const MAX_LINES: u8 = 4;
24483
24484 fn new(
24485 editor: WeakEntity<Editor>,
24486 breakpoint_anchor: Anchor,
24487 breakpoint: Breakpoint,
24488 edit_action: BreakpointPromptEditAction,
24489 window: &mut Window,
24490 cx: &mut Context<Self>,
24491 ) -> Self {
24492 let base_text = match edit_action {
24493 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24494 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24495 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24496 }
24497 .map(|msg| msg.to_string())
24498 .unwrap_or_default();
24499
24500 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24501 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24502
24503 let prompt = cx.new(|cx| {
24504 let mut prompt = Editor::new(
24505 EditorMode::AutoHeight {
24506 min_lines: 1,
24507 max_lines: Some(Self::MAX_LINES as usize),
24508 },
24509 buffer,
24510 None,
24511 window,
24512 cx,
24513 );
24514 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24515 prompt.set_show_cursor_when_unfocused(false, cx);
24516 prompt.set_placeholder_text(
24517 match edit_action {
24518 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24519 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24520 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24521 },
24522 window,
24523 cx,
24524 );
24525
24526 prompt
24527 });
24528
24529 Self {
24530 prompt,
24531 editor,
24532 breakpoint_anchor,
24533 breakpoint,
24534 edit_action,
24535 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24536 block_ids: Default::default(),
24537 _subscriptions: vec![],
24538 }
24539 }
24540
24541 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24542 self.block_ids.extend(block_ids)
24543 }
24544
24545 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24546 if let Some(editor) = self.editor.upgrade() {
24547 let message = self
24548 .prompt
24549 .read(cx)
24550 .buffer
24551 .read(cx)
24552 .as_singleton()
24553 .expect("A multi buffer in breakpoint prompt isn't possible")
24554 .read(cx)
24555 .as_rope()
24556 .to_string();
24557
24558 editor.update(cx, |editor, cx| {
24559 editor.edit_breakpoint_at_anchor(
24560 self.breakpoint_anchor,
24561 self.breakpoint.clone(),
24562 match self.edit_action {
24563 BreakpointPromptEditAction::Log => {
24564 BreakpointEditAction::EditLogMessage(message.into())
24565 }
24566 BreakpointPromptEditAction::Condition => {
24567 BreakpointEditAction::EditCondition(message.into())
24568 }
24569 BreakpointPromptEditAction::HitCondition => {
24570 BreakpointEditAction::EditHitCondition(message.into())
24571 }
24572 },
24573 cx,
24574 );
24575
24576 editor.remove_blocks(self.block_ids.clone(), None, cx);
24577 cx.focus_self(window);
24578 });
24579 }
24580 }
24581
24582 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24583 self.editor
24584 .update(cx, |editor, cx| {
24585 editor.remove_blocks(self.block_ids.clone(), None, cx);
24586 window.focus(&editor.focus_handle);
24587 })
24588 .log_err();
24589 }
24590
24591 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24592 let settings = ThemeSettings::get_global(cx);
24593 let text_style = TextStyle {
24594 color: if self.prompt.read(cx).read_only(cx) {
24595 cx.theme().colors().text_disabled
24596 } else {
24597 cx.theme().colors().text
24598 },
24599 font_family: settings.buffer_font.family.clone(),
24600 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24601 font_size: settings.buffer_font_size(cx).into(),
24602 font_weight: settings.buffer_font.weight,
24603 line_height: relative(settings.buffer_line_height.value()),
24604 ..Default::default()
24605 };
24606 EditorElement::new(
24607 &self.prompt,
24608 EditorStyle {
24609 background: cx.theme().colors().editor_background,
24610 local_player: cx.theme().players().local(),
24611 text: text_style,
24612 ..Default::default()
24613 },
24614 )
24615 }
24616}
24617
24618impl Render for BreakpointPromptEditor {
24619 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24620 let editor_margins = *self.editor_margins.lock();
24621 let gutter_dimensions = editor_margins.gutter;
24622 h_flex()
24623 .key_context("Editor")
24624 .bg(cx.theme().colors().editor_background)
24625 .border_y_1()
24626 .border_color(cx.theme().status().info_border)
24627 .size_full()
24628 .py(window.line_height() / 2.5)
24629 .on_action(cx.listener(Self::confirm))
24630 .on_action(cx.listener(Self::cancel))
24631 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24632 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24633 }
24634}
24635
24636impl Focusable for BreakpointPromptEditor {
24637 fn focus_handle(&self, cx: &App) -> FocusHandle {
24638 self.prompt.focus_handle(cx)
24639 }
24640}
24641
24642fn all_edits_insertions_or_deletions(
24643 edits: &Vec<(Range<Anchor>, String)>,
24644 snapshot: &MultiBufferSnapshot,
24645) -> bool {
24646 let mut all_insertions = true;
24647 let mut all_deletions = true;
24648
24649 for (range, new_text) in edits.iter() {
24650 let range_is_empty = range.to_offset(snapshot).is_empty();
24651 let text_is_empty = new_text.is_empty();
24652
24653 if range_is_empty != text_is_empty {
24654 if range_is_empty {
24655 all_deletions = false;
24656 } else {
24657 all_insertions = false;
24658 }
24659 } else {
24660 return false;
24661 }
24662
24663 if !all_insertions && !all_deletions {
24664 return false;
24665 }
24666 }
24667 all_insertions || all_deletions
24668}
24669
24670struct MissingEditPredictionKeybindingTooltip;
24671
24672impl Render for MissingEditPredictionKeybindingTooltip {
24673 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24674 ui::tooltip_container(cx, |container, cx| {
24675 container
24676 .flex_shrink_0()
24677 .max_w_80()
24678 .min_h(rems_from_px(124.))
24679 .justify_between()
24680 .child(
24681 v_flex()
24682 .flex_1()
24683 .text_ui_sm(cx)
24684 .child(Label::new("Conflict with Accept Keybinding"))
24685 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24686 )
24687 .child(
24688 h_flex()
24689 .pb_1()
24690 .gap_1()
24691 .items_end()
24692 .w_full()
24693 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24694 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
24695 }))
24696 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24697 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24698 })),
24699 )
24700 })
24701 }
24702}
24703
24704#[derive(Debug, Clone, Copy, PartialEq)]
24705pub struct LineHighlight {
24706 pub background: Background,
24707 pub border: Option<gpui::Hsla>,
24708 pub include_gutter: bool,
24709 pub type_id: Option<TypeId>,
24710}
24711
24712struct LineManipulationResult {
24713 pub new_text: String,
24714 pub line_count_before: usize,
24715 pub line_count_after: usize,
24716}
24717
24718fn render_diff_hunk_controls(
24719 row: u32,
24720 status: &DiffHunkStatus,
24721 hunk_range: Range<Anchor>,
24722 is_created_file: bool,
24723 line_height: Pixels,
24724 editor: &Entity<Editor>,
24725 _window: &mut Window,
24726 cx: &mut App,
24727) -> AnyElement {
24728 h_flex()
24729 .h(line_height)
24730 .mr_1()
24731 .gap_1()
24732 .px_0p5()
24733 .pb_1()
24734 .border_x_1()
24735 .border_b_1()
24736 .border_color(cx.theme().colors().border_variant)
24737 .rounded_b_lg()
24738 .bg(cx.theme().colors().editor_background)
24739 .gap_1()
24740 .block_mouse_except_scroll()
24741 .shadow_md()
24742 .child(if status.has_secondary_hunk() {
24743 Button::new(("stage", row as u64), "Stage")
24744 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24745 .tooltip({
24746 let focus_handle = editor.focus_handle(cx);
24747 move |window, cx| {
24748 Tooltip::for_action_in(
24749 "Stage Hunk",
24750 &::git::ToggleStaged,
24751 &focus_handle,
24752 window,
24753 cx,
24754 )
24755 }
24756 })
24757 .on_click({
24758 let editor = editor.clone();
24759 move |_event, _window, cx| {
24760 editor.update(cx, |editor, cx| {
24761 editor.stage_or_unstage_diff_hunks(
24762 true,
24763 vec![hunk_range.start..hunk_range.start],
24764 cx,
24765 );
24766 });
24767 }
24768 })
24769 } else {
24770 Button::new(("unstage", row as u64), "Unstage")
24771 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24772 .tooltip({
24773 let focus_handle = editor.focus_handle(cx);
24774 move |window, cx| {
24775 Tooltip::for_action_in(
24776 "Unstage Hunk",
24777 &::git::ToggleStaged,
24778 &focus_handle,
24779 window,
24780 cx,
24781 )
24782 }
24783 })
24784 .on_click({
24785 let editor = editor.clone();
24786 move |_event, _window, cx| {
24787 editor.update(cx, |editor, cx| {
24788 editor.stage_or_unstage_diff_hunks(
24789 false,
24790 vec![hunk_range.start..hunk_range.start],
24791 cx,
24792 );
24793 });
24794 }
24795 })
24796 })
24797 .child(
24798 Button::new(("restore", row as u64), "Restore")
24799 .tooltip({
24800 let focus_handle = editor.focus_handle(cx);
24801 move |window, cx| {
24802 Tooltip::for_action_in(
24803 "Restore Hunk",
24804 &::git::Restore,
24805 &focus_handle,
24806 window,
24807 cx,
24808 )
24809 }
24810 })
24811 .on_click({
24812 let editor = editor.clone();
24813 move |_event, window, cx| {
24814 editor.update(cx, |editor, cx| {
24815 let snapshot = editor.snapshot(window, cx);
24816 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
24817 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24818 });
24819 }
24820 })
24821 .disabled(is_created_file),
24822 )
24823 .when(
24824 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24825 |el| {
24826 el.child(
24827 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24828 .shape(IconButtonShape::Square)
24829 .icon_size(IconSize::Small)
24830 // .disabled(!has_multiple_hunks)
24831 .tooltip({
24832 let focus_handle = editor.focus_handle(cx);
24833 move |window, cx| {
24834 Tooltip::for_action_in(
24835 "Next Hunk",
24836 &GoToHunk,
24837 &focus_handle,
24838 window,
24839 cx,
24840 )
24841 }
24842 })
24843 .on_click({
24844 let editor = editor.clone();
24845 move |_event, window, cx| {
24846 editor.update(cx, |editor, cx| {
24847 let snapshot = editor.snapshot(window, cx);
24848 let position =
24849 hunk_range.end.to_point(&snapshot.buffer_snapshot());
24850 editor.go_to_hunk_before_or_after_position(
24851 &snapshot,
24852 position,
24853 Direction::Next,
24854 window,
24855 cx,
24856 );
24857 editor.expand_selected_diff_hunks(cx);
24858 });
24859 }
24860 }),
24861 )
24862 .child(
24863 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24864 .shape(IconButtonShape::Square)
24865 .icon_size(IconSize::Small)
24866 // .disabled(!has_multiple_hunks)
24867 .tooltip({
24868 let focus_handle = editor.focus_handle(cx);
24869 move |window, cx| {
24870 Tooltip::for_action_in(
24871 "Previous Hunk",
24872 &GoToPreviousHunk,
24873 &focus_handle,
24874 window,
24875 cx,
24876 )
24877 }
24878 })
24879 .on_click({
24880 let editor = editor.clone();
24881 move |_event, window, cx| {
24882 editor.update(cx, |editor, cx| {
24883 let snapshot = editor.snapshot(window, cx);
24884 let point =
24885 hunk_range.start.to_point(&snapshot.buffer_snapshot());
24886 editor.go_to_hunk_before_or_after_position(
24887 &snapshot,
24888 point,
24889 Direction::Prev,
24890 window,
24891 cx,
24892 );
24893 editor.expand_selected_diff_hunks(cx);
24894 });
24895 }
24896 }),
24897 )
24898 },
24899 )
24900 .into_any_element()
24901}
24902
24903pub fn multibuffer_context_lines(cx: &App) -> u32 {
24904 EditorSettings::try_get(cx)
24905 .map(|settings| settings.excerpt_context_lines)
24906 .unwrap_or(2)
24907 .min(32)
24908}