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//!
11//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
12//!
13//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
14pub mod actions;
15pub mod blink_manager;
16mod bracket_colorization;
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 inlays;
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 rust_analyzer_ext;
37pub mod scroll;
38mod selections_collection;
39mod split;
40pub mod split_editor_view;
41pub mod tasks;
42
43#[cfg(test)]
44mod code_completion_tests;
45#[cfg(test)]
46mod edit_prediction_tests;
47#[cfg(test)]
48mod editor_tests;
49mod signature_help;
50#[cfg(any(test, feature = "test-support"))]
51pub mod test;
52
53pub(crate) use actions::*;
54pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
55pub use edit_prediction_types::Direction;
56pub use editor_settings::{
57 CompletionDetailAlignment, CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings,
58 HideMouseMode, ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
59};
60pub use element::{
61 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
62 render_breadcrumb_text,
63};
64pub use git::blame::BlameRenderer;
65pub use hover_popover::hover_markdown_style;
66pub use inlays::Inlay;
67pub use items::MAX_TAB_TITLE_LEN;
68pub use lsp::CompletionContext;
69pub use lsp_ext::lsp_tasks;
70pub use multi_buffer::{
71 Anchor, AnchorRangeExt, BufferOffset, ExcerptId, ExcerptRange, MBTextSummary, MultiBuffer,
72 MultiBufferOffset, MultiBufferOffsetUtf16, MultiBufferSnapshot, PathKey, RowInfo, ToOffset,
73 ToPoint,
74};
75pub use split::{SplitDiffFeatureFlag, SplittableEditor, ToggleLockedCursors, ToggleSplitDiff};
76pub use split_editor_view::SplitEditorView;
77pub use text::Bias;
78
79use ::git::{Restore, blame::BlameEntry, commit::ParsedCommitMessage, status::FileStatus};
80use aho_corasick::{AhoCorasick, AhoCorasickBuilder, BuildError};
81use anyhow::{Context as _, Result, anyhow, bail};
82use blink_manager::BlinkManager;
83use buffer_diff::DiffHunkStatus;
84use client::{Collaborator, ParticipantIndex, parse_zed_link};
85use clock::ReplicaId;
86use code_context_menus::{
87 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
88 CompletionsMenu, ContextMenuOrigin,
89};
90use collections::{BTreeMap, HashMap, HashSet, VecDeque};
91use convert_case::{Case, Casing};
92use dap::TelemetrySpawnLocation;
93use display_map::*;
94use edit_prediction_types::{
95 EditPredictionDelegate, EditPredictionDelegateHandle, EditPredictionGranularity,
96 SuggestionDisplayType,
97};
98use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
99use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
100use futures::{
101 FutureExt,
102 future::{self, Shared, join},
103};
104use fuzzy::{StringMatch, StringMatchCandidate};
105use git::blame::{GitBlame, GlobalBlameRenderer};
106use gpui::{
107 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
108 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
109 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
110 Focusable, FontId, FontStyle, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
111 MouseButton, MouseDownEvent, MouseMoveEvent, PaintQuad, ParentElement, Pixels, PressureStage,
112 Render, ScrollHandle, SharedString, SharedUri, Size, Stateful, Styled, Subscription, Task,
113 TextRun, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle,
114 UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window, div, point, prelude::*,
115 pulsating_between, px, relative, size,
116};
117use hover_links::{HoverLink, HoveredLinkState, find_file};
118use hover_popover::{HoverState, hide_hover};
119use indent_guides::ActiveIndentGuidesState;
120use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
121use itertools::{Either, Itertools};
122use language::{
123 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
124 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
125 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
126 IndentSize, Language, LanguageName, LanguageRegistry, LanguageScope, OffsetRangeExt,
127 OutlineItem, Point, Runnable, Selection, SelectionGoal, TextObject, TransactionId,
128 TreeSitterOptions, WordsQuery,
129 language_settings::{
130 self, LanguageSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
131 all_language_settings, language_settings,
132 },
133 point_from_lsp, point_to_lsp, text_diff_with_options,
134};
135use linked_editing_ranges::refresh_linked_ranges;
136use lsp::{
137 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
138 LanguageServerId,
139};
140use lsp_colors::LspColorData;
141use markdown::Markdown;
142use mouse_context_menu::MouseContextMenu;
143use movement::TextLayoutDetails;
144use multi_buffer::{
145 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
146};
147use parking_lot::Mutex;
148use persistence::DB;
149use project::{
150 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
151 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
152 InvalidationStrategy, Location, LocationLink, LspAction, PrepareRenameResponse, Project,
153 ProjectItem, ProjectPath, ProjectTransaction, TaskSourceKind,
154 debugger::{
155 breakpoint_store::{
156 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
157 BreakpointStore, BreakpointStoreEvent,
158 },
159 session::{Session, SessionEvent},
160 },
161 git_store::GitStoreEvent,
162 lsp_store::{
163 CacheInlayHints, CompletionDocumentation, FormatTrigger, LspFormatTarget,
164 OpenLspBufferHandle,
165 },
166 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
167};
168use rand::seq::SliceRandom;
169use regex::Regex;
170use rpc::{ErrorCode, ErrorExt, proto::PeerId};
171use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, SharedScrollAnchor};
172use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
173use serde::{Deserialize, Serialize};
174use settings::{
175 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
176 update_settings_file,
177};
178use smallvec::{SmallVec, smallvec};
179use snippet::Snippet;
180use std::{
181 any::{Any, TypeId},
182 borrow::Cow,
183 cell::{OnceCell, RefCell},
184 cmp::{self, Ordering, Reverse},
185 collections::hash_map,
186 iter::{self, Peekable},
187 mem,
188 num::NonZeroU32,
189 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
190 path::{Path, PathBuf},
191 rc::Rc,
192 sync::Arc,
193 time::{Duration, Instant},
194};
195use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
196use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
197use theme::{
198 AccentColors, ActiveTheme, GlobalTheme, PlayerColor, StatusColors, SyntaxTheme, Theme,
199 ThemeSettings, observe_buffer_font_size_adjustment,
200};
201use ui::{
202 Avatar, ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape,
203 IconName, IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
204};
205use ui_input::ErasedEditor;
206use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
207use workspace::{
208 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, NavigationEntry, OpenInTerminal,
209 OpenTerminal, Pane, RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection,
210 TabBarSettings, Toast, ViewId, Workspace, WorkspaceId, WorkspaceSettings,
211 item::{BreadcrumbText, ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
212 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
213 searchable::{CollapseDirection, SearchEvent},
214};
215use zed_actions::editor::{MoveDown, MoveUp};
216
217use crate::{
218 code_context_menus::CompletionsMenuSource,
219 editor_settings::MultiCursorModifier,
220 hover_links::{find_url, find_url_from_range},
221 inlays::{
222 InlineValueCache,
223 inlay_hints::{LspInlayHintData, inlay_hint_settings},
224 },
225 scroll::{ScrollOffset, ScrollPixelOffset},
226 selections_collection::resolve_selections_wrapping_blocks,
227 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
228};
229
230pub const FILE_HEADER_HEIGHT: u32 = 2;
231pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
232const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
233const MAX_LINE_LEN: usize = 1024;
234const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
235const MAX_SELECTION_HISTORY_LEN: usize = 1024;
236pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
237#[doc(hidden)]
238pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
239pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
240
241pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
242pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
243pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
244pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
245
246pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
247pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
248pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
249
250pub type RenderDiffHunkControlsFn = Arc<
251 dyn Fn(
252 u32,
253 &DiffHunkStatus,
254 Range<Anchor>,
255 bool,
256 Pixels,
257 &Entity<Editor>,
258 &mut Window,
259 &mut App,
260 ) -> AnyElement,
261>;
262
263enum ReportEditorEvent {
264 Saved { auto_saved: bool },
265 EditorOpened,
266 Closed,
267}
268
269impl ReportEditorEvent {
270 pub fn event_type(&self) -> &'static str {
271 match self {
272 Self::Saved { .. } => "Editor Saved",
273 Self::EditorOpened => "Editor Opened",
274 Self::Closed => "Editor Closed",
275 }
276 }
277}
278
279pub enum ActiveDebugLine {}
280pub enum DebugStackFrameLine {}
281enum DocumentHighlightRead {}
282enum DocumentHighlightWrite {}
283enum InputComposition {}
284pub enum PendingInput {}
285enum SelectedTextHighlight {}
286
287pub enum ConflictsOuter {}
288pub enum ConflictsOurs {}
289pub enum ConflictsTheirs {}
290pub enum ConflictsOursMarker {}
291pub enum ConflictsTheirsMarker {}
292
293pub struct HunkAddedColor;
294pub struct HunkRemovedColor;
295
296#[derive(Debug, Copy, Clone, PartialEq, Eq)]
297pub enum Navigated {
298 Yes,
299 No,
300}
301
302impl Navigated {
303 pub fn from_bool(yes: bool) -> Navigated {
304 if yes { Navigated::Yes } else { Navigated::No }
305 }
306}
307
308#[derive(Debug, Clone, PartialEq, Eq)]
309enum DisplayDiffHunk {
310 Folded {
311 display_row: DisplayRow,
312 },
313 Unfolded {
314 is_created_file: bool,
315 diff_base_byte_range: Range<usize>,
316 display_row_range: Range<DisplayRow>,
317 multi_buffer_range: Range<Anchor>,
318 status: DiffHunkStatus,
319 word_diffs: Vec<Range<MultiBufferOffset>>,
320 },
321}
322
323pub enum HideMouseCursorOrigin {
324 TypingAction,
325 MovementAction,
326}
327
328pub fn init(cx: &mut App) {
329 cx.set_global(GlobalBlameRenderer(Arc::new(())));
330
331 workspace::register_project_item::<Editor>(cx);
332 workspace::FollowableViewRegistry::register::<Editor>(cx);
333 workspace::register_serializable_item::<Editor>(cx);
334
335 cx.observe_new(
336 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
337 workspace.register_action(Editor::new_file);
338 workspace.register_action(Editor::new_file_split);
339 workspace.register_action(Editor::new_file_vertical);
340 workspace.register_action(Editor::new_file_horizontal);
341 workspace.register_action(Editor::cancel_language_server_work);
342 workspace.register_action(Editor::toggle_focus);
343 },
344 )
345 .detach();
346
347 cx.on_action(move |_: &workspace::NewFile, cx| {
348 let app_state = workspace::AppState::global(cx);
349 if let Some(app_state) = app_state.upgrade() {
350 workspace::open_new(
351 Default::default(),
352 app_state,
353 cx,
354 |workspace, window, cx| {
355 Editor::new_file(workspace, &Default::default(), window, cx)
356 },
357 )
358 .detach();
359 }
360 })
361 .on_action(move |_: &workspace::NewWindow, cx| {
362 let app_state = workspace::AppState::global(cx);
363 if let Some(app_state) = app_state.upgrade() {
364 workspace::open_new(
365 Default::default(),
366 app_state,
367 cx,
368 |workspace, window, cx| {
369 cx.activate(true);
370 Editor::new_file(workspace, &Default::default(), window, cx)
371 },
372 )
373 .detach();
374 }
375 });
376 _ = ui_input::ERASED_EDITOR_FACTORY.set(|window, cx| {
377 Arc::new(ErasedEditorImpl(
378 cx.new(|cx| Editor::single_line(window, cx)),
379 )) as Arc<dyn ErasedEditor>
380 });
381 _ = multi_buffer::EXCERPT_CONTEXT_LINES.set(multibuffer_context_lines);
382}
383
384pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
385 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
386}
387
388pub trait DiagnosticRenderer {
389 fn render_group(
390 &self,
391 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
392 buffer_id: BufferId,
393 snapshot: EditorSnapshot,
394 editor: WeakEntity<Editor>,
395 language_registry: Option<Arc<LanguageRegistry>>,
396 cx: &mut App,
397 ) -> Vec<BlockProperties<Anchor>>;
398
399 fn render_hover(
400 &self,
401 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
402 range: Range<Point>,
403 buffer_id: BufferId,
404 language_registry: Option<Arc<LanguageRegistry>>,
405 cx: &mut App,
406 ) -> Option<Entity<markdown::Markdown>>;
407
408 fn open_link(
409 &self,
410 editor: &mut Editor,
411 link: SharedString,
412 window: &mut Window,
413 cx: &mut Context<Editor>,
414 );
415}
416
417pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
418
419impl GlobalDiagnosticRenderer {
420 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
421 cx.try_global::<Self>().map(|g| g.0.clone())
422 }
423}
424
425impl gpui::Global for GlobalDiagnosticRenderer {}
426pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
427 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
428}
429
430pub struct SearchWithinRange;
431
432trait InvalidationRegion {
433 fn ranges(&self) -> &[Range<Anchor>];
434}
435
436#[derive(Clone, Debug, PartialEq)]
437pub enum SelectPhase {
438 Begin {
439 position: DisplayPoint,
440 add: bool,
441 click_count: usize,
442 },
443 BeginColumnar {
444 position: DisplayPoint,
445 reset: bool,
446 mode: ColumnarMode,
447 goal_column: u32,
448 },
449 Extend {
450 position: DisplayPoint,
451 click_count: usize,
452 },
453 Update {
454 position: DisplayPoint,
455 goal_column: u32,
456 scroll_delta: gpui::Point<f32>,
457 },
458 End,
459}
460
461#[derive(Clone, Debug, PartialEq)]
462pub enum ColumnarMode {
463 FromMouse,
464 FromSelection,
465}
466
467#[derive(Clone, Debug)]
468pub enum SelectMode {
469 Character,
470 Word(Range<Anchor>),
471 Line(Range<Anchor>),
472 All,
473}
474
475#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
476pub enum SizingBehavior {
477 /// The editor will layout itself using `size_full` and will include the vertical
478 /// scroll margin as requested by user settings.
479 #[default]
480 Default,
481 /// The editor will layout itself using `size_full`, but will not have any
482 /// vertical overscroll.
483 ExcludeOverscrollMargin,
484 /// The editor will request a vertical size according to its content and will be
485 /// layouted without a vertical scroll margin.
486 SizeByContent,
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 /// Determines the sizing behavior for this editor
502 sizing_behavior: SizingBehavior,
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 sizing_behavior: SizingBehavior::Default,
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: &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: &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>, Arc<str>)>,
637 /// Predicted cursor position as (anchor, offset_from_anchor).
638 /// The anchor is in multibuffer coordinates; after applying edits,
639 /// resolve the anchor and add the offset to get the final cursor position.
640 cursor_position: Option<(Anchor, usize)>,
641 edit_preview: Option<EditPreview>,
642 display_mode: EditDisplayMode,
643 snapshot: BufferSnapshot,
644 },
645 /// Move to a specific location in the active editor
646 MoveWithin {
647 target: Anchor,
648 snapshot: BufferSnapshot,
649 },
650 /// Move to a specific location in a different editor (not the active one)
651 MoveOutside {
652 target: language::Anchor,
653 snapshot: BufferSnapshot,
654 },
655}
656
657struct EditPredictionState {
658 inlay_ids: Vec<InlayId>,
659 completion: EditPrediction,
660 completion_id: Option<SharedString>,
661 invalidation_range: Option<Range<Anchor>>,
662}
663
664enum EditPredictionSettings {
665 Disabled,
666 Enabled {
667 show_in_menu: bool,
668 preview_requires_modifier: bool,
669 },
670}
671
672enum EditPredictionHighlight {}
673
674#[derive(Debug, Clone)]
675struct InlineDiagnostic {
676 message: SharedString,
677 group_id: usize,
678 is_primary: bool,
679 start: Point,
680 severity: lsp::DiagnosticSeverity,
681}
682
683pub enum MenuEditPredictionsPolicy {
684 Never,
685 ByProvider,
686}
687
688pub enum EditPredictionPreview {
689 /// Modifier is not pressed
690 Inactive { released_too_fast: bool },
691 /// Modifier pressed
692 Active {
693 since: Instant,
694 previous_scroll_position: Option<SharedScrollAnchor>,
695 },
696}
697
698impl EditPredictionPreview {
699 pub fn released_too_fast(&self) -> bool {
700 match self {
701 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
702 EditPredictionPreview::Active { .. } => false,
703 }
704 }
705
706 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<SharedScrollAnchor>) {
707 if let EditPredictionPreview::Active {
708 previous_scroll_position,
709 ..
710 } = self
711 {
712 *previous_scroll_position = scroll_position;
713 }
714 }
715}
716
717pub struct ContextMenuOptions {
718 pub min_entries_visible: usize,
719 pub max_entries_visible: usize,
720 pub placement: Option<ContextMenuPlacement>,
721}
722
723#[derive(Debug, Clone, PartialEq, Eq)]
724pub enum ContextMenuPlacement {
725 Above,
726 Below,
727}
728
729#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
730struct EditorActionId(usize);
731
732impl EditorActionId {
733 pub fn post_inc(&mut self) -> Self {
734 let answer = self.0;
735
736 *self = Self(answer + 1);
737
738 Self(answer)
739 }
740}
741
742// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
743// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
744
745type BackgroundHighlight = (
746 Arc<dyn Fn(&usize, &Theme) -> Hsla + Send + Sync>,
747 Arc<[Range<Anchor>]>,
748);
749type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
750
751#[derive(Default)]
752struct ScrollbarMarkerState {
753 scrollbar_size: Size<Pixels>,
754 dirty: bool,
755 markers: Arc<[PaintQuad]>,
756 pending_refresh: Option<Task<Result<()>>>,
757}
758
759impl ScrollbarMarkerState {
760 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
761 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
762 }
763}
764
765#[derive(Clone, Copy, PartialEq, Eq)]
766pub enum MinimapVisibility {
767 Disabled,
768 Enabled {
769 /// The configuration currently present in the users settings.
770 setting_configuration: bool,
771 /// Whether to override the currently set visibility from the users setting.
772 toggle_override: bool,
773 },
774}
775
776impl MinimapVisibility {
777 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
778 if mode.is_full() {
779 Self::Enabled {
780 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
781 toggle_override: false,
782 }
783 } else {
784 Self::Disabled
785 }
786 }
787
788 fn hidden(&self) -> Self {
789 match *self {
790 Self::Enabled {
791 setting_configuration,
792 ..
793 } => Self::Enabled {
794 setting_configuration,
795 toggle_override: setting_configuration,
796 },
797 Self::Disabled => Self::Disabled,
798 }
799 }
800
801 fn disabled(&self) -> bool {
802 matches!(*self, Self::Disabled)
803 }
804
805 fn settings_visibility(&self) -> bool {
806 match *self {
807 Self::Enabled {
808 setting_configuration,
809 ..
810 } => setting_configuration,
811 _ => false,
812 }
813 }
814
815 fn visible(&self) -> bool {
816 match *self {
817 Self::Enabled {
818 setting_configuration,
819 toggle_override,
820 } => setting_configuration ^ toggle_override,
821 _ => false,
822 }
823 }
824
825 fn toggle_visibility(&self) -> Self {
826 match *self {
827 Self::Enabled {
828 toggle_override,
829 setting_configuration,
830 } => Self::Enabled {
831 setting_configuration,
832 toggle_override: !toggle_override,
833 },
834 Self::Disabled => Self::Disabled,
835 }
836 }
837}
838
839#[derive(Debug, Clone, Copy, PartialEq, Eq)]
840pub enum BufferSerialization {
841 All,
842 NonDirtyBuffers,
843}
844
845impl BufferSerialization {
846 fn new(restore_unsaved_buffers: bool) -> Self {
847 if restore_unsaved_buffers {
848 Self::All
849 } else {
850 Self::NonDirtyBuffers
851 }
852 }
853}
854
855#[derive(Clone, Debug)]
856struct RunnableTasks {
857 templates: Vec<(TaskSourceKind, TaskTemplate)>,
858 offset: multi_buffer::Anchor,
859 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
860 column: u32,
861 // Values of all named captures, including those starting with '_'
862 extra_variables: HashMap<String, String>,
863 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
864 context_range: Range<BufferOffset>,
865}
866
867impl RunnableTasks {
868 fn resolve<'a>(
869 &'a self,
870 cx: &'a task::TaskContext,
871 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
872 self.templates.iter().filter_map(|(kind, template)| {
873 template
874 .resolve_task(&kind.to_id_base(), cx)
875 .map(|task| (kind.clone(), task))
876 })
877 }
878}
879
880#[derive(Clone)]
881pub struct ResolvedTasks {
882 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
883 position: Anchor,
884}
885
886/// Addons allow storing per-editor state in other crates (e.g. Vim)
887pub trait Addon: 'static {
888 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
889
890 fn render_buffer_header_controls(
891 &self,
892 _: &ExcerptInfo,
893 _: &Window,
894 _: &App,
895 ) -> Option<AnyElement> {
896 None
897 }
898
899 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
900 None
901 }
902
903 fn to_any(&self) -> &dyn std::any::Any;
904
905 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
906 None
907 }
908}
909
910struct ChangeLocation {
911 current: Option<Vec<Anchor>>,
912 original: Vec<Anchor>,
913}
914impl ChangeLocation {
915 fn locations(&self) -> &[Anchor] {
916 self.current.as_ref().unwrap_or(&self.original)
917 }
918}
919
920/// A set of caret positions, registered when the editor was edited.
921pub struct ChangeList {
922 changes: Vec<ChangeLocation>,
923 /// Currently "selected" change.
924 position: Option<usize>,
925}
926
927impl ChangeList {
928 pub fn new() -> Self {
929 Self {
930 changes: Vec::new(),
931 position: None,
932 }
933 }
934
935 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
936 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
937 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
938 if self.changes.is_empty() {
939 return None;
940 }
941
942 let prev = self.position.unwrap_or(self.changes.len());
943 let next = if direction == Direction::Prev {
944 prev.saturating_sub(count)
945 } else {
946 (prev + count).min(self.changes.len() - 1)
947 };
948 self.position = Some(next);
949 self.changes.get(next).map(|change| change.locations())
950 }
951
952 /// Adds a new change to the list, resetting the change list position.
953 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
954 self.position.take();
955 if let Some(last) = self.changes.last_mut()
956 && group
957 {
958 last.current = Some(new_positions)
959 } else {
960 self.changes.push(ChangeLocation {
961 original: new_positions,
962 current: None,
963 });
964 }
965 }
966
967 pub fn last(&self) -> Option<&[Anchor]> {
968 self.changes.last().map(|change| change.locations())
969 }
970
971 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
972 self.changes.last().map(|change| change.original.as_slice())
973 }
974
975 pub fn invert_last_group(&mut self) {
976 if let Some(last) = self.changes.last_mut()
977 && let Some(current) = last.current.as_mut()
978 {
979 mem::swap(&mut last.original, current);
980 }
981 }
982}
983
984#[derive(Clone)]
985struct InlineBlamePopoverState {
986 scroll_handle: ScrollHandle,
987 commit_message: Option<ParsedCommitMessage>,
988 markdown: Entity<Markdown>,
989}
990
991struct InlineBlamePopover {
992 position: gpui::Point<Pixels>,
993 hide_task: Option<Task<()>>,
994 popover_bounds: Option<Bounds<Pixels>>,
995 popover_state: InlineBlamePopoverState,
996 keyboard_grace: bool,
997}
998
999enum SelectionDragState {
1000 /// State when no drag related activity is detected.
1001 None,
1002 /// State when the mouse is down on a selection that is about to be dragged.
1003 ReadyToDrag {
1004 selection: Selection<Anchor>,
1005 click_position: gpui::Point<Pixels>,
1006 mouse_down_time: Instant,
1007 },
1008 /// State when the mouse is dragging the selection in the editor.
1009 Dragging {
1010 selection: Selection<Anchor>,
1011 drop_cursor: Selection<Anchor>,
1012 hide_drop_cursor: bool,
1013 },
1014}
1015
1016enum ColumnarSelectionState {
1017 FromMouse {
1018 selection_tail: Anchor,
1019 display_point: Option<DisplayPoint>,
1020 },
1021 FromSelection {
1022 selection_tail: Anchor,
1023 },
1024}
1025
1026/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1027/// a breakpoint on them.
1028#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1029struct PhantomBreakpointIndicator {
1030 display_row: DisplayRow,
1031 /// There's a small debounce between hovering over the line and showing the indicator.
1032 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1033 is_active: bool,
1034 collides_with_existing_breakpoint: bool,
1035}
1036
1037/// Represents a diff review button indicator that shows up when hovering over lines in the gutter
1038/// in diff view mode.
1039#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1040pub(crate) struct PhantomDiffReviewIndicator {
1041 /// The starting anchor of the selection (or the only row if not dragging).
1042 pub start: Anchor,
1043 /// The ending anchor of the selection. Equal to start_anchor for single-line selection.
1044 pub end: Anchor,
1045 /// There's a small debounce between hovering over the line and showing the indicator.
1046 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1047 pub is_active: bool,
1048}
1049
1050#[derive(Clone, Debug)]
1051pub(crate) struct DiffReviewDragState {
1052 pub start_anchor: Anchor,
1053 pub current_anchor: Anchor,
1054}
1055
1056impl DiffReviewDragState {
1057 pub fn row_range(&self, snapshot: &DisplaySnapshot) -> std::ops::RangeInclusive<DisplayRow> {
1058 let start = self.start_anchor.to_display_point(snapshot).row();
1059 let current = self.current_anchor.to_display_point(snapshot).row();
1060
1061 (start..=current).sorted()
1062 }
1063}
1064
1065/// Identifies a specific hunk in the diff buffer.
1066/// Used as a key to group comments by their location.
1067#[derive(Clone, Debug)]
1068pub struct DiffHunkKey {
1069 /// The file path (relative to worktree) this hunk belongs to.
1070 pub file_path: Arc<util::rel_path::RelPath>,
1071 /// An anchor at the start of the hunk. This tracks position as the buffer changes.
1072 pub hunk_start_anchor: Anchor,
1073}
1074
1075/// A review comment stored locally before being sent to the Agent panel.
1076#[derive(Clone)]
1077pub struct StoredReviewComment {
1078 /// Unique identifier for this comment (for edit/delete operations).
1079 pub id: usize,
1080 /// The comment text entered by the user.
1081 pub comment: String,
1082 /// Anchors for the code range being reviewed.
1083 pub range: Range<Anchor>,
1084 /// Timestamp when the comment was created (for chronological ordering).
1085 pub created_at: Instant,
1086 /// Whether this comment is currently being edited inline.
1087 pub is_editing: bool,
1088}
1089
1090impl StoredReviewComment {
1091 pub fn new(id: usize, comment: String, anchor_range: Range<Anchor>) -> Self {
1092 Self {
1093 id,
1094 comment,
1095 range: anchor_range,
1096 created_at: Instant::now(),
1097 is_editing: false,
1098 }
1099 }
1100}
1101
1102/// Represents an active diff review overlay that appears when clicking the "Add Review" button.
1103pub(crate) struct DiffReviewOverlay {
1104 pub anchor_range: Range<Anchor>,
1105 /// The block ID for the overlay.
1106 pub block_id: CustomBlockId,
1107 /// The editor entity for the review input.
1108 pub prompt_editor: Entity<Editor>,
1109 /// The hunk key this overlay belongs to.
1110 pub hunk_key: DiffHunkKey,
1111 /// Whether the comments section is expanded.
1112 pub comments_expanded: bool,
1113 /// Editors for comments currently being edited inline.
1114 /// Key: comment ID, Value: Editor entity for inline editing.
1115 pub inline_edit_editors: HashMap<usize, Entity<Editor>>,
1116 /// Subscriptions for inline edit editors' action handlers.
1117 /// Key: comment ID, Value: Subscription keeping the Newline action handler alive.
1118 pub inline_edit_subscriptions: HashMap<usize, Subscription>,
1119 /// The current user's avatar URI for display in comment rows.
1120 pub user_avatar_uri: Option<SharedUri>,
1121 /// Subscription to keep the action handler alive.
1122 _subscription: Subscription,
1123}
1124
1125/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1126///
1127/// See the [module level documentation](self) for more information.
1128pub struct Editor {
1129 focus_handle: FocusHandle,
1130 last_focused_descendant: Option<WeakFocusHandle>,
1131 /// The text buffer being edited
1132 buffer: Entity<MultiBuffer>,
1133 /// Map of how text in the buffer should be displayed.
1134 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1135 pub display_map: Entity<DisplayMap>,
1136 placeholder_display_map: Option<Entity<DisplayMap>>,
1137 pub selections: SelectionsCollection,
1138 pub scroll_manager: ScrollManager,
1139 /// When inline assist editors are linked, they all render cursors because
1140 /// typing enters text into each of them, even the ones that aren't focused.
1141 pub(crate) show_cursor_when_unfocused: bool,
1142 columnar_selection_state: Option<ColumnarSelectionState>,
1143 add_selections_state: Option<AddSelectionsState>,
1144 select_next_state: Option<SelectNextState>,
1145 select_prev_state: Option<SelectNextState>,
1146 selection_history: SelectionHistory,
1147 defer_selection_effects: bool,
1148 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1149 autoclose_regions: Vec<AutocloseRegion>,
1150 snippet_stack: InvalidationStack<SnippetState>,
1151 select_syntax_node_history: SelectSyntaxNodeHistory,
1152 ime_transaction: Option<TransactionId>,
1153 pub diagnostics_max_severity: DiagnosticSeverity,
1154 active_diagnostics: ActiveDiagnostic,
1155 show_inline_diagnostics: bool,
1156 inline_diagnostics_update: Task<()>,
1157 inline_diagnostics_enabled: bool,
1158 diagnostics_enabled: bool,
1159 word_completions_enabled: bool,
1160 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1161 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1162 hard_wrap: Option<usize>,
1163 project: Option<Entity<Project>>,
1164 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1165 completion_provider: Option<Rc<dyn CompletionProvider>>,
1166 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1167 blink_manager: Entity<BlinkManager>,
1168 show_cursor_names: bool,
1169 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1170 pub show_local_selections: bool,
1171 mode: EditorMode,
1172 show_breadcrumbs: bool,
1173 show_gutter: bool,
1174 show_scrollbars: ScrollbarAxes,
1175 minimap_visibility: MinimapVisibility,
1176 offset_content: bool,
1177 disable_expand_excerpt_buttons: bool,
1178 delegate_expand_excerpts: bool,
1179 delegate_stage_and_restore: bool,
1180 show_line_numbers: Option<bool>,
1181 use_relative_line_numbers: Option<bool>,
1182 show_git_diff_gutter: Option<bool>,
1183 show_code_actions: Option<bool>,
1184 show_runnables: Option<bool>,
1185 show_breakpoints: Option<bool>,
1186 show_diff_review_button: bool,
1187 show_wrap_guides: Option<bool>,
1188 show_indent_guides: Option<bool>,
1189 buffers_with_disabled_indent_guides: HashSet<BufferId>,
1190 highlight_order: usize,
1191 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1192 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1193 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1194 scrollbar_marker_state: ScrollbarMarkerState,
1195 active_indent_guides_state: ActiveIndentGuidesState,
1196 nav_history: Option<ItemNavHistory>,
1197 context_menu: RefCell<Option<CodeContextMenu>>,
1198 context_menu_options: Option<ContextMenuOptions>,
1199 mouse_context_menu: Option<MouseContextMenu>,
1200 completion_tasks: Vec<(CompletionId, Task<()>)>,
1201 inline_blame_popover: Option<InlineBlamePopover>,
1202 inline_blame_popover_show_task: Option<Task<()>>,
1203 signature_help_state: SignatureHelpState,
1204 auto_signature_help: Option<bool>,
1205 find_all_references_task_sources: Vec<Anchor>,
1206 next_completion_id: CompletionId,
1207 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1208 code_actions_task: Option<Task<Result<()>>>,
1209 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1210 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1211 debounced_selection_highlight_complete: bool,
1212 document_highlights_task: Option<Task<()>>,
1213 linked_editing_range_task: Option<Task<Option<()>>>,
1214 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1215 pending_rename: Option<RenameState>,
1216 searchable: bool,
1217 cursor_shape: CursorShape,
1218 /// Whether the cursor is offset one character to the left when something is
1219 /// selected (needed for vim visual mode)
1220 cursor_offset_on_selection: bool,
1221 current_line_highlight: Option<CurrentLineHighlight>,
1222 /// Whether to collapse search match ranges to just their start position.
1223 /// When true, navigating to a match positions the cursor at the match
1224 /// without selecting the matched text.
1225 collapse_matches: bool,
1226 autoindent_mode: Option<AutoindentMode>,
1227 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1228 input_enabled: bool,
1229 use_modal_editing: bool,
1230 read_only: bool,
1231 leader_id: Option<CollaboratorId>,
1232 remote_id: Option<ViewId>,
1233 pub hover_state: HoverState,
1234 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1235 prev_pressure_stage: Option<PressureStage>,
1236 gutter_hovered: bool,
1237 hovered_link_state: Option<HoveredLinkState>,
1238 edit_prediction_provider: Option<RegisteredEditPredictionDelegate>,
1239 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1240 active_edit_prediction: Option<EditPredictionState>,
1241 /// Used to prevent flickering as the user types while the menu is open
1242 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1243 edit_prediction_settings: EditPredictionSettings,
1244 edit_predictions_hidden_for_vim_mode: bool,
1245 show_edit_predictions_override: Option<bool>,
1246 show_completions_on_input_override: Option<bool>,
1247 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1248 edit_prediction_preview: EditPredictionPreview,
1249 edit_prediction_indent_conflict: bool,
1250 edit_prediction_requires_modifier_in_indent_conflict: bool,
1251 next_inlay_id: usize,
1252 next_color_inlay_id: usize,
1253 _subscriptions: Vec<Subscription>,
1254 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1255 gutter_dimensions: GutterDimensions,
1256 style: Option<EditorStyle>,
1257 text_style_refinement: Option<TextStyleRefinement>,
1258 next_editor_action_id: EditorActionId,
1259 editor_actions: Rc<
1260 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1261 >,
1262 use_autoclose: bool,
1263 use_auto_surround: bool,
1264 auto_replace_emoji_shortcode: bool,
1265 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1266 show_git_blame_gutter: bool,
1267 show_git_blame_inline: bool,
1268 show_git_blame_inline_delay_task: Option<Task<()>>,
1269 git_blame_inline_enabled: bool,
1270 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1271 buffer_serialization: Option<BufferSerialization>,
1272 show_selection_menu: Option<bool>,
1273 blame: Option<Entity<GitBlame>>,
1274 blame_subscription: Option<Subscription>,
1275 custom_context_menu: Option<
1276 Box<
1277 dyn 'static
1278 + Fn(
1279 &mut Self,
1280 DisplayPoint,
1281 &mut Window,
1282 &mut Context<Self>,
1283 ) -> Option<Entity<ui::ContextMenu>>,
1284 >,
1285 >,
1286 last_bounds: Option<Bounds<Pixels>>,
1287 last_position_map: Option<Rc<PositionMap>>,
1288 expect_bounds_change: Option<Bounds<Pixels>>,
1289 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1290 tasks_update_task: Option<Task<()>>,
1291 breakpoint_store: Option<Entity<BreakpointStore>>,
1292 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1293 pub(crate) gutter_diff_review_indicator: (Option<PhantomDiffReviewIndicator>, Option<Task<()>>),
1294 pub(crate) diff_review_drag_state: Option<DiffReviewDragState>,
1295 /// Active diff review overlays. Multiple overlays can be open simultaneously
1296 /// when hunks have comments stored.
1297 pub(crate) diff_review_overlays: Vec<DiffReviewOverlay>,
1298 /// Stored review comments grouped by hunk.
1299 /// Uses a Vec instead of HashMap because DiffHunkKey contains an Anchor
1300 /// which doesn't implement Hash/Eq in a way suitable for HashMap keys.
1301 stored_review_comments: Vec<(DiffHunkKey, Vec<StoredReviewComment>)>,
1302 /// Counter for generating unique comment IDs.
1303 next_review_comment_id: usize,
1304 hovered_diff_hunk_row: Option<DisplayRow>,
1305 pull_diagnostics_task: Task<()>,
1306 in_project_search: bool,
1307 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1308 breadcrumb_header: Option<String>,
1309 focused_block: Option<FocusedBlock>,
1310 next_scroll_position: NextScrollCursorCenterTopBottom,
1311 addons: HashMap<TypeId, Box<dyn Addon>>,
1312 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1313 load_diff_task: Option<Shared<Task<()>>>,
1314 /// Whether we are temporarily displaying a diff other than git's
1315 temporary_diff_override: bool,
1316 selection_mark_mode: bool,
1317 toggle_fold_multiple_buffers: Task<()>,
1318 _scroll_cursor_center_top_bottom_task: Task<()>,
1319 serialize_selections: Task<()>,
1320 serialize_folds: Task<()>,
1321 mouse_cursor_hidden: bool,
1322 minimap: Option<Entity<Self>>,
1323 hide_mouse_mode: HideMouseMode,
1324 pub change_list: ChangeList,
1325 inline_value_cache: InlineValueCache,
1326 number_deleted_lines: bool,
1327
1328 selection_drag_state: SelectionDragState,
1329 colors: Option<LspColorData>,
1330 post_scroll_update: Task<()>,
1331 refresh_colors_task: Task<()>,
1332 inlay_hints: Option<LspInlayHintData>,
1333 folding_newlines: Task<()>,
1334 select_next_is_case_sensitive: Option<bool>,
1335 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1336 on_local_selections_changed:
1337 Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
1338 suppress_selection_callback: bool,
1339 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1340 accent_data: Option<AccentData>,
1341 fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
1342}
1343
1344#[derive(Debug, PartialEq)]
1345struct AccentData {
1346 colors: AccentColors,
1347 overrides: Vec<SharedString>,
1348}
1349
1350fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1351 if debounce_ms > 0 {
1352 Some(Duration::from_millis(debounce_ms))
1353 } else {
1354 None
1355 }
1356}
1357
1358#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1359enum NextScrollCursorCenterTopBottom {
1360 #[default]
1361 Center,
1362 Top,
1363 Bottom,
1364}
1365
1366impl NextScrollCursorCenterTopBottom {
1367 fn next(&self) -> Self {
1368 match self {
1369 Self::Center => Self::Top,
1370 Self::Top => Self::Bottom,
1371 Self::Bottom => Self::Center,
1372 }
1373 }
1374}
1375
1376#[derive(Clone)]
1377pub struct EditorSnapshot {
1378 pub mode: EditorMode,
1379 show_gutter: bool,
1380 offset_content: bool,
1381 show_line_numbers: Option<bool>,
1382 number_deleted_lines: bool,
1383 show_git_diff_gutter: Option<bool>,
1384 show_code_actions: Option<bool>,
1385 show_runnables: Option<bool>,
1386 show_breakpoints: Option<bool>,
1387 git_blame_gutter_max_author_length: Option<usize>,
1388 pub display_snapshot: DisplaySnapshot,
1389 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1390 is_focused: bool,
1391 scroll_anchor: SharedScrollAnchor,
1392 ongoing_scroll: OngoingScroll,
1393 current_line_highlight: CurrentLineHighlight,
1394 gutter_hovered: bool,
1395}
1396
1397#[derive(Default, Debug, Clone, Copy)]
1398pub struct GutterDimensions {
1399 pub left_padding: Pixels,
1400 pub right_padding: Pixels,
1401 pub width: Pixels,
1402 pub margin: Pixels,
1403 pub git_blame_entries_width: Option<Pixels>,
1404}
1405
1406impl GutterDimensions {
1407 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1408 Self {
1409 margin: Self::default_gutter_margin(font_id, font_size, cx),
1410 ..Default::default()
1411 }
1412 }
1413
1414 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1415 -cx.text_system().descent(font_id, font_size)
1416 }
1417 /// The full width of the space taken up by the gutter.
1418 pub fn full_width(&self) -> Pixels {
1419 self.margin + self.width
1420 }
1421
1422 /// The width of the space reserved for the fold indicators,
1423 /// use alongside 'justify_end' and `gutter_width` to
1424 /// right align content with the line numbers
1425 pub fn fold_area_width(&self) -> Pixels {
1426 self.margin + self.right_padding
1427 }
1428}
1429
1430struct CharacterDimensions {
1431 em_width: Pixels,
1432 em_advance: Pixels,
1433 line_height: Pixels,
1434}
1435
1436#[derive(Debug)]
1437pub struct RemoteSelection {
1438 pub replica_id: ReplicaId,
1439 pub selection: Selection<Anchor>,
1440 pub cursor_shape: CursorShape,
1441 pub collaborator_id: CollaboratorId,
1442 pub line_mode: bool,
1443 pub user_name: Option<SharedString>,
1444 pub color: PlayerColor,
1445}
1446
1447#[derive(Clone, Debug)]
1448struct SelectionHistoryEntry {
1449 selections: Arc<[Selection<Anchor>]>,
1450 select_next_state: Option<SelectNextState>,
1451 select_prev_state: Option<SelectNextState>,
1452 add_selections_state: Option<AddSelectionsState>,
1453}
1454
1455#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1456enum SelectionHistoryMode {
1457 #[default]
1458 Normal,
1459 Undoing,
1460 Redoing,
1461 Skipping,
1462}
1463
1464#[derive(Clone, PartialEq, Eq, Hash)]
1465struct HoveredCursor {
1466 replica_id: ReplicaId,
1467 selection_id: usize,
1468}
1469
1470#[derive(Debug)]
1471/// SelectionEffects controls the side-effects of updating the selection.
1472///
1473/// The default behaviour does "what you mostly want":
1474/// - it pushes to the nav history if the cursor moved by >10 lines
1475/// - it re-triggers completion requests
1476/// - it scrolls to fit
1477///
1478/// You might want to modify these behaviours. For example when doing a "jump"
1479/// like go to definition, we always want to add to nav history; but when scrolling
1480/// in vim mode we never do.
1481///
1482/// Similarly, you might want to disable scrolling if you don't want the viewport to
1483/// move.
1484#[derive(Clone)]
1485pub struct SelectionEffects {
1486 nav_history: Option<bool>,
1487 completions: bool,
1488 scroll: Option<Autoscroll>,
1489}
1490
1491impl Default for SelectionEffects {
1492 fn default() -> Self {
1493 Self {
1494 nav_history: None,
1495 completions: true,
1496 scroll: Some(Autoscroll::fit()),
1497 }
1498 }
1499}
1500impl SelectionEffects {
1501 pub fn scroll(scroll: Autoscroll) -> Self {
1502 Self {
1503 scroll: Some(scroll),
1504 ..Default::default()
1505 }
1506 }
1507
1508 pub fn no_scroll() -> Self {
1509 Self {
1510 scroll: None,
1511 ..Default::default()
1512 }
1513 }
1514
1515 pub fn completions(self, completions: bool) -> Self {
1516 Self {
1517 completions,
1518 ..self
1519 }
1520 }
1521
1522 pub fn nav_history(self, nav_history: bool) -> Self {
1523 Self {
1524 nav_history: Some(nav_history),
1525 ..self
1526 }
1527 }
1528}
1529
1530struct DeferredSelectionEffectsState {
1531 changed: bool,
1532 effects: SelectionEffects,
1533 old_cursor_position: Anchor,
1534 history_entry: SelectionHistoryEntry,
1535}
1536
1537#[derive(Default)]
1538struct SelectionHistory {
1539 #[allow(clippy::type_complexity)]
1540 selections_by_transaction:
1541 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1542 mode: SelectionHistoryMode,
1543 undo_stack: VecDeque<SelectionHistoryEntry>,
1544 redo_stack: VecDeque<SelectionHistoryEntry>,
1545}
1546
1547impl SelectionHistory {
1548 #[track_caller]
1549 fn insert_transaction(
1550 &mut self,
1551 transaction_id: TransactionId,
1552 selections: Arc<[Selection<Anchor>]>,
1553 ) {
1554 if selections.is_empty() {
1555 log::error!(
1556 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1557 std::panic::Location::caller()
1558 );
1559 return;
1560 }
1561 self.selections_by_transaction
1562 .insert(transaction_id, (selections, None));
1563 }
1564
1565 #[allow(clippy::type_complexity)]
1566 fn transaction(
1567 &self,
1568 transaction_id: TransactionId,
1569 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1570 self.selections_by_transaction.get(&transaction_id)
1571 }
1572
1573 #[allow(clippy::type_complexity)]
1574 fn transaction_mut(
1575 &mut self,
1576 transaction_id: TransactionId,
1577 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1578 self.selections_by_transaction.get_mut(&transaction_id)
1579 }
1580
1581 fn push(&mut self, entry: SelectionHistoryEntry) {
1582 if !entry.selections.is_empty() {
1583 match self.mode {
1584 SelectionHistoryMode::Normal => {
1585 self.push_undo(entry);
1586 self.redo_stack.clear();
1587 }
1588 SelectionHistoryMode::Undoing => self.push_redo(entry),
1589 SelectionHistoryMode::Redoing => self.push_undo(entry),
1590 SelectionHistoryMode::Skipping => {}
1591 }
1592 }
1593 }
1594
1595 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1596 if self
1597 .undo_stack
1598 .back()
1599 .is_none_or(|e| e.selections != entry.selections)
1600 {
1601 self.undo_stack.push_back(entry);
1602 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1603 self.undo_stack.pop_front();
1604 }
1605 }
1606 }
1607
1608 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1609 if self
1610 .redo_stack
1611 .back()
1612 .is_none_or(|e| e.selections != entry.selections)
1613 {
1614 self.redo_stack.push_back(entry);
1615 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1616 self.redo_stack.pop_front();
1617 }
1618 }
1619 }
1620}
1621
1622#[derive(Clone, Copy)]
1623pub struct RowHighlightOptions {
1624 pub autoscroll: bool,
1625 pub include_gutter: bool,
1626}
1627
1628impl Default for RowHighlightOptions {
1629 fn default() -> Self {
1630 Self {
1631 autoscroll: Default::default(),
1632 include_gutter: true,
1633 }
1634 }
1635}
1636
1637struct RowHighlight {
1638 index: usize,
1639 range: Range<Anchor>,
1640 color: Hsla,
1641 options: RowHighlightOptions,
1642 type_id: TypeId,
1643}
1644
1645#[derive(Clone, Debug)]
1646struct AddSelectionsState {
1647 groups: Vec<AddSelectionsGroup>,
1648}
1649
1650#[derive(Clone, Debug)]
1651struct AddSelectionsGroup {
1652 above: bool,
1653 stack: Vec<usize>,
1654}
1655
1656#[derive(Clone)]
1657struct SelectNextState {
1658 query: AhoCorasick,
1659 wordwise: bool,
1660 done: bool,
1661}
1662
1663impl std::fmt::Debug for SelectNextState {
1664 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1665 f.debug_struct(std::any::type_name::<Self>())
1666 .field("wordwise", &self.wordwise)
1667 .field("done", &self.done)
1668 .finish()
1669 }
1670}
1671
1672#[derive(Debug)]
1673struct AutocloseRegion {
1674 selection_id: usize,
1675 range: Range<Anchor>,
1676 pair: BracketPair,
1677}
1678
1679#[derive(Debug)]
1680struct SnippetState {
1681 ranges: Vec<Vec<Range<Anchor>>>,
1682 active_index: usize,
1683 choices: Vec<Option<Vec<String>>>,
1684}
1685
1686#[doc(hidden)]
1687pub struct RenameState {
1688 pub range: Range<Anchor>,
1689 pub old_name: Arc<str>,
1690 pub editor: Entity<Editor>,
1691 block_id: CustomBlockId,
1692}
1693
1694struct InvalidationStack<T>(Vec<T>);
1695
1696struct RegisteredEditPredictionDelegate {
1697 provider: Arc<dyn EditPredictionDelegateHandle>,
1698 _subscription: Subscription,
1699}
1700
1701#[derive(Debug, PartialEq, Eq)]
1702pub struct ActiveDiagnosticGroup {
1703 pub active_range: Range<Anchor>,
1704 pub active_message: String,
1705 pub group_id: usize,
1706 pub blocks: HashSet<CustomBlockId>,
1707}
1708
1709#[derive(Debug, PartialEq, Eq)]
1710
1711pub(crate) enum ActiveDiagnostic {
1712 None,
1713 All,
1714 Group(ActiveDiagnosticGroup),
1715}
1716
1717#[derive(Serialize, Deserialize, Clone, Debug)]
1718pub struct ClipboardSelection {
1719 /// The number of bytes in this selection.
1720 pub len: usize,
1721 /// Whether this was a full-line selection.
1722 pub is_entire_line: bool,
1723 /// The indentation of the first line when this content was originally copied.
1724 pub first_line_indent: u32,
1725 #[serde(default)]
1726 pub file_path: Option<PathBuf>,
1727 #[serde(default)]
1728 pub line_range: Option<RangeInclusive<u32>>,
1729}
1730
1731impl ClipboardSelection {
1732 pub fn for_buffer(
1733 len: usize,
1734 is_entire_line: bool,
1735 range: Range<Point>,
1736 buffer: &MultiBufferSnapshot,
1737 project: Option<&Entity<Project>>,
1738 cx: &App,
1739 ) -> Self {
1740 let first_line_indent = buffer
1741 .indent_size_for_line(MultiBufferRow(range.start.row))
1742 .len;
1743
1744 let file_path = util::maybe!({
1745 let project = project?.read(cx);
1746 let file = buffer.file_at(range.start)?;
1747 let project_path = ProjectPath {
1748 worktree_id: file.worktree_id(cx),
1749 path: file.path().clone(),
1750 };
1751 project.absolute_path(&project_path, cx)
1752 });
1753
1754 let line_range = file_path.as_ref().and_then(|_| {
1755 let (_, start_point, start_excerpt_id) = buffer.point_to_buffer_point(range.start)?;
1756 let (_, end_point, end_excerpt_id) = buffer.point_to_buffer_point(range.end)?;
1757 if start_excerpt_id == end_excerpt_id {
1758 Some(start_point.row..=end_point.row)
1759 } else {
1760 None
1761 }
1762 });
1763
1764 Self {
1765 len,
1766 is_entire_line,
1767 first_line_indent,
1768 file_path,
1769 line_range,
1770 }
1771 }
1772}
1773
1774// selections, scroll behavior, was newest selection reversed
1775type SelectSyntaxNodeHistoryState = (
1776 Box<[Selection<MultiBufferOffset>]>,
1777 SelectSyntaxNodeScrollBehavior,
1778 bool,
1779);
1780
1781#[derive(Default)]
1782struct SelectSyntaxNodeHistory {
1783 stack: Vec<SelectSyntaxNodeHistoryState>,
1784 // disable temporarily to allow changing selections without losing the stack
1785 pub disable_clearing: bool,
1786}
1787
1788impl SelectSyntaxNodeHistory {
1789 pub fn try_clear(&mut self) {
1790 if !self.disable_clearing {
1791 self.stack.clear();
1792 }
1793 }
1794
1795 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1796 self.stack.push(selection);
1797 }
1798
1799 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1800 self.stack.pop()
1801 }
1802}
1803
1804enum SelectSyntaxNodeScrollBehavior {
1805 CursorTop,
1806 FitSelection,
1807 CursorBottom,
1808}
1809
1810#[derive(Debug, Clone, Copy)]
1811pub(crate) struct NavigationData {
1812 cursor_anchor: Anchor,
1813 cursor_position: Point,
1814 scroll_anchor: ScrollAnchor,
1815 scroll_top_row: u32,
1816}
1817
1818#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1819pub enum GotoDefinitionKind {
1820 Symbol,
1821 Declaration,
1822 Type,
1823 Implementation,
1824}
1825
1826pub enum FormatTarget {
1827 Buffers(HashSet<Entity<Buffer>>),
1828 Ranges(Vec<Range<MultiBufferPoint>>),
1829}
1830
1831pub(crate) struct FocusedBlock {
1832 id: BlockId,
1833 focus_handle: WeakFocusHandle,
1834}
1835
1836#[derive(Clone, Debug)]
1837pub enum JumpData {
1838 MultiBufferRow {
1839 row: MultiBufferRow,
1840 line_offset_from_top: u32,
1841 },
1842 MultiBufferPoint {
1843 excerpt_id: ExcerptId,
1844 position: Point,
1845 anchor: text::Anchor,
1846 line_offset_from_top: u32,
1847 },
1848}
1849
1850pub enum MultibufferSelectionMode {
1851 First,
1852 All,
1853}
1854
1855#[derive(Clone, Copy, Debug, Default)]
1856pub struct RewrapOptions {
1857 pub override_language_settings: bool,
1858 pub preserve_existing_whitespace: bool,
1859}
1860
1861impl Editor {
1862 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1863 let buffer = cx.new(|cx| Buffer::local("", cx));
1864 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1865 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1866 }
1867
1868 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1869 let buffer = cx.new(|cx| Buffer::local("", cx));
1870 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1871 Self::new(EditorMode::full(), buffer, None, window, cx)
1872 }
1873
1874 pub fn auto_height(
1875 min_lines: usize,
1876 max_lines: usize,
1877 window: &mut Window,
1878 cx: &mut Context<Self>,
1879 ) -> Self {
1880 let buffer = cx.new(|cx| Buffer::local("", cx));
1881 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1882 Self::new(
1883 EditorMode::AutoHeight {
1884 min_lines,
1885 max_lines: Some(max_lines),
1886 },
1887 buffer,
1888 None,
1889 window,
1890 cx,
1891 )
1892 }
1893
1894 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1895 /// The editor grows as tall as needed to fit its content.
1896 pub fn auto_height_unbounded(
1897 min_lines: usize,
1898 window: &mut Window,
1899 cx: &mut Context<Self>,
1900 ) -> Self {
1901 let buffer = cx.new(|cx| Buffer::local("", cx));
1902 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1903 Self::new(
1904 EditorMode::AutoHeight {
1905 min_lines,
1906 max_lines: None,
1907 },
1908 buffer,
1909 None,
1910 window,
1911 cx,
1912 )
1913 }
1914
1915 pub fn for_buffer(
1916 buffer: Entity<Buffer>,
1917 project: Option<Entity<Project>>,
1918 window: &mut Window,
1919 cx: &mut Context<Self>,
1920 ) -> Self {
1921 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1922 Self::new(EditorMode::full(), buffer, project, window, cx)
1923 }
1924
1925 pub fn for_multibuffer(
1926 buffer: Entity<MultiBuffer>,
1927 project: Option<Entity<Project>>,
1928 window: &mut Window,
1929 cx: &mut Context<Self>,
1930 ) -> Self {
1931 Self::new(EditorMode::full(), buffer, project, window, cx)
1932 }
1933
1934 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1935 let mut clone = Self::new(
1936 self.mode.clone(),
1937 self.buffer.clone(),
1938 self.project.clone(),
1939 window,
1940 cx,
1941 );
1942 let my_snapshot = self.display_map.update(cx, |display_map, cx| {
1943 let snapshot = display_map.snapshot(cx);
1944 clone.display_map.update(cx, |display_map, cx| {
1945 display_map.set_state(&snapshot, cx);
1946 });
1947 snapshot
1948 });
1949 let clone_snapshot = clone.display_map.update(cx, |map, cx| map.snapshot(cx));
1950 clone.folds_did_change(cx);
1951 clone.selections.clone_state(&self.selections);
1952 clone
1953 .scroll_manager
1954 .clone_state(&self.scroll_manager, &my_snapshot, &clone_snapshot, cx);
1955 clone.searchable = self.searchable;
1956 clone.read_only = self.read_only;
1957 clone
1958 }
1959
1960 pub fn new(
1961 mode: EditorMode,
1962 buffer: Entity<MultiBuffer>,
1963 project: Option<Entity<Project>>,
1964 window: &mut Window,
1965 cx: &mut Context<Self>,
1966 ) -> Self {
1967 Editor::new_internal(mode, buffer, project, None, window, cx)
1968 }
1969
1970 pub fn sticky_headers(
1971 &self,
1972 display_snapshot: &DisplaySnapshot,
1973 style: &EditorStyle,
1974 cx: &App,
1975 ) -> Option<Vec<OutlineItem<Anchor>>> {
1976 let multi_buffer = self.buffer().read(cx);
1977 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
1978 let multi_buffer_visible_start = self
1979 .scroll_manager
1980 .native_anchor(display_snapshot, cx)
1981 .anchor
1982 .to_point(&multi_buffer_snapshot);
1983 let max_row = multi_buffer_snapshot.max_point().row;
1984
1985 let start_row = (multi_buffer_visible_start.row).min(max_row);
1986 let end_row = (multi_buffer_visible_start.row + 10).min(max_row);
1987
1988 if let Some((excerpt_id, _, buffer)) = multi_buffer.read(cx).as_singleton() {
1989 let outline_items = buffer
1990 .outline_items_containing(
1991 Point::new(start_row, 0)..Point::new(end_row, 0),
1992 true,
1993 Some(style.syntax.as_ref()),
1994 )
1995 .into_iter()
1996 .map(|outline_item| OutlineItem {
1997 depth: outline_item.depth,
1998 range: Anchor::range_in_buffer(*excerpt_id, outline_item.range),
1999 source_range_for_text: Anchor::range_in_buffer(
2000 *excerpt_id,
2001 outline_item.source_range_for_text,
2002 ),
2003 text: outline_item.text,
2004 highlight_ranges: outline_item.highlight_ranges,
2005 name_ranges: outline_item.name_ranges,
2006 body_range: outline_item
2007 .body_range
2008 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
2009 annotation_range: outline_item
2010 .annotation_range
2011 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
2012 });
2013 return Some(outline_items.collect());
2014 }
2015
2016 None
2017 }
2018
2019 fn new_internal(
2020 mode: EditorMode,
2021 multi_buffer: Entity<MultiBuffer>,
2022 project: Option<Entity<Project>>,
2023 display_map: Option<Entity<DisplayMap>>,
2024 window: &mut Window,
2025 cx: &mut Context<Self>,
2026 ) -> Self {
2027 debug_assert!(
2028 display_map.is_none() || mode.is_minimap(),
2029 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
2030 );
2031
2032 let full_mode = mode.is_full();
2033 let is_minimap = mode.is_minimap();
2034 let diagnostics_max_severity = if full_mode {
2035 EditorSettings::get_global(cx)
2036 .diagnostics_max_severity
2037 .unwrap_or(DiagnosticSeverity::Hint)
2038 } else {
2039 DiagnosticSeverity::Off
2040 };
2041 let style = window.text_style();
2042 let font_size = style.font_size.to_pixels(window.rem_size());
2043 let editor = cx.entity().downgrade();
2044 let fold_placeholder = FoldPlaceholder {
2045 constrain_width: false,
2046 render: Arc::new(move |fold_id, fold_range, cx| {
2047 let editor = editor.clone();
2048 div()
2049 .id(fold_id)
2050 .bg(cx.theme().colors().ghost_element_background)
2051 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
2052 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
2053 .rounded_xs()
2054 .size_full()
2055 .cursor_pointer()
2056 .child("⋯")
2057 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
2058 .on_click(move |_, _window, cx| {
2059 editor
2060 .update(cx, |editor, cx| {
2061 editor.unfold_ranges(
2062 &[fold_range.start..fold_range.end],
2063 true,
2064 false,
2065 cx,
2066 );
2067 cx.stop_propagation();
2068 })
2069 .ok();
2070 })
2071 .into_any()
2072 }),
2073 merge_adjacent: true,
2074 ..FoldPlaceholder::default()
2075 };
2076 let display_map = display_map.unwrap_or_else(|| {
2077 cx.new(|cx| {
2078 DisplayMap::new(
2079 multi_buffer.clone(),
2080 style.font(),
2081 font_size,
2082 None,
2083 FILE_HEADER_HEIGHT,
2084 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2085 fold_placeholder,
2086 diagnostics_max_severity,
2087 cx,
2088 )
2089 })
2090 });
2091
2092 let selections = SelectionsCollection::new();
2093
2094 let blink_manager = cx.new(|cx| {
2095 let mut blink_manager = BlinkManager::new(
2096 CURSOR_BLINK_INTERVAL,
2097 |cx| EditorSettings::get_global(cx).cursor_blink,
2098 cx,
2099 );
2100 if is_minimap {
2101 blink_manager.disable(cx);
2102 }
2103 blink_manager
2104 });
2105
2106 let soft_wrap_mode_override =
2107 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
2108
2109 let mut project_subscriptions = Vec::new();
2110 if full_mode && let Some(project) = project.as_ref() {
2111 project_subscriptions.push(cx.subscribe_in(
2112 project,
2113 window,
2114 |editor, _, event, window, cx| match event {
2115 project::Event::RefreshCodeLens => {
2116 // we always query lens with actions, without storing them, always refreshing them
2117 }
2118 project::Event::RefreshInlayHints {
2119 server_id,
2120 request_id,
2121 } => {
2122 editor.refresh_inlay_hints(
2123 InlayHintRefreshReason::RefreshRequested {
2124 server_id: *server_id,
2125 request_id: *request_id,
2126 },
2127 cx,
2128 );
2129 }
2130 project::Event::LanguageServerRemoved(..) => {
2131 if editor.tasks_update_task.is_none() {
2132 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2133 }
2134 editor.registered_buffers.clear();
2135 editor.register_visible_buffers(cx);
2136 }
2137 project::Event::LanguageServerAdded(..) => {
2138 if editor.tasks_update_task.is_none() {
2139 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2140 }
2141 }
2142 project::Event::SnippetEdit(id, snippet_edits) => {
2143 // todo(lw): Non singletons
2144 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
2145 let snapshot = buffer.read(cx).snapshot();
2146 let focus_handle = editor.focus_handle(cx);
2147 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
2148 for (range, snippet) in snippet_edits {
2149 let buffer_range =
2150 language::range_from_lsp(*range).to_offset(&snapshot);
2151 editor
2152 .insert_snippet(
2153 &[MultiBufferOffset(buffer_range.start)
2154 ..MultiBufferOffset(buffer_range.end)],
2155 snippet.clone(),
2156 window,
2157 cx,
2158 )
2159 .ok();
2160 }
2161 }
2162 }
2163 }
2164 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
2165 let buffer_id = *buffer_id;
2166 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
2167 editor.register_buffer(buffer_id, cx);
2168 editor.update_lsp_data(Some(buffer_id), window, cx);
2169 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
2170 refresh_linked_ranges(editor, window, cx);
2171 editor.refresh_code_actions(window, cx);
2172 editor.refresh_document_highlights(cx);
2173 }
2174 }
2175
2176 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
2177 let Some(workspace) = editor.workspace() else {
2178 return;
2179 };
2180 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2181 else {
2182 return;
2183 };
2184
2185 if active_editor.entity_id() == cx.entity_id() {
2186 let entity_id = cx.entity_id();
2187 workspace.update(cx, |this, cx| {
2188 this.panes_mut()
2189 .iter_mut()
2190 .filter(|pane| pane.entity_id() != entity_id)
2191 .for_each(|p| {
2192 p.update(cx, |pane, _| {
2193 pane.nav_history_mut().rename_item(
2194 entity_id,
2195 project_path.clone(),
2196 abs_path.clone().into(),
2197 );
2198 })
2199 });
2200 });
2201
2202 Self::open_transaction_for_hidden_buffers(
2203 workspace,
2204 transaction.clone(),
2205 "Rename".to_string(),
2206 window,
2207 cx,
2208 );
2209 }
2210 }
2211
2212 project::Event::WorkspaceEditApplied(transaction) => {
2213 let Some(workspace) = editor.workspace() else {
2214 return;
2215 };
2216 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2217 else {
2218 return;
2219 };
2220
2221 if active_editor.entity_id() == cx.entity_id() {
2222 Self::open_transaction_for_hidden_buffers(
2223 workspace,
2224 transaction.clone(),
2225 "LSP Edit".to_string(),
2226 window,
2227 cx,
2228 );
2229 }
2230 }
2231
2232 _ => {}
2233 },
2234 ));
2235 if let Some(task_inventory) = project
2236 .read(cx)
2237 .task_store()
2238 .read(cx)
2239 .task_inventory()
2240 .cloned()
2241 {
2242 project_subscriptions.push(cx.observe_in(
2243 &task_inventory,
2244 window,
2245 |editor, _, window, cx| {
2246 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2247 },
2248 ));
2249 };
2250
2251 project_subscriptions.push(cx.subscribe_in(
2252 &project.read(cx).breakpoint_store(),
2253 window,
2254 |editor, _, event, window, cx| match event {
2255 BreakpointStoreEvent::ClearDebugLines => {
2256 editor.clear_row_highlights::<ActiveDebugLine>();
2257 editor.refresh_inline_values(cx);
2258 }
2259 BreakpointStoreEvent::SetDebugLine => {
2260 if editor.go_to_active_debug_line(window, cx) {
2261 cx.stop_propagation();
2262 }
2263
2264 editor.refresh_inline_values(cx);
2265 }
2266 _ => {}
2267 },
2268 ));
2269 let git_store = project.read(cx).git_store().clone();
2270 let project = project.clone();
2271 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2272 if let GitStoreEvent::RepositoryAdded = event {
2273 this.load_diff_task = Some(
2274 update_uncommitted_diff_for_buffer(
2275 cx.entity(),
2276 &project,
2277 this.buffer.read(cx).all_buffers(),
2278 this.buffer.clone(),
2279 cx,
2280 )
2281 .shared(),
2282 );
2283 }
2284 }));
2285 }
2286
2287 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2288
2289 let inlay_hint_settings =
2290 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2291 let focus_handle = cx.focus_handle();
2292 if !is_minimap {
2293 cx.on_focus(&focus_handle, window, Self::handle_focus)
2294 .detach();
2295 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2296 .detach();
2297 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2298 .detach();
2299 cx.on_blur(&focus_handle, window, Self::handle_blur)
2300 .detach();
2301 cx.observe_pending_input(window, Self::observe_pending_input)
2302 .detach();
2303 }
2304
2305 let show_indent_guides =
2306 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2307 Some(false)
2308 } else {
2309 None
2310 };
2311
2312 let breakpoint_store = match (&mode, project.as_ref()) {
2313 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2314 _ => None,
2315 };
2316
2317 let mut code_action_providers = Vec::new();
2318 let mut load_uncommitted_diff = None;
2319 if let Some(project) = project.clone() {
2320 load_uncommitted_diff = Some(
2321 update_uncommitted_diff_for_buffer(
2322 cx.entity(),
2323 &project,
2324 multi_buffer.read(cx).all_buffers(),
2325 multi_buffer.clone(),
2326 cx,
2327 )
2328 .shared(),
2329 );
2330 code_action_providers.push(Rc::new(project) as Rc<_>);
2331 }
2332
2333 let mut editor = Self {
2334 focus_handle,
2335 show_cursor_when_unfocused: false,
2336 last_focused_descendant: None,
2337 buffer: multi_buffer.clone(),
2338 display_map: display_map.clone(),
2339 placeholder_display_map: None,
2340 selections,
2341 scroll_manager: ScrollManager::new(cx),
2342 columnar_selection_state: None,
2343 add_selections_state: None,
2344 select_next_state: None,
2345 select_prev_state: None,
2346 selection_history: SelectionHistory::default(),
2347 defer_selection_effects: false,
2348 deferred_selection_effects_state: None,
2349 autoclose_regions: Vec::new(),
2350 snippet_stack: InvalidationStack::default(),
2351 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2352 ime_transaction: None,
2353 active_diagnostics: ActiveDiagnostic::None,
2354 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2355 inline_diagnostics_update: Task::ready(()),
2356 inline_diagnostics: Vec::new(),
2357 soft_wrap_mode_override,
2358 diagnostics_max_severity,
2359 hard_wrap: None,
2360 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2361 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2362 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2363 project,
2364 blink_manager: blink_manager.clone(),
2365 show_local_selections: true,
2366 show_scrollbars: ScrollbarAxes {
2367 horizontal: full_mode,
2368 vertical: full_mode,
2369 },
2370 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2371 offset_content: !matches!(mode, EditorMode::SingleLine),
2372 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2373 show_gutter: full_mode,
2374 show_line_numbers: (!full_mode).then_some(false),
2375 use_relative_line_numbers: None,
2376 disable_expand_excerpt_buttons: !full_mode,
2377 delegate_expand_excerpts: false,
2378 delegate_stage_and_restore: false,
2379 show_git_diff_gutter: None,
2380 show_code_actions: None,
2381 show_runnables: None,
2382 show_breakpoints: None,
2383 show_diff_review_button: false,
2384 show_wrap_guides: None,
2385 show_indent_guides,
2386 buffers_with_disabled_indent_guides: HashSet::default(),
2387 highlight_order: 0,
2388 highlighted_rows: HashMap::default(),
2389 background_highlights: HashMap::default(),
2390 gutter_highlights: HashMap::default(),
2391 scrollbar_marker_state: ScrollbarMarkerState::default(),
2392 active_indent_guides_state: ActiveIndentGuidesState::default(),
2393 nav_history: None,
2394 context_menu: RefCell::new(None),
2395 context_menu_options: None,
2396 mouse_context_menu: None,
2397 completion_tasks: Vec::new(),
2398 inline_blame_popover: None,
2399 inline_blame_popover_show_task: None,
2400 signature_help_state: SignatureHelpState::default(),
2401 auto_signature_help: None,
2402 find_all_references_task_sources: Vec::new(),
2403 next_completion_id: 0,
2404 next_inlay_id: 0,
2405 code_action_providers,
2406 available_code_actions: None,
2407 code_actions_task: None,
2408 quick_selection_highlight_task: None,
2409 debounced_selection_highlight_task: None,
2410 debounced_selection_highlight_complete: false,
2411 document_highlights_task: None,
2412 linked_editing_range_task: None,
2413 pending_rename: None,
2414 searchable: !is_minimap,
2415 cursor_shape: EditorSettings::get_global(cx)
2416 .cursor_shape
2417 .unwrap_or_default(),
2418 cursor_offset_on_selection: false,
2419 current_line_highlight: None,
2420 autoindent_mode: Some(AutoindentMode::EachLine),
2421 collapse_matches: false,
2422 workspace: None,
2423 input_enabled: !is_minimap,
2424 use_modal_editing: full_mode,
2425 read_only: is_minimap,
2426 use_autoclose: true,
2427 use_auto_surround: true,
2428 auto_replace_emoji_shortcode: false,
2429 jsx_tag_auto_close_enabled_in_any_buffer: false,
2430 leader_id: None,
2431 remote_id: None,
2432 hover_state: HoverState::default(),
2433 pending_mouse_down: None,
2434 prev_pressure_stage: None,
2435 hovered_link_state: None,
2436 edit_prediction_provider: None,
2437 active_edit_prediction: None,
2438 stale_edit_prediction_in_menu: None,
2439 edit_prediction_preview: EditPredictionPreview::Inactive {
2440 released_too_fast: false,
2441 },
2442 inline_diagnostics_enabled: full_mode,
2443 diagnostics_enabled: full_mode,
2444 word_completions_enabled: full_mode,
2445 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2446 gutter_hovered: false,
2447 pixel_position_of_newest_cursor: None,
2448 last_bounds: None,
2449 last_position_map: None,
2450 expect_bounds_change: None,
2451 gutter_dimensions: GutterDimensions::default(),
2452 style: None,
2453 show_cursor_names: false,
2454 hovered_cursors: HashMap::default(),
2455 next_editor_action_id: EditorActionId::default(),
2456 editor_actions: Rc::default(),
2457 edit_predictions_hidden_for_vim_mode: false,
2458 show_edit_predictions_override: None,
2459 show_completions_on_input_override: None,
2460 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2461 edit_prediction_settings: EditPredictionSettings::Disabled,
2462 edit_prediction_indent_conflict: false,
2463 edit_prediction_requires_modifier_in_indent_conflict: true,
2464 custom_context_menu: None,
2465 show_git_blame_gutter: false,
2466 show_git_blame_inline: false,
2467 show_selection_menu: None,
2468 show_git_blame_inline_delay_task: None,
2469 git_blame_inline_enabled: full_mode
2470 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2471 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2472 buffer_serialization: is_minimap.not().then(|| {
2473 BufferSerialization::new(
2474 ProjectSettings::get_global(cx)
2475 .session
2476 .restore_unsaved_buffers,
2477 )
2478 }),
2479 blame: None,
2480 blame_subscription: None,
2481 tasks: BTreeMap::default(),
2482
2483 breakpoint_store,
2484 gutter_breakpoint_indicator: (None, None),
2485 gutter_diff_review_indicator: (None, None),
2486 diff_review_drag_state: None,
2487 diff_review_overlays: Vec::new(),
2488 stored_review_comments: Vec::new(),
2489 next_review_comment_id: 0,
2490 hovered_diff_hunk_row: None,
2491 _subscriptions: (!is_minimap)
2492 .then(|| {
2493 vec![
2494 cx.observe(&multi_buffer, Self::on_buffer_changed),
2495 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2496 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2497 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2498 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2499 cx.observe_global_in::<GlobalTheme>(window, Self::theme_changed),
2500 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2501 cx.observe_window_activation(window, |editor, window, cx| {
2502 let active = window.is_window_active();
2503 editor.blink_manager.update(cx, |blink_manager, cx| {
2504 if active {
2505 blink_manager.enable(cx);
2506 } else {
2507 blink_manager.disable(cx);
2508 }
2509 });
2510 if active {
2511 editor.show_mouse_cursor(cx);
2512 }
2513 }),
2514 ]
2515 })
2516 .unwrap_or_default(),
2517 tasks_update_task: None,
2518 pull_diagnostics_task: Task::ready(()),
2519 colors: None,
2520 refresh_colors_task: Task::ready(()),
2521 inlay_hints: None,
2522 next_color_inlay_id: 0,
2523 post_scroll_update: Task::ready(()),
2524 linked_edit_ranges: Default::default(),
2525 in_project_search: false,
2526 previous_search_ranges: None,
2527 breadcrumb_header: None,
2528 focused_block: None,
2529 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2530 addons: HashMap::default(),
2531 registered_buffers: HashMap::default(),
2532 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2533 selection_mark_mode: false,
2534 toggle_fold_multiple_buffers: Task::ready(()),
2535 serialize_selections: Task::ready(()),
2536 serialize_folds: Task::ready(()),
2537 text_style_refinement: None,
2538 load_diff_task: load_uncommitted_diff,
2539 temporary_diff_override: false,
2540 mouse_cursor_hidden: false,
2541 minimap: None,
2542 hide_mouse_mode: EditorSettings::get_global(cx)
2543 .hide_mouse
2544 .unwrap_or_default(),
2545 change_list: ChangeList::new(),
2546 mode,
2547 selection_drag_state: SelectionDragState::None,
2548 folding_newlines: Task::ready(()),
2549 lookup_key: None,
2550 select_next_is_case_sensitive: None,
2551 on_local_selections_changed: None,
2552 suppress_selection_callback: false,
2553 applicable_language_settings: HashMap::default(),
2554 accent_data: None,
2555 fetched_tree_sitter_chunks: HashMap::default(),
2556 number_deleted_lines: false,
2557 };
2558
2559 if is_minimap {
2560 return editor;
2561 }
2562
2563 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2564 editor.accent_data = editor.fetch_accent_data(cx);
2565
2566 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2567 editor
2568 ._subscriptions
2569 .push(cx.observe(breakpoints, |_, _, cx| {
2570 cx.notify();
2571 }));
2572 }
2573 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2574 editor._subscriptions.extend(project_subscriptions);
2575
2576 editor._subscriptions.push(cx.subscribe_in(
2577 &cx.entity(),
2578 window,
2579 |editor, _, e: &EditorEvent, window, cx| match e {
2580 EditorEvent::ScrollPositionChanged { local, .. } => {
2581 if *local {
2582 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2583 editor.inline_blame_popover.take();
2584 let snapshot = editor.snapshot(window, cx);
2585 let new_anchor = editor
2586 .scroll_manager
2587 .native_anchor(&snapshot.display_snapshot, cx);
2588 editor.update_restoration_data(cx, move |data| {
2589 data.scroll_position = (
2590 new_anchor.top_row(snapshot.buffer_snapshot()),
2591 new_anchor.offset,
2592 );
2593 });
2594
2595 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2596 cx.background_executor()
2597 .timer(Duration::from_millis(50))
2598 .await;
2599 editor
2600 .update_in(cx, |editor, window, cx| {
2601 editor.register_visible_buffers(cx);
2602 editor.refresh_colors_for_visible_range(None, window, cx);
2603 editor.refresh_inlay_hints(
2604 InlayHintRefreshReason::NewLinesShown,
2605 cx,
2606 );
2607 editor.colorize_brackets(false, cx);
2608 })
2609 .ok();
2610 });
2611 }
2612 }
2613 EditorEvent::Edited { .. } => {
2614 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2615 .map(|vim_mode| vim_mode.0)
2616 .unwrap_or(false);
2617 if !vim_mode {
2618 let display_map = editor.display_snapshot(cx);
2619 let selections = editor.selections.all_adjusted_display(&display_map);
2620 let pop_state = editor
2621 .change_list
2622 .last()
2623 .map(|previous| {
2624 previous.len() == selections.len()
2625 && previous.iter().enumerate().all(|(ix, p)| {
2626 p.to_display_point(&display_map).row()
2627 == selections[ix].head().row()
2628 })
2629 })
2630 .unwrap_or(false);
2631 let new_positions = selections
2632 .into_iter()
2633 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2634 .collect();
2635 editor
2636 .change_list
2637 .push_to_change_list(pop_state, new_positions);
2638 }
2639 }
2640 _ => (),
2641 },
2642 ));
2643
2644 if let Some(dap_store) = editor
2645 .project
2646 .as_ref()
2647 .map(|project| project.read(cx).dap_store())
2648 {
2649 let weak_editor = cx.weak_entity();
2650
2651 editor
2652 ._subscriptions
2653 .push(
2654 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2655 let session_entity = cx.entity();
2656 weak_editor
2657 .update(cx, |editor, cx| {
2658 editor._subscriptions.push(
2659 cx.subscribe(&session_entity, Self::on_debug_session_event),
2660 );
2661 })
2662 .ok();
2663 }),
2664 );
2665
2666 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2667 editor
2668 ._subscriptions
2669 .push(cx.subscribe(&session, Self::on_debug_session_event));
2670 }
2671 }
2672
2673 // skip adding the initial selection to selection history
2674 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2675 editor.end_selection(window, cx);
2676 editor.selection_history.mode = SelectionHistoryMode::Normal;
2677
2678 editor.scroll_manager.show_scrollbars(window, cx);
2679 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2680
2681 if full_mode {
2682 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2683 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2684
2685 if editor.git_blame_inline_enabled {
2686 editor.start_git_blame_inline(false, window, cx);
2687 }
2688
2689 editor.go_to_active_debug_line(window, cx);
2690
2691 editor.minimap =
2692 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2693 editor.colors = Some(LspColorData::new(cx));
2694 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2695
2696 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2697 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2698 }
2699 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2700 }
2701
2702 editor
2703 }
2704
2705 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2706 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2707 }
2708
2709 pub fn deploy_mouse_context_menu(
2710 &mut self,
2711 position: gpui::Point<Pixels>,
2712 context_menu: Entity<ContextMenu>,
2713 window: &mut Window,
2714 cx: &mut Context<Self>,
2715 ) {
2716 self.mouse_context_menu = Some(MouseContextMenu::new(
2717 self,
2718 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2719 context_menu,
2720 window,
2721 cx,
2722 ));
2723 }
2724
2725 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2726 self.mouse_context_menu
2727 .as_ref()
2728 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2729 }
2730
2731 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2732 if self
2733 .selections
2734 .pending_anchor()
2735 .is_some_and(|pending_selection| {
2736 let snapshot = self.buffer().read(cx).snapshot(cx);
2737 pending_selection.range().includes(range, &snapshot)
2738 })
2739 {
2740 return true;
2741 }
2742
2743 self.selections
2744 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2745 .into_iter()
2746 .any(|selection| {
2747 // This is needed to cover a corner case, if we just check for an existing
2748 // selection in the fold range, having a cursor at the start of the fold
2749 // marks it as selected. Non-empty selections don't cause this.
2750 let length = selection.end - selection.start;
2751 length > 0
2752 })
2753 }
2754
2755 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2756 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2757 }
2758
2759 fn key_context_internal(
2760 &self,
2761 has_active_edit_prediction: bool,
2762 window: &mut Window,
2763 cx: &mut App,
2764 ) -> KeyContext {
2765 let mut key_context = KeyContext::new_with_defaults();
2766 key_context.add("Editor");
2767 let mode = match self.mode {
2768 EditorMode::SingleLine => "single_line",
2769 EditorMode::AutoHeight { .. } => "auto_height",
2770 EditorMode::Minimap { .. } => "minimap",
2771 EditorMode::Full { .. } => "full",
2772 };
2773
2774 if EditorSettings::jupyter_enabled(cx) {
2775 key_context.add("jupyter");
2776 }
2777
2778 key_context.set("mode", mode);
2779 if self.pending_rename.is_some() {
2780 key_context.add("renaming");
2781 }
2782
2783 if let Some(snippet_stack) = self.snippet_stack.last() {
2784 key_context.add("in_snippet");
2785
2786 if snippet_stack.active_index > 0 {
2787 key_context.add("has_previous_tabstop");
2788 }
2789
2790 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2791 key_context.add("has_next_tabstop");
2792 }
2793 }
2794
2795 match self.context_menu.borrow().as_ref() {
2796 Some(CodeContextMenu::Completions(menu)) => {
2797 if menu.visible() {
2798 key_context.add("menu");
2799 key_context.add("showing_completions");
2800 }
2801 }
2802 Some(CodeContextMenu::CodeActions(menu)) => {
2803 if menu.visible() {
2804 key_context.add("menu");
2805 key_context.add("showing_code_actions")
2806 }
2807 }
2808 None => {}
2809 }
2810
2811 if self.signature_help_state.has_multiple_signatures() {
2812 key_context.add("showing_signature_help");
2813 }
2814
2815 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2816 if !self.focus_handle(cx).contains_focused(window, cx)
2817 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2818 {
2819 for addon in self.addons.values() {
2820 addon.extend_key_context(&mut key_context, cx)
2821 }
2822 }
2823
2824 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2825 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2826 Some(
2827 file.full_path(cx)
2828 .extension()?
2829 .to_string_lossy()
2830 .to_lowercase(),
2831 )
2832 }) {
2833 key_context.set("extension", extension);
2834 }
2835 } else {
2836 key_context.add("multibuffer");
2837 }
2838
2839 if has_active_edit_prediction {
2840 if self.edit_prediction_in_conflict() {
2841 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2842 } else {
2843 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2844 key_context.add("copilot_suggestion");
2845 }
2846 }
2847
2848 if self.selection_mark_mode {
2849 key_context.add("selection_mode");
2850 }
2851
2852 let disjoint = self.selections.disjoint_anchors();
2853 let snapshot = self.snapshot(window, cx);
2854 let snapshot = snapshot.buffer_snapshot();
2855 if self.mode == EditorMode::SingleLine
2856 && let [selection] = disjoint
2857 && selection.start == selection.end
2858 && selection.end.to_offset(snapshot) == snapshot.len()
2859 {
2860 key_context.add("end_of_input");
2861 }
2862
2863 if self.has_any_expanded_diff_hunks(cx) {
2864 key_context.add("diffs_expanded");
2865 }
2866
2867 key_context
2868 }
2869
2870 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2871 self.last_bounds.as_ref()
2872 }
2873
2874 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2875 if self.mouse_cursor_hidden {
2876 self.mouse_cursor_hidden = false;
2877 cx.notify();
2878 }
2879 }
2880
2881 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2882 let hide_mouse_cursor = match origin {
2883 HideMouseCursorOrigin::TypingAction => {
2884 matches!(
2885 self.hide_mouse_mode,
2886 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2887 )
2888 }
2889 HideMouseCursorOrigin::MovementAction => {
2890 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2891 }
2892 };
2893 if self.mouse_cursor_hidden != hide_mouse_cursor {
2894 self.mouse_cursor_hidden = hide_mouse_cursor;
2895 cx.notify();
2896 }
2897 }
2898
2899 pub fn edit_prediction_in_conflict(&self) -> bool {
2900 if !self.show_edit_predictions_in_menu() {
2901 return false;
2902 }
2903
2904 let showing_completions = self
2905 .context_menu
2906 .borrow()
2907 .as_ref()
2908 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2909
2910 showing_completions
2911 || self.edit_prediction_requires_modifier()
2912 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2913 // bindings to insert tab characters.
2914 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2915 }
2916
2917 pub fn accept_edit_prediction_keybind(
2918 &self,
2919 granularity: EditPredictionGranularity,
2920 window: &mut Window,
2921 cx: &mut App,
2922 ) -> AcceptEditPredictionBinding {
2923 let key_context = self.key_context_internal(true, window, cx);
2924 let in_conflict = self.edit_prediction_in_conflict();
2925
2926 let bindings =
2927 match granularity {
2928 EditPredictionGranularity::Word => window
2929 .bindings_for_action_in_context(&AcceptNextWordEditPrediction, key_context),
2930 EditPredictionGranularity::Line => window
2931 .bindings_for_action_in_context(&AcceptNextLineEditPrediction, key_context),
2932 EditPredictionGranularity::Full => {
2933 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2934 }
2935 };
2936
2937 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2938 !in_conflict
2939 || binding
2940 .keystrokes()
2941 .first()
2942 .is_some_and(|keystroke| keystroke.modifiers().modified())
2943 }))
2944 }
2945
2946 pub fn new_file(
2947 workspace: &mut Workspace,
2948 _: &workspace::NewFile,
2949 window: &mut Window,
2950 cx: &mut Context<Workspace>,
2951 ) {
2952 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2953 "Failed to create buffer",
2954 window,
2955 cx,
2956 |e, _, _| match e.error_code() {
2957 ErrorCode::RemoteUpgradeRequired => Some(format!(
2958 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2959 e.error_tag("required").unwrap_or("the latest version")
2960 )),
2961 _ => None,
2962 },
2963 );
2964 }
2965
2966 pub fn new_in_workspace(
2967 workspace: &mut Workspace,
2968 window: &mut Window,
2969 cx: &mut Context<Workspace>,
2970 ) -> Task<Result<Entity<Editor>>> {
2971 let project = workspace.project().clone();
2972 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
2973
2974 cx.spawn_in(window, async move |workspace, cx| {
2975 let buffer = create.await?;
2976 workspace.update_in(cx, |workspace, window, cx| {
2977 let editor =
2978 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2979 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2980 editor
2981 })
2982 })
2983 }
2984
2985 fn new_file_vertical(
2986 workspace: &mut Workspace,
2987 _: &workspace::NewFileSplitVertical,
2988 window: &mut Window,
2989 cx: &mut Context<Workspace>,
2990 ) {
2991 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2992 }
2993
2994 fn new_file_horizontal(
2995 workspace: &mut Workspace,
2996 _: &workspace::NewFileSplitHorizontal,
2997 window: &mut Window,
2998 cx: &mut Context<Workspace>,
2999 ) {
3000 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
3001 }
3002
3003 fn new_file_split(
3004 workspace: &mut Workspace,
3005 action: &workspace::NewFileSplit,
3006 window: &mut Window,
3007 cx: &mut Context<Workspace>,
3008 ) {
3009 Self::new_file_in_direction(workspace, action.0, window, cx)
3010 }
3011
3012 fn new_file_in_direction(
3013 workspace: &mut Workspace,
3014 direction: SplitDirection,
3015 window: &mut Window,
3016 cx: &mut Context<Workspace>,
3017 ) {
3018 let project = workspace.project().clone();
3019 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3020
3021 cx.spawn_in(window, async move |workspace, cx| {
3022 let buffer = create.await?;
3023 workspace.update_in(cx, move |workspace, window, cx| {
3024 workspace.split_item(
3025 direction,
3026 Box::new(
3027 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
3028 ),
3029 window,
3030 cx,
3031 )
3032 })?;
3033 anyhow::Ok(())
3034 })
3035 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
3036 match e.error_code() {
3037 ErrorCode::RemoteUpgradeRequired => Some(format!(
3038 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
3039 e.error_tag("required").unwrap_or("the latest version")
3040 )),
3041 _ => None,
3042 }
3043 });
3044 }
3045
3046 pub fn leader_id(&self) -> Option<CollaboratorId> {
3047 self.leader_id
3048 }
3049
3050 pub fn buffer(&self) -> &Entity<MultiBuffer> {
3051 &self.buffer
3052 }
3053
3054 pub fn project(&self) -> Option<&Entity<Project>> {
3055 self.project.as_ref()
3056 }
3057
3058 pub fn workspace(&self) -> Option<Entity<Workspace>> {
3059 self.workspace.as_ref()?.0.upgrade()
3060 }
3061
3062 /// Returns the workspace serialization ID if this editor should be serialized.
3063 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
3064 self.workspace
3065 .as_ref()
3066 .filter(|_| self.should_serialize_buffer())
3067 .and_then(|workspace| workspace.1)
3068 }
3069
3070 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
3071 self.buffer().read(cx).title(cx)
3072 }
3073
3074 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
3075 let git_blame_gutter_max_author_length = self
3076 .render_git_blame_gutter(cx)
3077 .then(|| {
3078 if let Some(blame) = self.blame.as_ref() {
3079 let max_author_length =
3080 blame.update(cx, |blame, cx| blame.max_author_length(cx));
3081 Some(max_author_length)
3082 } else {
3083 None
3084 }
3085 })
3086 .flatten();
3087
3088 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3089
3090 EditorSnapshot {
3091 mode: self.mode.clone(),
3092 show_gutter: self.show_gutter,
3093 offset_content: self.offset_content,
3094 show_line_numbers: self.show_line_numbers,
3095 number_deleted_lines: self.number_deleted_lines,
3096 show_git_diff_gutter: self.show_git_diff_gutter,
3097 show_code_actions: self.show_code_actions,
3098 show_runnables: self.show_runnables,
3099 show_breakpoints: self.show_breakpoints,
3100 git_blame_gutter_max_author_length,
3101 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
3102 display_snapshot,
3103 placeholder_display_snapshot: self
3104 .placeholder_display_map
3105 .as_ref()
3106 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
3107 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
3108 is_focused: self.focus_handle.is_focused(window),
3109 current_line_highlight: self
3110 .current_line_highlight
3111 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
3112 gutter_hovered: self.gutter_hovered,
3113 }
3114 }
3115
3116 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
3117 self.buffer.read(cx).language_at(point, cx)
3118 }
3119
3120 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
3121 self.buffer.read(cx).read(cx).file_at(point).cloned()
3122 }
3123
3124 pub fn active_excerpt(
3125 &self,
3126 cx: &App,
3127 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
3128 self.buffer
3129 .read(cx)
3130 .excerpt_containing(self.selections.newest_anchor().head(), cx)
3131 }
3132
3133 pub fn mode(&self) -> &EditorMode {
3134 &self.mode
3135 }
3136
3137 pub fn set_mode(&mut self, mode: EditorMode) {
3138 self.mode = mode;
3139 }
3140
3141 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
3142 self.collaboration_hub.as_deref()
3143 }
3144
3145 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
3146 self.collaboration_hub = Some(hub);
3147 }
3148
3149 pub fn set_in_project_search(&mut self, in_project_search: bool) {
3150 self.in_project_search = in_project_search;
3151 }
3152
3153 pub fn set_custom_context_menu(
3154 &mut self,
3155 f: impl 'static
3156 + Fn(
3157 &mut Self,
3158 DisplayPoint,
3159 &mut Window,
3160 &mut Context<Self>,
3161 ) -> Option<Entity<ui::ContextMenu>>,
3162 ) {
3163 self.custom_context_menu = Some(Box::new(f))
3164 }
3165
3166 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
3167 self.completion_provider = provider;
3168 }
3169
3170 #[cfg(any(test, feature = "test-support"))]
3171 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
3172 self.completion_provider.clone()
3173 }
3174
3175 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3176 self.semantics_provider.clone()
3177 }
3178
3179 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3180 self.semantics_provider = provider;
3181 }
3182
3183 pub fn set_edit_prediction_provider<T>(
3184 &mut self,
3185 provider: Option<Entity<T>>,
3186 window: &mut Window,
3187 cx: &mut Context<Self>,
3188 ) where
3189 T: EditPredictionDelegate,
3190 {
3191 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
3192 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
3193 if this.focus_handle.is_focused(window) {
3194 this.update_visible_edit_prediction(window, cx);
3195 }
3196 }),
3197 provider: Arc::new(provider),
3198 });
3199 self.update_edit_prediction_settings(cx);
3200 self.refresh_edit_prediction(false, false, window, cx);
3201 }
3202
3203 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3204 self.placeholder_display_map
3205 .as_ref()
3206 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3207 }
3208
3209 pub fn set_placeholder_text(
3210 &mut self,
3211 placeholder_text: &str,
3212 window: &mut Window,
3213 cx: &mut Context<Self>,
3214 ) {
3215 let multibuffer = cx
3216 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3217
3218 let style = window.text_style();
3219
3220 self.placeholder_display_map = Some(cx.new(|cx| {
3221 DisplayMap::new(
3222 multibuffer,
3223 style.font(),
3224 style.font_size.to_pixels(window.rem_size()),
3225 None,
3226 FILE_HEADER_HEIGHT,
3227 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3228 Default::default(),
3229 DiagnosticSeverity::Off,
3230 cx,
3231 )
3232 }));
3233 cx.notify();
3234 }
3235
3236 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3237 self.cursor_shape = cursor_shape;
3238
3239 // Disrupt blink for immediate user feedback that the cursor shape has changed
3240 self.blink_manager.update(cx, BlinkManager::show_cursor);
3241
3242 cx.notify();
3243 }
3244
3245 pub fn cursor_shape(&self) -> CursorShape {
3246 self.cursor_shape
3247 }
3248
3249 pub fn set_cursor_offset_on_selection(&mut self, set_cursor_offset_on_selection: bool) {
3250 self.cursor_offset_on_selection = set_cursor_offset_on_selection;
3251 }
3252
3253 pub fn set_current_line_highlight(
3254 &mut self,
3255 current_line_highlight: Option<CurrentLineHighlight>,
3256 ) {
3257 self.current_line_highlight = current_line_highlight;
3258 }
3259
3260 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3261 self.collapse_matches = collapse_matches;
3262 }
3263
3264 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3265 if self.collapse_matches {
3266 return range.start..range.start;
3267 }
3268 range.clone()
3269 }
3270
3271 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3272 self.display_map.read(cx).clip_at_line_ends
3273 }
3274
3275 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3276 if self.display_map.read(cx).clip_at_line_ends != clip {
3277 self.display_map
3278 .update(cx, |map, _| map.clip_at_line_ends = clip);
3279 }
3280 }
3281
3282 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3283 self.input_enabled = input_enabled;
3284 }
3285
3286 pub fn set_edit_predictions_hidden_for_vim_mode(
3287 &mut self,
3288 hidden: bool,
3289 window: &mut Window,
3290 cx: &mut Context<Self>,
3291 ) {
3292 if hidden != self.edit_predictions_hidden_for_vim_mode {
3293 self.edit_predictions_hidden_for_vim_mode = hidden;
3294 if hidden {
3295 self.update_visible_edit_prediction(window, cx);
3296 } else {
3297 self.refresh_edit_prediction(true, false, window, cx);
3298 }
3299 }
3300 }
3301
3302 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3303 self.menu_edit_predictions_policy = value;
3304 }
3305
3306 pub fn set_autoindent(&mut self, autoindent: bool) {
3307 if autoindent {
3308 self.autoindent_mode = Some(AutoindentMode::EachLine);
3309 } else {
3310 self.autoindent_mode = None;
3311 }
3312 }
3313
3314 pub fn capability(&self, cx: &App) -> Capability {
3315 if self.read_only {
3316 Capability::ReadOnly
3317 } else {
3318 self.buffer.read(cx).capability()
3319 }
3320 }
3321
3322 pub fn read_only(&self, cx: &App) -> bool {
3323 self.read_only || self.buffer.read(cx).read_only()
3324 }
3325
3326 pub fn set_read_only(&mut self, read_only: bool) {
3327 self.read_only = read_only;
3328 }
3329
3330 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3331 self.use_autoclose = autoclose;
3332 }
3333
3334 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3335 self.use_auto_surround = auto_surround;
3336 }
3337
3338 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3339 self.auto_replace_emoji_shortcode = auto_replace;
3340 }
3341
3342 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3343 self.buffer_serialization = should_serialize.then(|| {
3344 BufferSerialization::new(
3345 ProjectSettings::get_global(cx)
3346 .session
3347 .restore_unsaved_buffers,
3348 )
3349 })
3350 }
3351
3352 fn should_serialize_buffer(&self) -> bool {
3353 self.buffer_serialization.is_some()
3354 }
3355
3356 pub fn toggle_edit_predictions(
3357 &mut self,
3358 _: &ToggleEditPrediction,
3359 window: &mut Window,
3360 cx: &mut Context<Self>,
3361 ) {
3362 if self.show_edit_predictions_override.is_some() {
3363 self.set_show_edit_predictions(None, window, cx);
3364 } else {
3365 let show_edit_predictions = !self.edit_predictions_enabled();
3366 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3367 }
3368 }
3369
3370 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3371 self.show_completions_on_input_override = show_completions_on_input;
3372 }
3373
3374 pub fn set_show_edit_predictions(
3375 &mut self,
3376 show_edit_predictions: Option<bool>,
3377 window: &mut Window,
3378 cx: &mut Context<Self>,
3379 ) {
3380 self.show_edit_predictions_override = show_edit_predictions;
3381 self.update_edit_prediction_settings(cx);
3382
3383 if let Some(false) = show_edit_predictions {
3384 self.discard_edit_prediction(false, cx);
3385 } else {
3386 self.refresh_edit_prediction(false, true, window, cx);
3387 }
3388 }
3389
3390 fn edit_predictions_disabled_in_scope(
3391 &self,
3392 buffer: &Entity<Buffer>,
3393 buffer_position: language::Anchor,
3394 cx: &App,
3395 ) -> bool {
3396 let snapshot = buffer.read(cx).snapshot();
3397 let settings = snapshot.settings_at(buffer_position, cx);
3398
3399 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3400 return false;
3401 };
3402
3403 scope.override_name().is_some_and(|scope_name| {
3404 settings
3405 .edit_predictions_disabled_in
3406 .iter()
3407 .any(|s| s == scope_name)
3408 })
3409 }
3410
3411 pub fn set_use_modal_editing(&mut self, to: bool) {
3412 self.use_modal_editing = to;
3413 }
3414
3415 pub fn use_modal_editing(&self) -> bool {
3416 self.use_modal_editing
3417 }
3418
3419 fn selections_did_change(
3420 &mut self,
3421 local: bool,
3422 old_cursor_position: &Anchor,
3423 effects: SelectionEffects,
3424 window: &mut Window,
3425 cx: &mut Context<Self>,
3426 ) {
3427 window.invalidate_character_coordinates();
3428
3429 // Copy selections to primary selection buffer
3430 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3431 if local {
3432 let selections = self
3433 .selections
3434 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3435 let buffer_handle = self.buffer.read(cx).read(cx);
3436
3437 let mut text = String::new();
3438 for (index, selection) in selections.iter().enumerate() {
3439 let text_for_selection = buffer_handle
3440 .text_for_range(selection.start..selection.end)
3441 .collect::<String>();
3442
3443 text.push_str(&text_for_selection);
3444 if index != selections.len() - 1 {
3445 text.push('\n');
3446 }
3447 }
3448
3449 if !text.is_empty() {
3450 cx.write_to_primary(ClipboardItem::new_string(text));
3451 }
3452 }
3453
3454 let selection_anchors = self.selections.disjoint_anchors_arc();
3455
3456 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3457 self.buffer.update(cx, |buffer, cx| {
3458 buffer.set_active_selections(
3459 &selection_anchors,
3460 self.selections.line_mode(),
3461 self.cursor_shape,
3462 cx,
3463 )
3464 });
3465 }
3466 let display_map = self
3467 .display_map
3468 .update(cx, |display_map, cx| display_map.snapshot(cx));
3469 let buffer = display_map.buffer_snapshot();
3470 if self.selections.count() == 1 {
3471 self.add_selections_state = None;
3472 }
3473 self.select_next_state = None;
3474 self.select_prev_state = None;
3475 self.select_syntax_node_history.try_clear();
3476 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3477 self.snippet_stack.invalidate(&selection_anchors, buffer);
3478 self.take_rename(false, window, cx);
3479
3480 let newest_selection = self.selections.newest_anchor();
3481 let new_cursor_position = newest_selection.head();
3482 let selection_start = newest_selection.start;
3483
3484 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3485 self.push_to_nav_history(
3486 *old_cursor_position,
3487 Some(new_cursor_position.to_point(buffer)),
3488 false,
3489 effects.nav_history == Some(true),
3490 cx,
3491 );
3492 }
3493
3494 if local {
3495 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3496 self.register_buffer(buffer_id, cx);
3497 }
3498
3499 let mut context_menu = self.context_menu.borrow_mut();
3500 let completion_menu = match context_menu.as_ref() {
3501 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3502 Some(CodeContextMenu::CodeActions(_)) => {
3503 *context_menu = None;
3504 None
3505 }
3506 None => None,
3507 };
3508 let completion_position = completion_menu.map(|menu| menu.initial_position);
3509 drop(context_menu);
3510
3511 if effects.completions
3512 && let Some(completion_position) = completion_position
3513 {
3514 let start_offset = selection_start.to_offset(buffer);
3515 let position_matches = start_offset == completion_position.to_offset(buffer);
3516 let continue_showing = if let Some((snap, ..)) =
3517 buffer.point_to_buffer_offset(completion_position)
3518 && !snap.capability.editable()
3519 {
3520 false
3521 } else if position_matches {
3522 if self.snippet_stack.is_empty() {
3523 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3524 == Some(CharKind::Word)
3525 } else {
3526 // Snippet choices can be shown even when the cursor is in whitespace.
3527 // Dismissing the menu with actions like backspace is handled by
3528 // invalidation regions.
3529 true
3530 }
3531 } else {
3532 false
3533 };
3534
3535 if continue_showing {
3536 self.open_or_update_completions_menu(None, None, false, window, cx);
3537 } else {
3538 self.hide_context_menu(window, cx);
3539 }
3540 }
3541
3542 hide_hover(self, cx);
3543
3544 if old_cursor_position.to_display_point(&display_map).row()
3545 != new_cursor_position.to_display_point(&display_map).row()
3546 {
3547 self.available_code_actions.take();
3548 }
3549 self.refresh_code_actions(window, cx);
3550 self.refresh_document_highlights(cx);
3551 refresh_linked_ranges(self, window, cx);
3552
3553 self.refresh_selected_text_highlights(false, window, cx);
3554 self.refresh_matching_bracket_highlights(window, cx);
3555 self.update_visible_edit_prediction(window, cx);
3556 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3557 self.inline_blame_popover.take();
3558 if self.git_blame_inline_enabled {
3559 self.start_inline_blame_timer(window, cx);
3560 }
3561 }
3562
3563 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3564
3565 if local && !self.suppress_selection_callback {
3566 if let Some(callback) = self.on_local_selections_changed.as_ref() {
3567 let cursor_position = self.selections.newest::<Point>(&display_map).head();
3568 callback(cursor_position, window, cx);
3569 }
3570 }
3571
3572 cx.emit(EditorEvent::SelectionsChanged { local });
3573
3574 let selections = &self.selections.disjoint_anchors_arc();
3575 if selections.len() == 1 {
3576 cx.emit(SearchEvent::ActiveMatchChanged)
3577 }
3578 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3579 let inmemory_selections = selections
3580 .iter()
3581 .map(|s| {
3582 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3583 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3584 })
3585 .collect();
3586 self.update_restoration_data(cx, |data| {
3587 data.selections = inmemory_selections;
3588 });
3589
3590 if WorkspaceSettings::get(None, cx).restore_on_startup
3591 != RestoreOnStartupBehavior::EmptyTab
3592 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3593 {
3594 let snapshot = self.buffer().read(cx).snapshot(cx);
3595 let selections = selections.clone();
3596 let background_executor = cx.background_executor().clone();
3597 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3598 self.serialize_selections = cx.background_spawn(async move {
3599 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3600 let db_selections = selections
3601 .iter()
3602 .map(|selection| {
3603 (
3604 selection.start.to_offset(&snapshot).0,
3605 selection.end.to_offset(&snapshot).0,
3606 )
3607 })
3608 .collect();
3609
3610 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3611 .await
3612 .with_context(|| {
3613 format!(
3614 "persisting editor selections for editor {editor_id}, \
3615 workspace {workspace_id:?}"
3616 )
3617 })
3618 .log_err();
3619 });
3620 }
3621 }
3622
3623 cx.notify();
3624 }
3625
3626 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3627 use text::ToOffset as _;
3628 use text::ToPoint as _;
3629
3630 if self.mode.is_minimap()
3631 || WorkspaceSettings::get(None, cx).restore_on_startup
3632 == RestoreOnStartupBehavior::EmptyTab
3633 {
3634 return;
3635 }
3636
3637 if !self.buffer().read(cx).is_singleton() {
3638 return;
3639 }
3640
3641 let display_snapshot = self
3642 .display_map
3643 .update(cx, |display_map, cx| display_map.snapshot(cx));
3644 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3645 return;
3646 };
3647 let inmemory_folds = display_snapshot
3648 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3649 .map(|fold| {
3650 fold.range.start.text_anchor.to_point(&snapshot)
3651 ..fold.range.end.text_anchor.to_point(&snapshot)
3652 })
3653 .collect();
3654 self.update_restoration_data(cx, |data| {
3655 data.folds = inmemory_folds;
3656 });
3657
3658 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3659 return;
3660 };
3661 let background_executor = cx.background_executor().clone();
3662 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3663 const FINGERPRINT_LEN: usize = 32;
3664 let db_folds = display_snapshot
3665 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3666 .map(|fold| {
3667 let start = fold.range.start.text_anchor.to_offset(&snapshot);
3668 let end = fold.range.end.text_anchor.to_offset(&snapshot);
3669
3670 // Extract fingerprints - content at fold boundaries for validation on restore
3671 // Both fingerprints must be INSIDE the fold to avoid capturing surrounding
3672 // content that might change independently.
3673 // start_fp: first min(32, fold_len) bytes of fold content
3674 // end_fp: last min(32, fold_len) bytes of fold content
3675 // Clip to character boundaries to handle multibyte UTF-8 characters.
3676 let fold_len = end - start;
3677 let start_fp_end = snapshot
3678 .clip_offset(start + std::cmp::min(FINGERPRINT_LEN, fold_len), Bias::Left);
3679 let start_fp: String = snapshot.text_for_range(start..start_fp_end).collect();
3680 let end_fp_start = snapshot
3681 .clip_offset(end.saturating_sub(FINGERPRINT_LEN).max(start), Bias::Right);
3682 let end_fp: String = snapshot.text_for_range(end_fp_start..end).collect();
3683
3684 (start, end, start_fp, end_fp)
3685 })
3686 .collect::<Vec<_>>();
3687 self.serialize_folds = cx.background_spawn(async move {
3688 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3689 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3690 .await
3691 .with_context(|| {
3692 format!(
3693 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3694 )
3695 })
3696 .log_err();
3697 });
3698 }
3699
3700 pub fn sync_selections(
3701 &mut self,
3702 other: Entity<Editor>,
3703 cx: &mut Context<Self>,
3704 ) -> gpui::Subscription {
3705 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3706 if !other_selections.is_empty() {
3707 self.selections
3708 .change_with(&self.display_snapshot(cx), |selections| {
3709 selections.select_anchors(other_selections);
3710 });
3711 }
3712
3713 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3714 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3715 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3716 if other_selections.is_empty() {
3717 return;
3718 }
3719 let snapshot = this.display_snapshot(cx);
3720 this.selections.change_with(&snapshot, |selections| {
3721 selections.select_anchors(other_selections);
3722 });
3723 }
3724 });
3725
3726 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3727 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3728 let these_selections = this.selections.disjoint_anchors().to_vec();
3729 if these_selections.is_empty() {
3730 return;
3731 }
3732 other.update(cx, |other_editor, cx| {
3733 let snapshot = other_editor.display_snapshot(cx);
3734 other_editor
3735 .selections
3736 .change_with(&snapshot, |selections| {
3737 selections.select_anchors(these_selections);
3738 })
3739 });
3740 }
3741 });
3742
3743 Subscription::join(other_subscription, this_subscription)
3744 }
3745
3746 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3747 if self.buffer().read(cx).is_singleton() {
3748 return;
3749 }
3750 let snapshot = self.buffer.read(cx).snapshot(cx);
3751 let buffer_ids: HashSet<BufferId> = self
3752 .selections
3753 .disjoint_anchor_ranges()
3754 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3755 .collect();
3756 for buffer_id in buffer_ids {
3757 self.unfold_buffer(buffer_id, cx);
3758 }
3759 }
3760
3761 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3762 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3763 /// effects of selection change occur at the end of the transaction.
3764 pub fn change_selections<R>(
3765 &mut self,
3766 effects: SelectionEffects,
3767 window: &mut Window,
3768 cx: &mut Context<Self>,
3769 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3770 ) -> R {
3771 let snapshot = self.display_snapshot(cx);
3772 if let Some(state) = &mut self.deferred_selection_effects_state {
3773 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3774 state.effects.completions = effects.completions;
3775 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3776 let (changed, result) = self.selections.change_with(&snapshot, change);
3777 state.changed |= changed;
3778 return result;
3779 }
3780 let mut state = DeferredSelectionEffectsState {
3781 changed: false,
3782 effects,
3783 old_cursor_position: self.selections.newest_anchor().head(),
3784 history_entry: SelectionHistoryEntry {
3785 selections: self.selections.disjoint_anchors_arc(),
3786 select_next_state: self.select_next_state.clone(),
3787 select_prev_state: self.select_prev_state.clone(),
3788 add_selections_state: self.add_selections_state.clone(),
3789 },
3790 };
3791 let (changed, result) = self.selections.change_with(&snapshot, change);
3792 state.changed = state.changed || changed;
3793 if self.defer_selection_effects {
3794 self.deferred_selection_effects_state = Some(state);
3795 } else {
3796 self.apply_selection_effects(state, window, cx);
3797 }
3798 result
3799 }
3800
3801 /// Defers the effects of selection change, so that the effects of multiple calls to
3802 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3803 /// to selection history and the state of popovers based on selection position aren't
3804 /// erroneously updated.
3805 pub fn with_selection_effects_deferred<R>(
3806 &mut self,
3807 window: &mut Window,
3808 cx: &mut Context<Self>,
3809 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3810 ) -> R {
3811 let already_deferred = self.defer_selection_effects;
3812 self.defer_selection_effects = true;
3813 let result = update(self, window, cx);
3814 if !already_deferred {
3815 self.defer_selection_effects = false;
3816 if let Some(state) = self.deferred_selection_effects_state.take() {
3817 self.apply_selection_effects(state, window, cx);
3818 }
3819 }
3820 result
3821 }
3822
3823 fn apply_selection_effects(
3824 &mut self,
3825 state: DeferredSelectionEffectsState,
3826 window: &mut Window,
3827 cx: &mut Context<Self>,
3828 ) {
3829 if state.changed {
3830 self.selection_history.push(state.history_entry);
3831
3832 if let Some(autoscroll) = state.effects.scroll {
3833 self.request_autoscroll(autoscroll, cx);
3834 }
3835
3836 let old_cursor_position = &state.old_cursor_position;
3837
3838 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3839
3840 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3841 self.show_signature_help_auto(window, cx);
3842 }
3843 }
3844 }
3845
3846 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3847 where
3848 I: IntoIterator<Item = (Range<S>, T)>,
3849 S: ToOffset,
3850 T: Into<Arc<str>>,
3851 {
3852 if self.read_only(cx) {
3853 return;
3854 }
3855
3856 self.buffer
3857 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3858 }
3859
3860 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3861 where
3862 I: IntoIterator<Item = (Range<S>, T)>,
3863 S: ToOffset,
3864 T: Into<Arc<str>>,
3865 {
3866 if self.read_only(cx) {
3867 return;
3868 }
3869
3870 self.buffer.update(cx, |buffer, cx| {
3871 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3872 });
3873 }
3874
3875 pub fn edit_with_block_indent<I, S, T>(
3876 &mut self,
3877 edits: I,
3878 original_indent_columns: Vec<Option<u32>>,
3879 cx: &mut Context<Self>,
3880 ) where
3881 I: IntoIterator<Item = (Range<S>, T)>,
3882 S: ToOffset,
3883 T: Into<Arc<str>>,
3884 {
3885 if self.read_only(cx) {
3886 return;
3887 }
3888
3889 self.buffer.update(cx, |buffer, cx| {
3890 buffer.edit(
3891 edits,
3892 Some(AutoindentMode::Block {
3893 original_indent_columns,
3894 }),
3895 cx,
3896 )
3897 });
3898 }
3899
3900 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3901 self.hide_context_menu(window, cx);
3902
3903 match phase {
3904 SelectPhase::Begin {
3905 position,
3906 add,
3907 click_count,
3908 } => self.begin_selection(position, add, click_count, window, cx),
3909 SelectPhase::BeginColumnar {
3910 position,
3911 goal_column,
3912 reset,
3913 mode,
3914 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3915 SelectPhase::Extend {
3916 position,
3917 click_count,
3918 } => self.extend_selection(position, click_count, window, cx),
3919 SelectPhase::Update {
3920 position,
3921 goal_column,
3922 scroll_delta,
3923 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3924 SelectPhase::End => self.end_selection(window, cx),
3925 }
3926 }
3927
3928 fn extend_selection(
3929 &mut self,
3930 position: DisplayPoint,
3931 click_count: usize,
3932 window: &mut Window,
3933 cx: &mut Context<Self>,
3934 ) {
3935 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3936 let tail = self
3937 .selections
3938 .newest::<MultiBufferOffset>(&display_map)
3939 .tail();
3940 let click_count = click_count.max(match self.selections.select_mode() {
3941 SelectMode::Character => 1,
3942 SelectMode::Word(_) => 2,
3943 SelectMode::Line(_) => 3,
3944 SelectMode::All => 4,
3945 });
3946 self.begin_selection(position, false, click_count, window, cx);
3947
3948 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3949
3950 let current_selection = match self.selections.select_mode() {
3951 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3952 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3953 };
3954
3955 let mut pending_selection = self
3956 .selections
3957 .pending_anchor()
3958 .cloned()
3959 .expect("extend_selection not called with pending selection");
3960
3961 if pending_selection
3962 .start
3963 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3964 == Ordering::Greater
3965 {
3966 pending_selection.start = current_selection.start;
3967 }
3968 if pending_selection
3969 .end
3970 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3971 == Ordering::Less
3972 {
3973 pending_selection.end = current_selection.end;
3974 pending_selection.reversed = true;
3975 }
3976
3977 let mut pending_mode = self.selections.pending_mode().unwrap();
3978 match &mut pending_mode {
3979 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3980 _ => {}
3981 }
3982
3983 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3984 SelectionEffects::scroll(Autoscroll::fit())
3985 } else {
3986 SelectionEffects::no_scroll()
3987 };
3988
3989 self.change_selections(effects, window, cx, |s| {
3990 s.set_pending(pending_selection.clone(), pending_mode);
3991 s.set_is_extending(true);
3992 });
3993 }
3994
3995 fn begin_selection(
3996 &mut self,
3997 position: DisplayPoint,
3998 add: bool,
3999 click_count: usize,
4000 window: &mut Window,
4001 cx: &mut Context<Self>,
4002 ) {
4003 if !self.focus_handle.is_focused(window) {
4004 self.last_focused_descendant = None;
4005 window.focus(&self.focus_handle, cx);
4006 }
4007
4008 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4009 let buffer = display_map.buffer_snapshot();
4010 let position = display_map.clip_point(position, Bias::Left);
4011
4012 let start;
4013 let end;
4014 let mode;
4015 let mut auto_scroll;
4016 match click_count {
4017 1 => {
4018 start = buffer.anchor_before(position.to_point(&display_map));
4019 end = start;
4020 mode = SelectMode::Character;
4021 auto_scroll = true;
4022 }
4023 2 => {
4024 let position = display_map
4025 .clip_point(position, Bias::Left)
4026 .to_offset(&display_map, Bias::Left);
4027 let (range, _) = buffer.surrounding_word(position, None);
4028 start = buffer.anchor_before(range.start);
4029 end = buffer.anchor_before(range.end);
4030 mode = SelectMode::Word(start..end);
4031 auto_scroll = true;
4032 }
4033 3 => {
4034 let position = display_map
4035 .clip_point(position, Bias::Left)
4036 .to_point(&display_map);
4037 let line_start = display_map.prev_line_boundary(position).0;
4038 let next_line_start = buffer.clip_point(
4039 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4040 Bias::Left,
4041 );
4042 start = buffer.anchor_before(line_start);
4043 end = buffer.anchor_before(next_line_start);
4044 mode = SelectMode::Line(start..end);
4045 auto_scroll = true;
4046 }
4047 _ => {
4048 start = buffer.anchor_before(MultiBufferOffset(0));
4049 end = buffer.anchor_before(buffer.len());
4050 mode = SelectMode::All;
4051 auto_scroll = false;
4052 }
4053 }
4054 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
4055
4056 let point_to_delete: Option<usize> = {
4057 let selected_points: Vec<Selection<Point>> =
4058 self.selections.disjoint_in_range(start..end, &display_map);
4059
4060 if !add || click_count > 1 {
4061 None
4062 } else if !selected_points.is_empty() {
4063 Some(selected_points[0].id)
4064 } else {
4065 let clicked_point_already_selected =
4066 self.selections.disjoint_anchors().iter().find(|selection| {
4067 selection.start.to_point(buffer) == start.to_point(buffer)
4068 || selection.end.to_point(buffer) == end.to_point(buffer)
4069 });
4070
4071 clicked_point_already_selected.map(|selection| selection.id)
4072 }
4073 };
4074
4075 let selections_count = self.selections.count();
4076 let effects = if auto_scroll {
4077 SelectionEffects::default()
4078 } else {
4079 SelectionEffects::no_scroll()
4080 };
4081
4082 self.change_selections(effects, window, cx, |s| {
4083 if let Some(point_to_delete) = point_to_delete {
4084 s.delete(point_to_delete);
4085
4086 if selections_count == 1 {
4087 s.set_pending_anchor_range(start..end, mode);
4088 }
4089 } else {
4090 if !add {
4091 s.clear_disjoint();
4092 }
4093
4094 s.set_pending_anchor_range(start..end, mode);
4095 }
4096 });
4097 }
4098
4099 fn begin_columnar_selection(
4100 &mut self,
4101 position: DisplayPoint,
4102 goal_column: u32,
4103 reset: bool,
4104 mode: ColumnarMode,
4105 window: &mut Window,
4106 cx: &mut Context<Self>,
4107 ) {
4108 if !self.focus_handle.is_focused(window) {
4109 self.last_focused_descendant = None;
4110 window.focus(&self.focus_handle, cx);
4111 }
4112
4113 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4114
4115 if reset {
4116 let pointer_position = display_map
4117 .buffer_snapshot()
4118 .anchor_before(position.to_point(&display_map));
4119
4120 self.change_selections(
4121 SelectionEffects::scroll(Autoscroll::newest()),
4122 window,
4123 cx,
4124 |s| {
4125 s.clear_disjoint();
4126 s.set_pending_anchor_range(
4127 pointer_position..pointer_position,
4128 SelectMode::Character,
4129 );
4130 },
4131 );
4132 };
4133
4134 let tail = self.selections.newest::<Point>(&display_map).tail();
4135 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
4136 self.columnar_selection_state = match mode {
4137 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
4138 selection_tail: selection_anchor,
4139 display_point: if reset {
4140 if position.column() != goal_column {
4141 Some(DisplayPoint::new(position.row(), goal_column))
4142 } else {
4143 None
4144 }
4145 } else {
4146 None
4147 },
4148 }),
4149 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
4150 selection_tail: selection_anchor,
4151 }),
4152 };
4153
4154 if !reset {
4155 self.select_columns(position, goal_column, &display_map, window, cx);
4156 }
4157 }
4158
4159 fn update_selection(
4160 &mut self,
4161 position: DisplayPoint,
4162 goal_column: u32,
4163 scroll_delta: gpui::Point<f32>,
4164 window: &mut Window,
4165 cx: &mut Context<Self>,
4166 ) {
4167 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4168
4169 if self.columnar_selection_state.is_some() {
4170 self.select_columns(position, goal_column, &display_map, window, cx);
4171 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
4172 let buffer = display_map.buffer_snapshot();
4173 let head;
4174 let tail;
4175 let mode = self.selections.pending_mode().unwrap();
4176 match &mode {
4177 SelectMode::Character => {
4178 head = position.to_point(&display_map);
4179 tail = pending.tail().to_point(buffer);
4180 }
4181 SelectMode::Word(original_range) => {
4182 let offset = display_map
4183 .clip_point(position, Bias::Left)
4184 .to_offset(&display_map, Bias::Left);
4185 let original_range = original_range.to_offset(buffer);
4186
4187 let head_offset = if buffer.is_inside_word(offset, None)
4188 || original_range.contains(&offset)
4189 {
4190 let (word_range, _) = buffer.surrounding_word(offset, None);
4191 if word_range.start < original_range.start {
4192 word_range.start
4193 } else {
4194 word_range.end
4195 }
4196 } else {
4197 offset
4198 };
4199
4200 head = head_offset.to_point(buffer);
4201 if head_offset <= original_range.start {
4202 tail = original_range.end.to_point(buffer);
4203 } else {
4204 tail = original_range.start.to_point(buffer);
4205 }
4206 }
4207 SelectMode::Line(original_range) => {
4208 let original_range = original_range.to_point(display_map.buffer_snapshot());
4209
4210 let position = display_map
4211 .clip_point(position, Bias::Left)
4212 .to_point(&display_map);
4213 let line_start = display_map.prev_line_boundary(position).0;
4214 let next_line_start = buffer.clip_point(
4215 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4216 Bias::Left,
4217 );
4218
4219 if line_start < original_range.start {
4220 head = line_start
4221 } else {
4222 head = next_line_start
4223 }
4224
4225 if head <= original_range.start {
4226 tail = original_range.end;
4227 } else {
4228 tail = original_range.start;
4229 }
4230 }
4231 SelectMode::All => {
4232 return;
4233 }
4234 };
4235
4236 if head < tail {
4237 pending.start = buffer.anchor_before(head);
4238 pending.end = buffer.anchor_before(tail);
4239 pending.reversed = true;
4240 } else {
4241 pending.start = buffer.anchor_before(tail);
4242 pending.end = buffer.anchor_before(head);
4243 pending.reversed = false;
4244 }
4245
4246 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4247 s.set_pending(pending.clone(), mode);
4248 });
4249 } else {
4250 log::error!("update_selection dispatched with no pending selection");
4251 return;
4252 }
4253
4254 self.apply_scroll_delta(scroll_delta, window, cx);
4255 cx.notify();
4256 }
4257
4258 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4259 self.columnar_selection_state.take();
4260 if let Some(pending_mode) = self.selections.pending_mode() {
4261 let selections = self
4262 .selections
4263 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4264 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4265 s.select(selections);
4266 s.clear_pending();
4267 if s.is_extending() {
4268 s.set_is_extending(false);
4269 } else {
4270 s.set_select_mode(pending_mode);
4271 }
4272 });
4273 }
4274 }
4275
4276 fn select_columns(
4277 &mut self,
4278 head: DisplayPoint,
4279 goal_column: u32,
4280 display_map: &DisplaySnapshot,
4281 window: &mut Window,
4282 cx: &mut Context<Self>,
4283 ) {
4284 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4285 return;
4286 };
4287
4288 let tail = match columnar_state {
4289 ColumnarSelectionState::FromMouse {
4290 selection_tail,
4291 display_point,
4292 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4293 ColumnarSelectionState::FromSelection { selection_tail } => {
4294 selection_tail.to_display_point(display_map)
4295 }
4296 };
4297
4298 let start_row = cmp::min(tail.row(), head.row());
4299 let end_row = cmp::max(tail.row(), head.row());
4300 let start_column = cmp::min(tail.column(), goal_column);
4301 let end_column = cmp::max(tail.column(), goal_column);
4302 let reversed = start_column < tail.column();
4303
4304 let selection_ranges = (start_row.0..=end_row.0)
4305 .map(DisplayRow)
4306 .filter_map(|row| {
4307 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4308 || start_column <= display_map.line_len(row))
4309 && !display_map.is_block_line(row)
4310 {
4311 let start = display_map
4312 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4313 .to_point(display_map);
4314 let end = display_map
4315 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4316 .to_point(display_map);
4317 if reversed {
4318 Some(end..start)
4319 } else {
4320 Some(start..end)
4321 }
4322 } else {
4323 None
4324 }
4325 })
4326 .collect::<Vec<_>>();
4327 if selection_ranges.is_empty() {
4328 return;
4329 }
4330
4331 let ranges = match columnar_state {
4332 ColumnarSelectionState::FromMouse { .. } => {
4333 let mut non_empty_ranges = selection_ranges
4334 .iter()
4335 .filter(|selection_range| selection_range.start != selection_range.end)
4336 .peekable();
4337 if non_empty_ranges.peek().is_some() {
4338 non_empty_ranges.cloned().collect()
4339 } else {
4340 selection_ranges
4341 }
4342 }
4343 _ => selection_ranges,
4344 };
4345
4346 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4347 s.select_ranges(ranges);
4348 });
4349 cx.notify();
4350 }
4351
4352 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4353 self.selections
4354 .all_adjusted(snapshot)
4355 .iter()
4356 .any(|selection| !selection.is_empty())
4357 }
4358
4359 pub fn has_pending_nonempty_selection(&self) -> bool {
4360 let pending_nonempty_selection = match self.selections.pending_anchor() {
4361 Some(Selection { start, end, .. }) => start != end,
4362 None => false,
4363 };
4364
4365 pending_nonempty_selection
4366 || (self.columnar_selection_state.is_some()
4367 && self.selections.disjoint_anchors().len() > 1)
4368 }
4369
4370 pub fn has_pending_selection(&self) -> bool {
4371 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4372 }
4373
4374 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4375 self.selection_mark_mode = false;
4376 self.selection_drag_state = SelectionDragState::None;
4377
4378 if self.dismiss_menus_and_popups(true, window, cx) {
4379 cx.notify();
4380 return;
4381 }
4382 if self.clear_expanded_diff_hunks(cx) {
4383 cx.notify();
4384 return;
4385 }
4386 if self.show_git_blame_gutter {
4387 self.show_git_blame_gutter = false;
4388 cx.notify();
4389 return;
4390 }
4391
4392 if self.mode.is_full()
4393 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4394 {
4395 cx.notify();
4396 return;
4397 }
4398
4399 cx.propagate();
4400 }
4401
4402 pub fn dismiss_menus_and_popups(
4403 &mut self,
4404 is_user_requested: bool,
4405 window: &mut Window,
4406 cx: &mut Context<Self>,
4407 ) -> bool {
4408 let mut dismissed = false;
4409
4410 dismissed |= self.take_rename(false, window, cx).is_some();
4411 dismissed |= self.hide_blame_popover(true, cx);
4412 dismissed |= hide_hover(self, cx);
4413 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4414 dismissed |= self.hide_context_menu(window, cx).is_some();
4415 dismissed |= self.mouse_context_menu.take().is_some();
4416 dismissed |= is_user_requested && self.discard_edit_prediction(true, cx);
4417 dismissed |= self.snippet_stack.pop().is_some();
4418 if self.diff_review_drag_state.is_some() {
4419 self.cancel_diff_review_drag(cx);
4420 dismissed = true;
4421 }
4422 if !self.diff_review_overlays.is_empty() {
4423 self.dismiss_all_diff_review_overlays(cx);
4424 dismissed = true;
4425 }
4426
4427 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4428 self.dismiss_diagnostics(cx);
4429 dismissed = true;
4430 }
4431
4432 dismissed
4433 }
4434
4435 fn linked_editing_ranges_for(
4436 &self,
4437 selection: Range<text::Anchor>,
4438 cx: &App,
4439 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4440 if self.linked_edit_ranges.is_empty() {
4441 return None;
4442 }
4443 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4444 selection.end.buffer_id.and_then(|end_buffer_id| {
4445 if selection.start.buffer_id != Some(end_buffer_id) {
4446 return None;
4447 }
4448 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4449 let snapshot = buffer.read(cx).snapshot();
4450 self.linked_edit_ranges
4451 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4452 .map(|ranges| (ranges, snapshot, buffer))
4453 })?;
4454 use text::ToOffset as TO;
4455 // find offset from the start of current range to current cursor position
4456 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4457
4458 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4459 let start_difference = start_offset - start_byte_offset;
4460 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4461 let end_difference = end_offset - start_byte_offset;
4462 // Current range has associated linked ranges.
4463 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4464 for range in linked_ranges.iter() {
4465 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4466 let end_offset = start_offset + end_difference;
4467 let start_offset = start_offset + start_difference;
4468 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4469 continue;
4470 }
4471 if self.selections.disjoint_anchor_ranges().any(|s| {
4472 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4473 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4474 {
4475 return false;
4476 }
4477 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4478 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4479 }) {
4480 continue;
4481 }
4482 let start = buffer_snapshot.anchor_after(start_offset);
4483 let end = buffer_snapshot.anchor_after(end_offset);
4484 linked_edits
4485 .entry(buffer.clone())
4486 .or_default()
4487 .push(start..end);
4488 }
4489 Some(linked_edits)
4490 }
4491
4492 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4493 let text: Arc<str> = text.into();
4494
4495 if self.read_only(cx) {
4496 return;
4497 }
4498
4499 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4500
4501 self.unfold_buffers_with_selections(cx);
4502
4503 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4504 let mut bracket_inserted = false;
4505 let mut edits = Vec::new();
4506 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4507 let mut new_selections = Vec::with_capacity(selections.len());
4508 let mut new_autoclose_regions = Vec::new();
4509 let snapshot = self.buffer.read(cx).read(cx);
4510 let mut clear_linked_edit_ranges = false;
4511 let mut all_selections_read_only = true;
4512 let mut has_adjacent_edits = false;
4513 let mut in_adjacent_group = false;
4514
4515 let mut regions = self
4516 .selections_with_autoclose_regions(selections, &snapshot)
4517 .peekable();
4518
4519 while let Some((selection, autoclose_region)) = regions.next() {
4520 if snapshot
4521 .point_to_buffer_point(selection.head())
4522 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4523 {
4524 continue;
4525 }
4526 if snapshot
4527 .point_to_buffer_point(selection.tail())
4528 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4529 {
4530 // note, ideally we'd clip the tail to the closest writeable region towards the head
4531 continue;
4532 }
4533 all_selections_read_only = false;
4534
4535 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4536 // Determine if the inserted text matches the opening or closing
4537 // bracket of any of this language's bracket pairs.
4538 let mut bracket_pair = None;
4539 let mut is_bracket_pair_start = false;
4540 let mut is_bracket_pair_end = false;
4541 if !text.is_empty() {
4542 let mut bracket_pair_matching_end = None;
4543 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4544 // and they are removing the character that triggered IME popup.
4545 for (pair, enabled) in scope.brackets() {
4546 if !pair.close && !pair.surround {
4547 continue;
4548 }
4549
4550 if enabled && pair.start.ends_with(text.as_ref()) {
4551 let prefix_len = pair.start.len() - text.len();
4552 let preceding_text_matches_prefix = prefix_len == 0
4553 || (selection.start.column >= (prefix_len as u32)
4554 && snapshot.contains_str_at(
4555 Point::new(
4556 selection.start.row,
4557 selection.start.column - (prefix_len as u32),
4558 ),
4559 &pair.start[..prefix_len],
4560 ));
4561 if preceding_text_matches_prefix {
4562 bracket_pair = Some(pair.clone());
4563 is_bracket_pair_start = true;
4564 break;
4565 }
4566 }
4567 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4568 {
4569 // take first bracket pair matching end, but don't break in case a later bracket
4570 // pair matches start
4571 bracket_pair_matching_end = Some(pair.clone());
4572 }
4573 }
4574 if let Some(end) = bracket_pair_matching_end
4575 && bracket_pair.is_none()
4576 {
4577 bracket_pair = Some(end);
4578 is_bracket_pair_end = true;
4579 }
4580 }
4581
4582 if let Some(bracket_pair) = bracket_pair {
4583 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4584 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4585 let auto_surround =
4586 self.use_auto_surround && snapshot_settings.use_auto_surround;
4587 if selection.is_empty() {
4588 if is_bracket_pair_start {
4589 // If the inserted text is a suffix of an opening bracket and the
4590 // selection is preceded by the rest of the opening bracket, then
4591 // insert the closing bracket.
4592 let following_text_allows_autoclose = snapshot
4593 .chars_at(selection.start)
4594 .next()
4595 .is_none_or(|c| scope.should_autoclose_before(c));
4596
4597 let preceding_text_allows_autoclose = selection.start.column == 0
4598 || snapshot
4599 .reversed_chars_at(selection.start)
4600 .next()
4601 .is_none_or(|c| {
4602 bracket_pair.start != bracket_pair.end
4603 || !snapshot
4604 .char_classifier_at(selection.start)
4605 .is_word(c)
4606 });
4607
4608 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4609 && bracket_pair.start.len() == 1
4610 {
4611 let target = bracket_pair.start.chars().next().unwrap();
4612 let mut byte_offset = 0u32;
4613 let current_line_count = snapshot
4614 .reversed_chars_at(selection.start)
4615 .take_while(|&c| c != '\n')
4616 .filter(|c| {
4617 byte_offset += c.len_utf8() as u32;
4618 if *c != target {
4619 return false;
4620 }
4621
4622 let point = Point::new(
4623 selection.start.row,
4624 selection.start.column.saturating_sub(byte_offset),
4625 );
4626
4627 let is_enabled = snapshot
4628 .language_scope_at(point)
4629 .and_then(|scope| {
4630 scope
4631 .brackets()
4632 .find(|(pair, _)| {
4633 pair.start == bracket_pair.start
4634 })
4635 .map(|(_, enabled)| enabled)
4636 })
4637 .unwrap_or(true);
4638
4639 let is_delimiter = snapshot
4640 .language_scope_at(Point::new(
4641 point.row,
4642 point.column + 1,
4643 ))
4644 .and_then(|scope| {
4645 scope
4646 .brackets()
4647 .find(|(pair, _)| {
4648 pair.start == bracket_pair.start
4649 })
4650 .map(|(_, enabled)| !enabled)
4651 })
4652 .unwrap_or(false);
4653
4654 is_enabled && !is_delimiter
4655 })
4656 .count();
4657 current_line_count % 2 == 1
4658 } else {
4659 false
4660 };
4661
4662 if autoclose
4663 && bracket_pair.close
4664 && following_text_allows_autoclose
4665 && preceding_text_allows_autoclose
4666 && !is_closing_quote
4667 {
4668 let anchor = snapshot.anchor_before(selection.end);
4669 new_selections.push((selection.map(|_| anchor), text.len()));
4670 new_autoclose_regions.push((
4671 anchor,
4672 text.len(),
4673 selection.id,
4674 bracket_pair.clone(),
4675 ));
4676 edits.push((
4677 selection.range(),
4678 format!("{}{}", text, bracket_pair.end).into(),
4679 ));
4680 bracket_inserted = true;
4681 continue;
4682 }
4683 }
4684
4685 if let Some(region) = autoclose_region {
4686 // If the selection is followed by an auto-inserted closing bracket,
4687 // then don't insert that closing bracket again; just move the selection
4688 // past the closing bracket.
4689 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4690 && text.as_ref() == region.pair.end.as_str()
4691 && snapshot.contains_str_at(region.range.end, text.as_ref());
4692 if should_skip {
4693 let anchor = snapshot.anchor_after(selection.end);
4694 new_selections
4695 .push((selection.map(|_| anchor), region.pair.end.len()));
4696 continue;
4697 }
4698 }
4699
4700 let always_treat_brackets_as_autoclosed = snapshot
4701 .language_settings_at(selection.start, cx)
4702 .always_treat_brackets_as_autoclosed;
4703 if always_treat_brackets_as_autoclosed
4704 && is_bracket_pair_end
4705 && snapshot.contains_str_at(selection.end, text.as_ref())
4706 {
4707 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4708 // and the inserted text is a closing bracket and the selection is followed
4709 // by the closing bracket then move the selection past the closing bracket.
4710 let anchor = snapshot.anchor_after(selection.end);
4711 new_selections.push((selection.map(|_| anchor), text.len()));
4712 continue;
4713 }
4714 }
4715 // If an opening bracket is 1 character long and is typed while
4716 // text is selected, then surround that text with the bracket pair.
4717 else if auto_surround
4718 && bracket_pair.surround
4719 && is_bracket_pair_start
4720 && bracket_pair.start.chars().count() == 1
4721 {
4722 edits.push((selection.start..selection.start, text.clone()));
4723 edits.push((
4724 selection.end..selection.end,
4725 bracket_pair.end.as_str().into(),
4726 ));
4727 bracket_inserted = true;
4728 new_selections.push((
4729 Selection {
4730 id: selection.id,
4731 start: snapshot.anchor_after(selection.start),
4732 end: snapshot.anchor_before(selection.end),
4733 reversed: selection.reversed,
4734 goal: selection.goal,
4735 },
4736 0,
4737 ));
4738 continue;
4739 }
4740 }
4741 }
4742
4743 if self.auto_replace_emoji_shortcode
4744 && selection.is_empty()
4745 && text.as_ref().ends_with(':')
4746 && let Some(possible_emoji_short_code) =
4747 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4748 && !possible_emoji_short_code.is_empty()
4749 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4750 {
4751 let emoji_shortcode_start = Point::new(
4752 selection.start.row,
4753 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4754 );
4755
4756 // Remove shortcode from buffer
4757 edits.push((
4758 emoji_shortcode_start..selection.start,
4759 "".to_string().into(),
4760 ));
4761 new_selections.push((
4762 Selection {
4763 id: selection.id,
4764 start: snapshot.anchor_after(emoji_shortcode_start),
4765 end: snapshot.anchor_before(selection.start),
4766 reversed: selection.reversed,
4767 goal: selection.goal,
4768 },
4769 0,
4770 ));
4771
4772 // Insert emoji
4773 let selection_start_anchor = snapshot.anchor_after(selection.start);
4774 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4775 edits.push((selection.start..selection.end, emoji.to_string().into()));
4776
4777 continue;
4778 }
4779
4780 let next_is_adjacent = regions
4781 .peek()
4782 .is_some_and(|(next, _)| selection.end == next.start);
4783
4784 // If not handling any auto-close operation, then just replace the selected
4785 // text with the given input and move the selection to the end of the
4786 // newly inserted text.
4787 let anchor = if in_adjacent_group || next_is_adjacent {
4788 // After edits the right bias would shift those anchor to the next visible fragment
4789 // but we want to resolve to the previous one
4790 snapshot.anchor_before(selection.end)
4791 } else {
4792 snapshot.anchor_after(selection.end)
4793 };
4794
4795 if !self.linked_edit_ranges.is_empty() {
4796 let start_anchor = snapshot.anchor_before(selection.start);
4797
4798 let is_word_char = text.chars().next().is_none_or(|char| {
4799 let classifier = snapshot
4800 .char_classifier_at(start_anchor.to_offset(&snapshot))
4801 .scope_context(Some(CharScopeContext::LinkedEdit));
4802 classifier.is_word(char)
4803 });
4804
4805 if is_word_char {
4806 if let Some(ranges) = self
4807 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4808 {
4809 for (buffer, edits) in ranges {
4810 linked_edits
4811 .entry(buffer.clone())
4812 .or_default()
4813 .extend(edits.into_iter().map(|range| (range, text.clone())));
4814 }
4815 }
4816 } else {
4817 clear_linked_edit_ranges = true;
4818 }
4819 }
4820
4821 new_selections.push((selection.map(|_| anchor), 0));
4822 edits.push((selection.start..selection.end, text.clone()));
4823
4824 has_adjacent_edits |= next_is_adjacent;
4825 in_adjacent_group = next_is_adjacent;
4826 }
4827
4828 if all_selections_read_only {
4829 return;
4830 }
4831
4832 drop(regions);
4833 drop(snapshot);
4834
4835 self.transact(window, cx, |this, window, cx| {
4836 if clear_linked_edit_ranges {
4837 this.linked_edit_ranges.clear();
4838 }
4839 let initial_buffer_versions =
4840 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4841
4842 this.buffer.update(cx, |buffer, cx| {
4843 if has_adjacent_edits {
4844 buffer.edit_non_coalesce(edits, this.autoindent_mode.clone(), cx);
4845 } else {
4846 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4847 }
4848 });
4849 for (buffer, edits) in linked_edits {
4850 buffer.update(cx, |buffer, cx| {
4851 let snapshot = buffer.snapshot();
4852 let edits = edits
4853 .into_iter()
4854 .map(|(range, text)| {
4855 use text::ToPoint as TP;
4856 let end_point = TP::to_point(&range.end, &snapshot);
4857 let start_point = TP::to_point(&range.start, &snapshot);
4858 (start_point..end_point, text)
4859 })
4860 .sorted_by_key(|(range, _)| range.start);
4861 buffer.edit(edits, None, cx);
4862 })
4863 }
4864 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4865 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4866 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4867 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
4868 new_anchor_selections,
4869 &map,
4870 )
4871 .zip(new_selection_deltas)
4872 .map(|(selection, delta)| Selection {
4873 id: selection.id,
4874 start: selection.start + delta,
4875 end: selection.end + delta,
4876 reversed: selection.reversed,
4877 goal: SelectionGoal::None,
4878 })
4879 .collect::<Vec<_>>();
4880
4881 let mut i = 0;
4882 for (position, delta, selection_id, pair) in new_autoclose_regions {
4883 let position = position.to_offset(map.buffer_snapshot()) + delta;
4884 let start = map.buffer_snapshot().anchor_before(position);
4885 let end = map.buffer_snapshot().anchor_after(position);
4886 while let Some(existing_state) = this.autoclose_regions.get(i) {
4887 match existing_state
4888 .range
4889 .start
4890 .cmp(&start, map.buffer_snapshot())
4891 {
4892 Ordering::Less => i += 1,
4893 Ordering::Greater => break,
4894 Ordering::Equal => {
4895 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4896 Ordering::Less => i += 1,
4897 Ordering::Equal => break,
4898 Ordering::Greater => break,
4899 }
4900 }
4901 }
4902 }
4903 this.autoclose_regions.insert(
4904 i,
4905 AutocloseRegion {
4906 selection_id,
4907 range: start..end,
4908 pair,
4909 },
4910 );
4911 }
4912
4913 let had_active_edit_prediction = this.has_active_edit_prediction();
4914 this.change_selections(
4915 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4916 window,
4917 cx,
4918 |s| s.select(new_selections),
4919 );
4920
4921 if !bracket_inserted
4922 && let Some(on_type_format_task) =
4923 this.trigger_on_type_formatting(text.to_string(), window, cx)
4924 {
4925 on_type_format_task.detach_and_log_err(cx);
4926 }
4927
4928 let editor_settings = EditorSettings::get_global(cx);
4929 if bracket_inserted
4930 && (editor_settings.auto_signature_help
4931 || editor_settings.show_signature_help_after_edits)
4932 {
4933 this.show_signature_help(&ShowSignatureHelp, window, cx);
4934 }
4935
4936 let trigger_in_words =
4937 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4938 if this.hard_wrap.is_some() {
4939 let latest: Range<Point> = this.selections.newest(&map).range();
4940 if latest.is_empty()
4941 && this
4942 .buffer()
4943 .read(cx)
4944 .snapshot(cx)
4945 .line_len(MultiBufferRow(latest.start.row))
4946 == latest.start.column
4947 {
4948 this.rewrap_impl(
4949 RewrapOptions {
4950 override_language_settings: true,
4951 preserve_existing_whitespace: true,
4952 },
4953 cx,
4954 )
4955 }
4956 }
4957 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4958 refresh_linked_ranges(this, window, cx);
4959 this.refresh_edit_prediction(true, false, window, cx);
4960 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4961 });
4962 }
4963
4964 fn find_possible_emoji_shortcode_at_position(
4965 snapshot: &MultiBufferSnapshot,
4966 position: Point,
4967 ) -> Option<String> {
4968 let mut chars = Vec::new();
4969 let mut found_colon = false;
4970 for char in snapshot.reversed_chars_at(position).take(100) {
4971 // Found a possible emoji shortcode in the middle of the buffer
4972 if found_colon {
4973 if char.is_whitespace() {
4974 chars.reverse();
4975 return Some(chars.iter().collect());
4976 }
4977 // If the previous character is not a whitespace, we are in the middle of a word
4978 // and we only want to complete the shortcode if the word is made up of other emojis
4979 let mut containing_word = String::new();
4980 for ch in snapshot
4981 .reversed_chars_at(position)
4982 .skip(chars.len() + 1)
4983 .take(100)
4984 {
4985 if ch.is_whitespace() {
4986 break;
4987 }
4988 containing_word.push(ch);
4989 }
4990 let containing_word = containing_word.chars().rev().collect::<String>();
4991 if util::word_consists_of_emojis(containing_word.as_str()) {
4992 chars.reverse();
4993 return Some(chars.iter().collect());
4994 }
4995 }
4996
4997 if char.is_whitespace() || !char.is_ascii() {
4998 return None;
4999 }
5000 if char == ':' {
5001 found_colon = true;
5002 } else {
5003 chars.push(char);
5004 }
5005 }
5006 // Found a possible emoji shortcode at the beginning of the buffer
5007 chars.reverse();
5008 Some(chars.iter().collect())
5009 }
5010
5011 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
5012 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5013 self.transact(window, cx, |this, window, cx| {
5014 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
5015 let selections = this
5016 .selections
5017 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
5018 let multi_buffer = this.buffer.read(cx);
5019 let buffer = multi_buffer.snapshot(cx);
5020 selections
5021 .iter()
5022 .map(|selection| {
5023 let start_point = selection.start.to_point(&buffer);
5024 let mut existing_indent =
5025 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
5026 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
5027 let start = selection.start;
5028 let end = selection.end;
5029 let selection_is_empty = start == end;
5030 let language_scope = buffer.language_scope_at(start);
5031 let (delimiter, newline_config) = if let Some(language) = &language_scope {
5032 let needs_extra_newline = NewlineConfig::insert_extra_newline_brackets(
5033 &buffer,
5034 start..end,
5035 language,
5036 )
5037 || NewlineConfig::insert_extra_newline_tree_sitter(
5038 &buffer,
5039 start..end,
5040 );
5041
5042 let mut newline_config = NewlineConfig::Newline {
5043 additional_indent: IndentSize::spaces(0),
5044 extra_line_additional_indent: if needs_extra_newline {
5045 Some(IndentSize::spaces(0))
5046 } else {
5047 None
5048 },
5049 prevent_auto_indent: false,
5050 };
5051
5052 let comment_delimiter = maybe!({
5053 if !selection_is_empty {
5054 return None;
5055 }
5056
5057 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5058 return None;
5059 }
5060
5061 return comment_delimiter_for_newline(
5062 &start_point,
5063 &buffer,
5064 language,
5065 );
5066 });
5067
5068 let doc_delimiter = maybe!({
5069 if !selection_is_empty {
5070 return None;
5071 }
5072
5073 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5074 return None;
5075 }
5076
5077 return documentation_delimiter_for_newline(
5078 &start_point,
5079 &buffer,
5080 language,
5081 &mut newline_config,
5082 );
5083 });
5084
5085 let list_delimiter = maybe!({
5086 if !selection_is_empty {
5087 return None;
5088 }
5089
5090 if !multi_buffer.language_settings(cx).extend_list_on_newline {
5091 return None;
5092 }
5093
5094 return list_delimiter_for_newline(
5095 &start_point,
5096 &buffer,
5097 language,
5098 &mut newline_config,
5099 );
5100 });
5101
5102 (
5103 comment_delimiter.or(doc_delimiter).or(list_delimiter),
5104 newline_config,
5105 )
5106 } else {
5107 (
5108 None,
5109 NewlineConfig::Newline {
5110 additional_indent: IndentSize::spaces(0),
5111 extra_line_additional_indent: None,
5112 prevent_auto_indent: false,
5113 },
5114 )
5115 };
5116
5117 let (edit_start, new_text, prevent_auto_indent) = match &newline_config {
5118 NewlineConfig::ClearCurrentLine => {
5119 let row_start =
5120 buffer.point_to_offset(Point::new(start_point.row, 0));
5121 (row_start, String::new(), false)
5122 }
5123 NewlineConfig::UnindentCurrentLine { continuation } => {
5124 let row_start =
5125 buffer.point_to_offset(Point::new(start_point.row, 0));
5126 let tab_size = buffer.language_settings_at(start, cx).tab_size;
5127 let tab_size_indent = IndentSize::spaces(tab_size.get());
5128 let reduced_indent =
5129 existing_indent.with_delta(Ordering::Less, tab_size_indent);
5130 let mut new_text = String::new();
5131 new_text.extend(reduced_indent.chars());
5132 new_text.push_str(continuation);
5133 (row_start, new_text, true)
5134 }
5135 NewlineConfig::Newline {
5136 additional_indent,
5137 extra_line_additional_indent,
5138 prevent_auto_indent,
5139 } => {
5140 let capacity_for_delimiter =
5141 delimiter.as_deref().map(str::len).unwrap_or_default();
5142 let extra_line_len = extra_line_additional_indent
5143 .map(|i| 1 + existing_indent.len as usize + i.len as usize)
5144 .unwrap_or(0);
5145 let mut new_text = String::with_capacity(
5146 1 + capacity_for_delimiter
5147 + existing_indent.len as usize
5148 + additional_indent.len as usize
5149 + extra_line_len,
5150 );
5151 new_text.push('\n');
5152 new_text.extend(existing_indent.chars());
5153 new_text.extend(additional_indent.chars());
5154 if let Some(delimiter) = &delimiter {
5155 new_text.push_str(delimiter);
5156 }
5157 if let Some(extra_indent) = extra_line_additional_indent {
5158 new_text.push('\n');
5159 new_text.extend(existing_indent.chars());
5160 new_text.extend(extra_indent.chars());
5161 }
5162 (start, new_text, *prevent_auto_indent)
5163 }
5164 };
5165
5166 let anchor = buffer.anchor_after(end);
5167 let new_selection = selection.map(|_| anchor);
5168 (
5169 ((edit_start..end, new_text), prevent_auto_indent),
5170 (newline_config.has_extra_line(), new_selection),
5171 )
5172 })
5173 .unzip()
5174 };
5175
5176 let mut auto_indent_edits = Vec::new();
5177 let mut edits = Vec::new();
5178 for (edit, prevent_auto_indent) in edits_with_flags {
5179 if prevent_auto_indent {
5180 edits.push(edit);
5181 } else {
5182 auto_indent_edits.push(edit);
5183 }
5184 }
5185 if !edits.is_empty() {
5186 this.edit(edits, cx);
5187 }
5188 if !auto_indent_edits.is_empty() {
5189 this.edit_with_autoindent(auto_indent_edits, cx);
5190 }
5191
5192 let buffer = this.buffer.read(cx).snapshot(cx);
5193 let new_selections = selection_info
5194 .into_iter()
5195 .map(|(extra_newline_inserted, new_selection)| {
5196 let mut cursor = new_selection.end.to_point(&buffer);
5197 if extra_newline_inserted {
5198 cursor.row -= 1;
5199 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
5200 }
5201 new_selection.map(|_| cursor)
5202 })
5203 .collect();
5204
5205 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
5206 this.refresh_edit_prediction(true, false, window, cx);
5207 if let Some(task) = this.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5208 task.detach_and_log_err(cx);
5209 }
5210 });
5211 }
5212
5213 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
5214 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5215
5216 let buffer = self.buffer.read(cx);
5217 let snapshot = buffer.snapshot(cx);
5218
5219 let mut edits = Vec::new();
5220 let mut rows = Vec::new();
5221
5222 for (rows_inserted, selection) in self
5223 .selections
5224 .all_adjusted(&self.display_snapshot(cx))
5225 .into_iter()
5226 .enumerate()
5227 {
5228 let cursor = selection.head();
5229 let row = cursor.row;
5230
5231 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
5232
5233 let newline = "\n".to_string();
5234 edits.push((start_of_line..start_of_line, newline));
5235
5236 rows.push(row + rows_inserted as u32);
5237 }
5238
5239 self.transact(window, cx, |editor, window, cx| {
5240 editor.edit(edits, cx);
5241
5242 editor.change_selections(Default::default(), window, cx, |s| {
5243 let mut index = 0;
5244 s.move_cursors_with(|map, _, _| {
5245 let row = rows[index];
5246 index += 1;
5247
5248 let point = Point::new(row, 0);
5249 let boundary = map.next_line_boundary(point).1;
5250 let clipped = map.clip_point(boundary, Bias::Left);
5251
5252 (clipped, SelectionGoal::None)
5253 });
5254 });
5255
5256 let mut indent_edits = Vec::new();
5257 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5258 for row in rows {
5259 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5260 for (row, indent) in indents {
5261 if indent.len == 0 {
5262 continue;
5263 }
5264
5265 let text = match indent.kind {
5266 IndentKind::Space => " ".repeat(indent.len as usize),
5267 IndentKind::Tab => "\t".repeat(indent.len as usize),
5268 };
5269 let point = Point::new(row.0, 0);
5270 indent_edits.push((point..point, text));
5271 }
5272 }
5273 editor.edit(indent_edits, cx);
5274 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5275 format.detach_and_log_err(cx);
5276 }
5277 });
5278 }
5279
5280 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5281 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5282
5283 let buffer = self.buffer.read(cx);
5284 let snapshot = buffer.snapshot(cx);
5285
5286 let mut edits = Vec::new();
5287 let mut rows = Vec::new();
5288 let mut rows_inserted = 0;
5289
5290 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5291 let cursor = selection.head();
5292 let row = cursor.row;
5293
5294 let point = Point::new(row + 1, 0);
5295 let start_of_line = snapshot.clip_point(point, Bias::Left);
5296
5297 let newline = "\n".to_string();
5298 edits.push((start_of_line..start_of_line, newline));
5299
5300 rows_inserted += 1;
5301 rows.push(row + rows_inserted);
5302 }
5303
5304 self.transact(window, cx, |editor, window, cx| {
5305 editor.edit(edits, cx);
5306
5307 editor.change_selections(Default::default(), window, cx, |s| {
5308 let mut index = 0;
5309 s.move_cursors_with(|map, _, _| {
5310 let row = rows[index];
5311 index += 1;
5312
5313 let point = Point::new(row, 0);
5314 let boundary = map.next_line_boundary(point).1;
5315 let clipped = map.clip_point(boundary, Bias::Left);
5316
5317 (clipped, SelectionGoal::None)
5318 });
5319 });
5320
5321 let mut indent_edits = Vec::new();
5322 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5323 for row in rows {
5324 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5325 for (row, indent) in indents {
5326 if indent.len == 0 {
5327 continue;
5328 }
5329
5330 let text = match indent.kind {
5331 IndentKind::Space => " ".repeat(indent.len as usize),
5332 IndentKind::Tab => "\t".repeat(indent.len as usize),
5333 };
5334 let point = Point::new(row.0, 0);
5335 indent_edits.push((point..point, text));
5336 }
5337 }
5338 editor.edit(indent_edits, cx);
5339 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5340 format.detach_and_log_err(cx);
5341 }
5342 });
5343 }
5344
5345 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5346 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5347 original_indent_columns: Vec::new(),
5348 });
5349 self.insert_with_autoindent_mode(text, autoindent, window, cx);
5350 }
5351
5352 fn insert_with_autoindent_mode(
5353 &mut self,
5354 text: &str,
5355 autoindent_mode: Option<AutoindentMode>,
5356 window: &mut Window,
5357 cx: &mut Context<Self>,
5358 ) {
5359 if self.read_only(cx) {
5360 return;
5361 }
5362
5363 let text: Arc<str> = text.into();
5364 self.transact(window, cx, |this, window, cx| {
5365 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5366 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5367 let anchors = {
5368 let snapshot = buffer.read(cx);
5369 old_selections
5370 .iter()
5371 .map(|s| {
5372 let anchor = snapshot.anchor_after(s.head());
5373 s.map(|_| anchor)
5374 })
5375 .collect::<Vec<_>>()
5376 };
5377 buffer.edit(
5378 old_selections
5379 .iter()
5380 .map(|s| (s.start..s.end, text.clone())),
5381 autoindent_mode,
5382 cx,
5383 );
5384 anchors
5385 });
5386
5387 this.change_selections(Default::default(), window, cx, |s| {
5388 s.select_anchors(selection_anchors);
5389 });
5390
5391 cx.notify();
5392 });
5393 }
5394
5395 fn trigger_completion_on_input(
5396 &mut self,
5397 text: &str,
5398 trigger_in_words: bool,
5399 window: &mut Window,
5400 cx: &mut Context<Self>,
5401 ) {
5402 let completions_source = self
5403 .context_menu
5404 .borrow()
5405 .as_ref()
5406 .and_then(|menu| match menu {
5407 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5408 CodeContextMenu::CodeActions(_) => None,
5409 });
5410
5411 match completions_source {
5412 Some(CompletionsMenuSource::Words { .. }) => {
5413 self.open_or_update_completions_menu(
5414 Some(CompletionsMenuSource::Words {
5415 ignore_threshold: false,
5416 }),
5417 None,
5418 trigger_in_words,
5419 window,
5420 cx,
5421 );
5422 }
5423 _ => self.open_or_update_completions_menu(
5424 None,
5425 Some(text.to_owned()).filter(|x| !x.is_empty()),
5426 true,
5427 window,
5428 cx,
5429 ),
5430 }
5431 }
5432
5433 /// If any empty selections is touching the start of its innermost containing autoclose
5434 /// region, expand it to select the brackets.
5435 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5436 let selections = self
5437 .selections
5438 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5439 let buffer = self.buffer.read(cx).read(cx);
5440 let new_selections = self
5441 .selections_with_autoclose_regions(selections, &buffer)
5442 .map(|(mut selection, region)| {
5443 if !selection.is_empty() {
5444 return selection;
5445 }
5446
5447 if let Some(region) = region {
5448 let mut range = region.range.to_offset(&buffer);
5449 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5450 range.start -= region.pair.start.len();
5451 if buffer.contains_str_at(range.start, ®ion.pair.start)
5452 && buffer.contains_str_at(range.end, ®ion.pair.end)
5453 {
5454 range.end += region.pair.end.len();
5455 selection.start = range.start;
5456 selection.end = range.end;
5457
5458 return selection;
5459 }
5460 }
5461 }
5462
5463 let always_treat_brackets_as_autoclosed = buffer
5464 .language_settings_at(selection.start, cx)
5465 .always_treat_brackets_as_autoclosed;
5466
5467 if !always_treat_brackets_as_autoclosed {
5468 return selection;
5469 }
5470
5471 if let Some(scope) = buffer.language_scope_at(selection.start) {
5472 for (pair, enabled) in scope.brackets() {
5473 if !enabled || !pair.close {
5474 continue;
5475 }
5476
5477 if buffer.contains_str_at(selection.start, &pair.end) {
5478 let pair_start_len = pair.start.len();
5479 if buffer.contains_str_at(
5480 selection.start.saturating_sub_usize(pair_start_len),
5481 &pair.start,
5482 ) {
5483 selection.start -= pair_start_len;
5484 selection.end += pair.end.len();
5485
5486 return selection;
5487 }
5488 }
5489 }
5490 }
5491
5492 selection
5493 })
5494 .collect();
5495
5496 drop(buffer);
5497 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5498 selections.select(new_selections)
5499 });
5500 }
5501
5502 /// Iterate the given selections, and for each one, find the smallest surrounding
5503 /// autoclose region. This uses the ordering of the selections and the autoclose
5504 /// regions to avoid repeated comparisons.
5505 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5506 &'a self,
5507 selections: impl IntoIterator<Item = Selection<D>>,
5508 buffer: &'a MultiBufferSnapshot,
5509 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5510 let mut i = 0;
5511 let mut regions = self.autoclose_regions.as_slice();
5512 selections.into_iter().map(move |selection| {
5513 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5514
5515 let mut enclosing = None;
5516 while let Some(pair_state) = regions.get(i) {
5517 if pair_state.range.end.to_offset(buffer) < range.start {
5518 regions = ®ions[i + 1..];
5519 i = 0;
5520 } else if pair_state.range.start.to_offset(buffer) > range.end {
5521 break;
5522 } else {
5523 if pair_state.selection_id == selection.id {
5524 enclosing = Some(pair_state);
5525 }
5526 i += 1;
5527 }
5528 }
5529
5530 (selection, enclosing)
5531 })
5532 }
5533
5534 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5535 fn invalidate_autoclose_regions(
5536 &mut self,
5537 mut selections: &[Selection<Anchor>],
5538 buffer: &MultiBufferSnapshot,
5539 ) {
5540 self.autoclose_regions.retain(|state| {
5541 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5542 return false;
5543 }
5544
5545 let mut i = 0;
5546 while let Some(selection) = selections.get(i) {
5547 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5548 selections = &selections[1..];
5549 continue;
5550 }
5551 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5552 break;
5553 }
5554 if selection.id == state.selection_id {
5555 return true;
5556 } else {
5557 i += 1;
5558 }
5559 }
5560 false
5561 });
5562 }
5563
5564 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5565 let offset = position.to_offset(buffer);
5566 let (word_range, kind) =
5567 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5568 if offset > word_range.start && kind == Some(CharKind::Word) {
5569 Some(
5570 buffer
5571 .text_for_range(word_range.start..offset)
5572 .collect::<String>(),
5573 )
5574 } else {
5575 None
5576 }
5577 }
5578
5579 pub fn visible_excerpts(
5580 &self,
5581 lsp_related_only: bool,
5582 cx: &mut Context<Editor>,
5583 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5584 let project = self.project().cloned();
5585 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
5586 let multi_buffer = self.buffer().read(cx);
5587 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5588 let multi_buffer_visible_start = self
5589 .scroll_manager
5590 .native_anchor(&display_snapshot, cx)
5591 .anchor
5592 .to_point(&multi_buffer_snapshot);
5593 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5594 multi_buffer_visible_start
5595 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5596 Bias::Left,
5597 );
5598 multi_buffer_snapshot
5599 .range_to_buffer_ranges(multi_buffer_visible_start..=multi_buffer_visible_end)
5600 .into_iter()
5601 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5602 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5603 if !lsp_related_only {
5604 return Some((
5605 excerpt_id,
5606 (
5607 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5608 buffer.version().clone(),
5609 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5610 ),
5611 ));
5612 }
5613
5614 let project = project.as_ref()?.read(cx);
5615 let buffer_file = project::File::from_dyn(buffer.file())?;
5616 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5617 let worktree_entry = buffer_worktree
5618 .read(cx)
5619 .entry_for_id(buffer_file.project_entry_id()?)?;
5620 if worktree_entry.is_ignored {
5621 None
5622 } else {
5623 Some((
5624 excerpt_id,
5625 (
5626 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5627 buffer.version().clone(),
5628 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5629 ),
5630 ))
5631 }
5632 })
5633 .collect()
5634 }
5635
5636 pub fn text_layout_details(&self, window: &mut Window, cx: &mut App) -> TextLayoutDetails {
5637 TextLayoutDetails {
5638 text_system: window.text_system().clone(),
5639 editor_style: self.style.clone().unwrap(),
5640 rem_size: window.rem_size(),
5641 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
5642 visible_rows: self.visible_line_count(),
5643 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5644 }
5645 }
5646
5647 fn trigger_on_type_formatting(
5648 &self,
5649 input: String,
5650 window: &mut Window,
5651 cx: &mut Context<Self>,
5652 ) -> Option<Task<Result<()>>> {
5653 if input.chars().count() != 1 {
5654 return None;
5655 }
5656
5657 let project = self.project()?;
5658 let position = self.selections.newest_anchor().head();
5659 let (buffer, buffer_position) = self
5660 .buffer
5661 .read(cx)
5662 .text_anchor_for_position(position, cx)?;
5663
5664 let settings = language_settings::language_settings(
5665 buffer
5666 .read(cx)
5667 .language_at(buffer_position)
5668 .map(|l| l.name()),
5669 buffer.read(cx).file(),
5670 cx,
5671 );
5672 if !settings.use_on_type_format {
5673 return None;
5674 }
5675
5676 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5677 // hence we do LSP request & edit on host side only — add formats to host's history.
5678 let push_to_lsp_host_history = true;
5679 // If this is not the host, append its history with new edits.
5680 let push_to_client_history = project.read(cx).is_via_collab();
5681
5682 let on_type_formatting = project.update(cx, |project, cx| {
5683 project.on_type_format(
5684 buffer.clone(),
5685 buffer_position,
5686 input,
5687 push_to_lsp_host_history,
5688 cx,
5689 )
5690 });
5691 Some(cx.spawn_in(window, async move |editor, cx| {
5692 if let Some(transaction) = on_type_formatting.await? {
5693 if push_to_client_history {
5694 buffer.update(cx, |buffer, _| {
5695 buffer.push_transaction(transaction, Instant::now());
5696 buffer.finalize_last_transaction();
5697 });
5698 }
5699 editor.update(cx, |editor, cx| {
5700 editor.refresh_document_highlights(cx);
5701 })?;
5702 }
5703 Ok(())
5704 }))
5705 }
5706
5707 pub fn show_word_completions(
5708 &mut self,
5709 _: &ShowWordCompletions,
5710 window: &mut Window,
5711 cx: &mut Context<Self>,
5712 ) {
5713 self.open_or_update_completions_menu(
5714 Some(CompletionsMenuSource::Words {
5715 ignore_threshold: true,
5716 }),
5717 None,
5718 false,
5719 window,
5720 cx,
5721 );
5722 }
5723
5724 pub fn show_completions(
5725 &mut self,
5726 _: &ShowCompletions,
5727 window: &mut Window,
5728 cx: &mut Context<Self>,
5729 ) {
5730 self.open_or_update_completions_menu(None, None, false, window, cx);
5731 }
5732
5733 fn open_or_update_completions_menu(
5734 &mut self,
5735 requested_source: Option<CompletionsMenuSource>,
5736 trigger: Option<String>,
5737 trigger_in_words: bool,
5738 window: &mut Window,
5739 cx: &mut Context<Self>,
5740 ) {
5741 if self.pending_rename.is_some() {
5742 return;
5743 }
5744
5745 let completions_source = self
5746 .context_menu
5747 .borrow()
5748 .as_ref()
5749 .and_then(|menu| match menu {
5750 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5751 CodeContextMenu::CodeActions(_) => None,
5752 });
5753
5754 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5755
5756 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5757 // inserted and selected. To handle that case, the start of the selection is used so that
5758 // the menu starts with all choices.
5759 let position = self
5760 .selections
5761 .newest_anchor()
5762 .start
5763 .bias_right(&multibuffer_snapshot);
5764 if position.diff_base_anchor.is_some() {
5765 return;
5766 }
5767 let buffer_position = multibuffer_snapshot.anchor_before(position);
5768 let Some(buffer) = buffer_position
5769 .text_anchor
5770 .buffer_id
5771 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5772 else {
5773 return;
5774 };
5775 let buffer_snapshot = buffer.read(cx).snapshot();
5776
5777 let menu_is_open = matches!(
5778 self.context_menu.borrow().as_ref(),
5779 Some(CodeContextMenu::Completions(_))
5780 );
5781
5782 let language = buffer_snapshot
5783 .language_at(buffer_position.text_anchor)
5784 .map(|language| language.name());
5785
5786 let language_settings = language_settings(language.clone(), buffer_snapshot.file(), cx);
5787 let completion_settings = language_settings.completions.clone();
5788
5789 let show_completions_on_input = self
5790 .show_completions_on_input_override
5791 .unwrap_or(language_settings.show_completions_on_input);
5792 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
5793 return;
5794 }
5795
5796 let query: Option<Arc<String>> =
5797 Self::completion_query(&multibuffer_snapshot, buffer_position)
5798 .map(|query| query.into());
5799
5800 drop(multibuffer_snapshot);
5801
5802 // Hide the current completions menu when query is empty. Without this, cached
5803 // completions from before the trigger char may be reused (#32774).
5804 if query.is_none() && menu_is_open {
5805 self.hide_context_menu(window, cx);
5806 }
5807
5808 let mut ignore_word_threshold = false;
5809 let provider = match requested_source {
5810 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5811 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5812 ignore_word_threshold = ignore_threshold;
5813 None
5814 }
5815 Some(CompletionsMenuSource::SnippetChoices)
5816 | Some(CompletionsMenuSource::SnippetsOnly) => {
5817 log::error!("bug: SnippetChoices requested_source is not handled");
5818 None
5819 }
5820 };
5821
5822 let sort_completions = provider
5823 .as_ref()
5824 .is_some_and(|provider| provider.sort_completions());
5825
5826 let filter_completions = provider
5827 .as_ref()
5828 .is_none_or(|provider| provider.filter_completions());
5829
5830 let was_snippets_only = matches!(
5831 completions_source,
5832 Some(CompletionsMenuSource::SnippetsOnly)
5833 );
5834
5835 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5836 if filter_completions {
5837 menu.filter(
5838 query.clone().unwrap_or_default(),
5839 buffer_position.text_anchor,
5840 &buffer,
5841 provider.clone(),
5842 window,
5843 cx,
5844 );
5845 }
5846 // When `is_incomplete` is false, no need to re-query completions when the current query
5847 // is a suffix of the initial query.
5848 let was_complete = !menu.is_incomplete;
5849 if was_complete && !was_snippets_only {
5850 // If the new query is a suffix of the old query (typing more characters) and
5851 // the previous result was complete, the existing completions can be filtered.
5852 //
5853 // Note that snippet completions are always complete.
5854 let query_matches = match (&menu.initial_query, &query) {
5855 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5856 (None, _) => true,
5857 _ => false,
5858 };
5859 if query_matches {
5860 let position_matches = if menu.initial_position == position {
5861 true
5862 } else {
5863 let snapshot = self.buffer.read(cx).read(cx);
5864 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5865 };
5866 if position_matches {
5867 return;
5868 }
5869 }
5870 }
5871 };
5872
5873 let Anchor {
5874 excerpt_id: buffer_excerpt_id,
5875 text_anchor: buffer_position,
5876 ..
5877 } = buffer_position;
5878
5879 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5880 buffer_snapshot.surrounding_word(buffer_position, None)
5881 {
5882 let word_to_exclude = buffer_snapshot
5883 .text_for_range(word_range.clone())
5884 .collect::<String>();
5885 (
5886 buffer_snapshot.anchor_before(word_range.start)
5887 ..buffer_snapshot.anchor_after(buffer_position),
5888 Some(word_to_exclude),
5889 )
5890 } else {
5891 (buffer_position..buffer_position, None)
5892 };
5893
5894 let show_completion_documentation = buffer_snapshot
5895 .settings_at(buffer_position, cx)
5896 .show_completion_documentation;
5897
5898 // The document can be large, so stay in reasonable bounds when searching for words,
5899 // otherwise completion pop-up might be slow to appear.
5900 const WORD_LOOKUP_ROWS: u32 = 5_000;
5901 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5902 let min_word_search = buffer_snapshot.clip_point(
5903 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5904 Bias::Left,
5905 );
5906 let max_word_search = buffer_snapshot.clip_point(
5907 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5908 Bias::Right,
5909 );
5910 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5911 ..buffer_snapshot.point_to_offset(max_word_search);
5912
5913 let skip_digits = query
5914 .as_ref()
5915 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5916
5917 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5918 trigger.as_ref().is_none_or(|trigger| {
5919 provider.is_completion_trigger(
5920 &buffer,
5921 position.text_anchor,
5922 trigger,
5923 trigger_in_words,
5924 cx,
5925 )
5926 })
5927 });
5928
5929 let provider_responses = if let Some(provider) = &provider
5930 && load_provider_completions
5931 {
5932 let trigger_character =
5933 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5934 let completion_context = CompletionContext {
5935 trigger_kind: match &trigger_character {
5936 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5937 None => CompletionTriggerKind::INVOKED,
5938 },
5939 trigger_character,
5940 };
5941
5942 provider.completions(
5943 buffer_excerpt_id,
5944 &buffer,
5945 buffer_position,
5946 completion_context,
5947 window,
5948 cx,
5949 )
5950 } else {
5951 Task::ready(Ok(Vec::new()))
5952 };
5953
5954 let load_word_completions = if !self.word_completions_enabled {
5955 false
5956 } else if requested_source
5957 == Some(CompletionsMenuSource::Words {
5958 ignore_threshold: true,
5959 })
5960 {
5961 true
5962 } else {
5963 load_provider_completions
5964 && completion_settings.words != WordsCompletionMode::Disabled
5965 && (ignore_word_threshold || {
5966 let words_min_length = completion_settings.words_min_length;
5967 // check whether word has at least `words_min_length` characters
5968 let query_chars = query.iter().flat_map(|q| q.chars());
5969 query_chars.take(words_min_length).count() == words_min_length
5970 })
5971 };
5972
5973 let mut words = if load_word_completions {
5974 cx.background_spawn({
5975 let buffer_snapshot = buffer_snapshot.clone();
5976 async move {
5977 buffer_snapshot.words_in_range(WordsQuery {
5978 fuzzy_contents: None,
5979 range: word_search_range,
5980 skip_digits,
5981 })
5982 }
5983 })
5984 } else {
5985 Task::ready(BTreeMap::default())
5986 };
5987
5988 let snippets = if let Some(provider) = &provider
5989 && provider.show_snippets()
5990 && let Some(project) = self.project()
5991 {
5992 let char_classifier = buffer_snapshot
5993 .char_classifier_at(buffer_position)
5994 .scope_context(Some(CharScopeContext::Completion));
5995 project.update(cx, |project, cx| {
5996 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
5997 })
5998 } else {
5999 Task::ready(Ok(CompletionResponse {
6000 completions: Vec::new(),
6001 display_options: Default::default(),
6002 is_incomplete: false,
6003 }))
6004 };
6005
6006 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
6007
6008 let id = post_inc(&mut self.next_completion_id);
6009 let task = cx.spawn_in(window, async move |editor, cx| {
6010 let Ok(()) = editor.update(cx, |this, _| {
6011 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
6012 }) else {
6013 return;
6014 };
6015
6016 // TODO: Ideally completions from different sources would be selectively re-queried, so
6017 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
6018 let mut completions = Vec::new();
6019 let mut is_incomplete = false;
6020 let mut display_options: Option<CompletionDisplayOptions> = None;
6021 if let Some(provider_responses) = provider_responses.await.log_err()
6022 && !provider_responses.is_empty()
6023 {
6024 for response in provider_responses {
6025 completions.extend(response.completions);
6026 is_incomplete = is_incomplete || response.is_incomplete;
6027 match display_options.as_mut() {
6028 None => {
6029 display_options = Some(response.display_options);
6030 }
6031 Some(options) => options.merge(&response.display_options),
6032 }
6033 }
6034 if completion_settings.words == WordsCompletionMode::Fallback {
6035 words = Task::ready(BTreeMap::default());
6036 }
6037 }
6038 let display_options = display_options.unwrap_or_default();
6039
6040 let mut words = words.await;
6041 if let Some(word_to_exclude) = &word_to_exclude {
6042 words.remove(word_to_exclude);
6043 }
6044 for lsp_completion in &completions {
6045 words.remove(&lsp_completion.new_text);
6046 }
6047 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
6048 replace_range: word_replace_range.clone(),
6049 new_text: word.clone(),
6050 label: CodeLabel::plain(word, None),
6051 match_start: None,
6052 snippet_deduplication_key: None,
6053 icon_path: None,
6054 documentation: None,
6055 source: CompletionSource::BufferWord {
6056 word_range,
6057 resolved: false,
6058 },
6059 insert_text_mode: Some(InsertTextMode::AS_IS),
6060 confirm: None,
6061 }));
6062
6063 completions.extend(
6064 snippets
6065 .await
6066 .into_iter()
6067 .flat_map(|response| response.completions),
6068 );
6069
6070 let menu = if completions.is_empty() {
6071 None
6072 } else {
6073 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
6074 let languages = editor
6075 .workspace
6076 .as_ref()
6077 .and_then(|(workspace, _)| workspace.upgrade())
6078 .map(|workspace| workspace.read(cx).app_state().languages.clone());
6079 let menu = CompletionsMenu::new(
6080 id,
6081 requested_source.unwrap_or(if load_provider_completions {
6082 CompletionsMenuSource::Normal
6083 } else {
6084 CompletionsMenuSource::SnippetsOnly
6085 }),
6086 sort_completions,
6087 show_completion_documentation,
6088 position,
6089 query.clone(),
6090 is_incomplete,
6091 buffer.clone(),
6092 completions.into(),
6093 editor
6094 .context_menu()
6095 .borrow_mut()
6096 .as_ref()
6097 .map(|menu| menu.primary_scroll_handle()),
6098 display_options,
6099 snippet_sort_order,
6100 languages,
6101 language,
6102 cx,
6103 );
6104
6105 let query = if filter_completions { query } else { None };
6106 let matches_task = menu.do_async_filtering(
6107 query.unwrap_or_default(),
6108 buffer_position,
6109 &buffer,
6110 cx,
6111 );
6112 (menu, matches_task)
6113 }) else {
6114 return;
6115 };
6116
6117 let matches = matches_task.await;
6118
6119 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
6120 // Newer menu already set, so exit.
6121 if let Some(CodeContextMenu::Completions(prev_menu)) =
6122 editor.context_menu.borrow().as_ref()
6123 && prev_menu.id > id
6124 {
6125 return;
6126 };
6127
6128 // Only valid to take prev_menu because either the new menu is immediately set
6129 // below, or the menu is hidden.
6130 if let Some(CodeContextMenu::Completions(prev_menu)) =
6131 editor.context_menu.borrow_mut().take()
6132 {
6133 let position_matches =
6134 if prev_menu.initial_position == menu.initial_position {
6135 true
6136 } else {
6137 let snapshot = editor.buffer.read(cx).read(cx);
6138 prev_menu.initial_position.to_offset(&snapshot)
6139 == menu.initial_position.to_offset(&snapshot)
6140 };
6141 if position_matches {
6142 // Preserve markdown cache before `set_filter_results` because it will
6143 // try to populate the documentation cache.
6144 menu.preserve_markdown_cache(prev_menu);
6145 }
6146 };
6147
6148 menu.set_filter_results(matches, provider, window, cx);
6149 }) else {
6150 return;
6151 };
6152
6153 menu.visible().then_some(menu)
6154 };
6155
6156 editor
6157 .update_in(cx, |editor, window, cx| {
6158 if editor.focus_handle.is_focused(window)
6159 && let Some(menu) = menu
6160 {
6161 *editor.context_menu.borrow_mut() =
6162 Some(CodeContextMenu::Completions(menu));
6163
6164 crate::hover_popover::hide_hover(editor, cx);
6165 if editor.show_edit_predictions_in_menu() {
6166 editor.update_visible_edit_prediction(window, cx);
6167 } else {
6168 editor.discard_edit_prediction(false, cx);
6169 }
6170
6171 cx.notify();
6172 return;
6173 }
6174
6175 if editor.completion_tasks.len() <= 1 {
6176 // If there are no more completion tasks and the last menu was empty, we should hide it.
6177 let was_hidden = editor.hide_context_menu(window, cx).is_none();
6178 // If it was already hidden and we don't show edit predictions in the menu,
6179 // we should also show the edit prediction when available.
6180 if was_hidden && editor.show_edit_predictions_in_menu() {
6181 editor.update_visible_edit_prediction(window, cx);
6182 }
6183 }
6184 })
6185 .ok();
6186 });
6187
6188 self.completion_tasks.push((id, task));
6189 }
6190
6191 #[cfg(feature = "test-support")]
6192 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
6193 let menu = self.context_menu.borrow();
6194 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
6195 let completions = menu.completions.borrow();
6196 Some(completions.to_vec())
6197 } else {
6198 None
6199 }
6200 }
6201
6202 pub fn with_completions_menu_matching_id<R>(
6203 &self,
6204 id: CompletionId,
6205 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
6206 ) -> R {
6207 let mut context_menu = self.context_menu.borrow_mut();
6208 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
6209 return f(None);
6210 };
6211 if completions_menu.id != id {
6212 return f(None);
6213 }
6214 f(Some(completions_menu))
6215 }
6216
6217 pub fn confirm_completion(
6218 &mut self,
6219 action: &ConfirmCompletion,
6220 window: &mut Window,
6221 cx: &mut Context<Self>,
6222 ) -> Option<Task<Result<()>>> {
6223 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6224 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
6225 }
6226
6227 pub fn confirm_completion_insert(
6228 &mut self,
6229 _: &ConfirmCompletionInsert,
6230 window: &mut Window,
6231 cx: &mut Context<Self>,
6232 ) -> Option<Task<Result<()>>> {
6233 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6234 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
6235 }
6236
6237 pub fn confirm_completion_replace(
6238 &mut self,
6239 _: &ConfirmCompletionReplace,
6240 window: &mut Window,
6241 cx: &mut Context<Self>,
6242 ) -> Option<Task<Result<()>>> {
6243 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6244 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
6245 }
6246
6247 pub fn compose_completion(
6248 &mut self,
6249 action: &ComposeCompletion,
6250 window: &mut Window,
6251 cx: &mut Context<Self>,
6252 ) -> Option<Task<Result<()>>> {
6253 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6254 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
6255 }
6256
6257 fn do_completion(
6258 &mut self,
6259 item_ix: Option<usize>,
6260 intent: CompletionIntent,
6261 window: &mut Window,
6262 cx: &mut Context<Editor>,
6263 ) -> Option<Task<Result<()>>> {
6264 use language::ToOffset as _;
6265
6266 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6267 else {
6268 return None;
6269 };
6270
6271 let candidate_id = {
6272 let entries = completions_menu.entries.borrow();
6273 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6274 if self.show_edit_predictions_in_menu() {
6275 self.discard_edit_prediction(true, cx);
6276 }
6277 mat.candidate_id
6278 };
6279
6280 let completion = completions_menu
6281 .completions
6282 .borrow()
6283 .get(candidate_id)?
6284 .clone();
6285 cx.stop_propagation();
6286
6287 let buffer_handle = completions_menu.buffer.clone();
6288
6289 let CompletionEdit {
6290 new_text,
6291 snippet,
6292 replace_range,
6293 } = process_completion_for_edit(
6294 &completion,
6295 intent,
6296 &buffer_handle,
6297 &completions_menu.initial_position.text_anchor,
6298 cx,
6299 );
6300
6301 let buffer = buffer_handle.read(cx);
6302 let snapshot = self.buffer.read(cx).snapshot(cx);
6303 let newest_anchor = self.selections.newest_anchor();
6304 let replace_range_multibuffer = {
6305 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6306 excerpt.map_range_from_buffer(replace_range.clone())
6307 };
6308 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6309 return None;
6310 }
6311
6312 let old_text = buffer
6313 .text_for_range(replace_range.clone())
6314 .collect::<String>();
6315 let lookbehind = newest_anchor
6316 .start
6317 .text_anchor
6318 .to_offset(buffer)
6319 .saturating_sub(replace_range.start.0);
6320 let lookahead = replace_range
6321 .end
6322 .0
6323 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6324 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6325 let suffix = &old_text[lookbehind.min(old_text.len())..];
6326
6327 let selections = self
6328 .selections
6329 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6330 let mut ranges = Vec::new();
6331 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6332
6333 for selection in &selections {
6334 let range = if selection.id == newest_anchor.id {
6335 replace_range_multibuffer.clone()
6336 } else {
6337 let mut range = selection.range();
6338
6339 // if prefix is present, don't duplicate it
6340 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6341 range.start = range.start.saturating_sub_usize(lookbehind);
6342
6343 // if suffix is also present, mimic the newest cursor and replace it
6344 if selection.id != newest_anchor.id
6345 && snapshot.contains_str_at(range.end, suffix)
6346 {
6347 range.end += lookahead;
6348 }
6349 }
6350 range
6351 };
6352
6353 ranges.push(range.clone());
6354
6355 if !self.linked_edit_ranges.is_empty() {
6356 let start_anchor = snapshot.anchor_before(range.start);
6357 let end_anchor = snapshot.anchor_after(range.end);
6358 if let Some(ranges) = self
6359 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6360 {
6361 for (buffer, edits) in ranges {
6362 linked_edits
6363 .entry(buffer.clone())
6364 .or_default()
6365 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6366 }
6367 }
6368 }
6369 }
6370
6371 let common_prefix_len = old_text
6372 .chars()
6373 .zip(new_text.chars())
6374 .take_while(|(a, b)| a == b)
6375 .map(|(a, _)| a.len_utf8())
6376 .sum::<usize>();
6377
6378 cx.emit(EditorEvent::InputHandled {
6379 utf16_range_to_replace: None,
6380 text: new_text[common_prefix_len..].into(),
6381 });
6382
6383 self.transact(window, cx, |editor, window, cx| {
6384 if let Some(mut snippet) = snippet {
6385 snippet.text = new_text.to_string();
6386 editor
6387 .insert_snippet(&ranges, snippet, window, cx)
6388 .log_err();
6389 } else {
6390 editor.buffer.update(cx, |multi_buffer, cx| {
6391 let auto_indent = match completion.insert_text_mode {
6392 Some(InsertTextMode::AS_IS) => None,
6393 _ => editor.autoindent_mode.clone(),
6394 };
6395 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6396 multi_buffer.edit(edits, auto_indent, cx);
6397 });
6398 }
6399 for (buffer, edits) in linked_edits {
6400 buffer.update(cx, |buffer, cx| {
6401 let snapshot = buffer.snapshot();
6402 let edits = edits
6403 .into_iter()
6404 .map(|(range, text)| {
6405 use text::ToPoint as TP;
6406 let end_point = TP::to_point(&range.end, &snapshot);
6407 let start_point = TP::to_point(&range.start, &snapshot);
6408 (start_point..end_point, text)
6409 })
6410 .sorted_by_key(|(range, _)| range.start);
6411 buffer.edit(edits, None, cx);
6412 })
6413 }
6414
6415 editor.refresh_edit_prediction(true, false, window, cx);
6416 });
6417 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6418
6419 let show_new_completions_on_confirm = completion
6420 .confirm
6421 .as_ref()
6422 .is_some_and(|confirm| confirm(intent, window, cx));
6423 if show_new_completions_on_confirm {
6424 self.open_or_update_completions_menu(None, None, false, window, cx);
6425 }
6426
6427 let provider = self.completion_provider.as_ref()?;
6428
6429 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6430 let command = lsp_store.as_ref().and_then(|lsp_store| {
6431 let CompletionSource::Lsp {
6432 lsp_completion,
6433 server_id,
6434 ..
6435 } = &completion.source
6436 else {
6437 return None;
6438 };
6439 let lsp_command = lsp_completion.command.as_ref()?;
6440 let available_commands = lsp_store
6441 .read(cx)
6442 .lsp_server_capabilities
6443 .get(server_id)
6444 .and_then(|server_capabilities| {
6445 server_capabilities
6446 .execute_command_provider
6447 .as_ref()
6448 .map(|options| options.commands.as_slice())
6449 })?;
6450 if available_commands.contains(&lsp_command.command) {
6451 Some(CodeAction {
6452 server_id: *server_id,
6453 range: language::Anchor::MIN..language::Anchor::MIN,
6454 lsp_action: LspAction::Command(lsp_command.clone()),
6455 resolved: false,
6456 })
6457 } else {
6458 None
6459 }
6460 });
6461
6462 drop(completion);
6463 let apply_edits = provider.apply_additional_edits_for_completion(
6464 buffer_handle.clone(),
6465 completions_menu.completions.clone(),
6466 candidate_id,
6467 true,
6468 cx,
6469 );
6470
6471 let editor_settings = EditorSettings::get_global(cx);
6472 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6473 // After the code completion is finished, users often want to know what signatures are needed.
6474 // so we should automatically call signature_help
6475 self.show_signature_help(&ShowSignatureHelp, window, cx);
6476 }
6477
6478 Some(cx.spawn_in(window, async move |editor, cx| {
6479 apply_edits.await?;
6480
6481 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6482 let title = command.lsp_action.title().to_owned();
6483 let project_transaction = lsp_store
6484 .update(cx, |lsp_store, cx| {
6485 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6486 })
6487 .await
6488 .context("applying post-completion command")?;
6489 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6490 Self::open_project_transaction(
6491 &editor,
6492 workspace.downgrade(),
6493 project_transaction,
6494 title,
6495 cx,
6496 )
6497 .await?;
6498 }
6499 }
6500
6501 Ok(())
6502 }))
6503 }
6504
6505 pub fn toggle_code_actions(
6506 &mut self,
6507 action: &ToggleCodeActions,
6508 window: &mut Window,
6509 cx: &mut Context<Self>,
6510 ) {
6511 let quick_launch = action.quick_launch;
6512 let mut context_menu = self.context_menu.borrow_mut();
6513 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6514 if code_actions.deployed_from == action.deployed_from {
6515 // Toggle if we're selecting the same one
6516 *context_menu = None;
6517 cx.notify();
6518 return;
6519 } else {
6520 // Otherwise, clear it and start a new one
6521 *context_menu = None;
6522 cx.notify();
6523 }
6524 }
6525 drop(context_menu);
6526 let snapshot = self.snapshot(window, cx);
6527 let deployed_from = action.deployed_from.clone();
6528 let action = action.clone();
6529 self.completion_tasks.clear();
6530 self.discard_edit_prediction(false, cx);
6531
6532 let multibuffer_point = match &action.deployed_from {
6533 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6534 DisplayPoint::new(*row, 0).to_point(&snapshot)
6535 }
6536 _ => self
6537 .selections
6538 .newest::<Point>(&snapshot.display_snapshot)
6539 .head(),
6540 };
6541 let Some((buffer, buffer_row)) = snapshot
6542 .buffer_snapshot()
6543 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6544 .and_then(|(buffer_snapshot, range)| {
6545 self.buffer()
6546 .read(cx)
6547 .buffer(buffer_snapshot.remote_id())
6548 .map(|buffer| (buffer, range.start.row))
6549 })
6550 else {
6551 return;
6552 };
6553 let buffer_id = buffer.read(cx).remote_id();
6554 let tasks = self
6555 .tasks
6556 .get(&(buffer_id, buffer_row))
6557 .map(|t| Arc::new(t.to_owned()));
6558
6559 if !self.focus_handle.is_focused(window) {
6560 return;
6561 }
6562 let project = self.project.clone();
6563
6564 let code_actions_task = match deployed_from {
6565 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6566 _ => self.code_actions(buffer_row, window, cx),
6567 };
6568
6569 let runnable_task = match deployed_from {
6570 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6571 _ => {
6572 let mut task_context_task = Task::ready(None);
6573 if let Some(tasks) = &tasks
6574 && let Some(project) = project
6575 {
6576 task_context_task =
6577 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6578 }
6579
6580 cx.spawn_in(window, {
6581 let buffer = buffer.clone();
6582 async move |editor, cx| {
6583 let task_context = task_context_task.await;
6584
6585 let resolved_tasks =
6586 tasks
6587 .zip(task_context.clone())
6588 .map(|(tasks, task_context)| ResolvedTasks {
6589 templates: tasks.resolve(&task_context).collect(),
6590 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6591 multibuffer_point.row,
6592 tasks.column,
6593 )),
6594 });
6595 let debug_scenarios = editor
6596 .update(cx, |editor, cx| {
6597 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6598 })?
6599 .await;
6600 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6601 }
6602 })
6603 }
6604 };
6605
6606 cx.spawn_in(window, async move |editor, cx| {
6607 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6608 let code_actions = code_actions_task.await;
6609 let spawn_straight_away = quick_launch
6610 && resolved_tasks
6611 .as_ref()
6612 .is_some_and(|tasks| tasks.templates.len() == 1)
6613 && code_actions
6614 .as_ref()
6615 .is_none_or(|actions| actions.is_empty())
6616 && debug_scenarios.is_empty();
6617
6618 editor.update_in(cx, |editor, window, cx| {
6619 crate::hover_popover::hide_hover(editor, cx);
6620 let actions = CodeActionContents::new(
6621 resolved_tasks,
6622 code_actions,
6623 debug_scenarios,
6624 task_context.unwrap_or_default(),
6625 );
6626
6627 // Don't show the menu if there are no actions available
6628 if actions.is_empty() {
6629 cx.notify();
6630 return Task::ready(Ok(()));
6631 }
6632
6633 *editor.context_menu.borrow_mut() =
6634 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6635 buffer,
6636 actions,
6637 selected_item: Default::default(),
6638 scroll_handle: UniformListScrollHandle::default(),
6639 deployed_from,
6640 }));
6641 cx.notify();
6642 if spawn_straight_away
6643 && let Some(task) = editor.confirm_code_action(
6644 &ConfirmCodeAction { item_ix: Some(0) },
6645 window,
6646 cx,
6647 )
6648 {
6649 return task;
6650 }
6651
6652 Task::ready(Ok(()))
6653 })
6654 })
6655 .detach_and_log_err(cx);
6656 }
6657
6658 fn debug_scenarios(
6659 &mut self,
6660 resolved_tasks: &Option<ResolvedTasks>,
6661 buffer: &Entity<Buffer>,
6662 cx: &mut App,
6663 ) -> Task<Vec<task::DebugScenario>> {
6664 maybe!({
6665 let project = self.project()?;
6666 let dap_store = project.read(cx).dap_store();
6667 let mut scenarios = vec![];
6668 let resolved_tasks = resolved_tasks.as_ref()?;
6669 let buffer = buffer.read(cx);
6670 let language = buffer.language()?;
6671 let file = buffer.file();
6672 let debug_adapter = language_settings(language.name().into(), file, cx)
6673 .debuggers
6674 .first()
6675 .map(SharedString::from)
6676 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6677
6678 dap_store.update(cx, |dap_store, cx| {
6679 for (_, task) in &resolved_tasks.templates {
6680 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6681 task.original_task().clone(),
6682 debug_adapter.clone().into(),
6683 task.display_label().to_owned().into(),
6684 cx,
6685 );
6686 scenarios.push(maybe_scenario);
6687 }
6688 });
6689 Some(cx.background_spawn(async move {
6690 futures::future::join_all(scenarios)
6691 .await
6692 .into_iter()
6693 .flatten()
6694 .collect::<Vec<_>>()
6695 }))
6696 })
6697 .unwrap_or_else(|| Task::ready(vec![]))
6698 }
6699
6700 fn code_actions(
6701 &mut self,
6702 buffer_row: u32,
6703 window: &mut Window,
6704 cx: &mut Context<Self>,
6705 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6706 let mut task = self.code_actions_task.take();
6707 cx.spawn_in(window, async move |editor, cx| {
6708 while let Some(prev_task) = task {
6709 prev_task.await.log_err();
6710 task = editor
6711 .update(cx, |this, _| this.code_actions_task.take())
6712 .ok()?;
6713 }
6714
6715 editor
6716 .update(cx, |editor, cx| {
6717 editor
6718 .available_code_actions
6719 .clone()
6720 .and_then(|(location, code_actions)| {
6721 let snapshot = location.buffer.read(cx).snapshot();
6722 let point_range = location.range.to_point(&snapshot);
6723 let point_range = point_range.start.row..=point_range.end.row;
6724 if point_range.contains(&buffer_row) {
6725 Some(code_actions)
6726 } else {
6727 None
6728 }
6729 })
6730 })
6731 .ok()
6732 .flatten()
6733 })
6734 }
6735
6736 pub fn confirm_code_action(
6737 &mut self,
6738 action: &ConfirmCodeAction,
6739 window: &mut Window,
6740 cx: &mut Context<Self>,
6741 ) -> Option<Task<Result<()>>> {
6742 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6743
6744 let actions_menu =
6745 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6746 menu
6747 } else {
6748 return None;
6749 };
6750
6751 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6752 let action = actions_menu.actions.get(action_ix)?;
6753 let title = action.label();
6754 let buffer = actions_menu.buffer;
6755 let workspace = self.workspace()?;
6756
6757 match action {
6758 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6759 workspace.update(cx, |workspace, cx| {
6760 workspace.schedule_resolved_task(
6761 task_source_kind,
6762 resolved_task,
6763 false,
6764 window,
6765 cx,
6766 );
6767
6768 Some(Task::ready(Ok(())))
6769 })
6770 }
6771 CodeActionsItem::CodeAction {
6772 excerpt_id,
6773 action,
6774 provider,
6775 } => {
6776 let apply_code_action =
6777 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6778 let workspace = workspace.downgrade();
6779 Some(cx.spawn_in(window, async move |editor, cx| {
6780 let project_transaction = apply_code_action.await?;
6781 Self::open_project_transaction(
6782 &editor,
6783 workspace,
6784 project_transaction,
6785 title,
6786 cx,
6787 )
6788 .await
6789 }))
6790 }
6791 CodeActionsItem::DebugScenario(scenario) => {
6792 let context = actions_menu.actions.context;
6793
6794 workspace.update(cx, |workspace, cx| {
6795 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6796 workspace.start_debug_session(
6797 scenario,
6798 context,
6799 Some(buffer),
6800 None,
6801 window,
6802 cx,
6803 );
6804 });
6805 Some(Task::ready(Ok(())))
6806 }
6807 }
6808 }
6809
6810 fn open_transaction_for_hidden_buffers(
6811 workspace: Entity<Workspace>,
6812 transaction: ProjectTransaction,
6813 title: String,
6814 window: &mut Window,
6815 cx: &mut Context<Self>,
6816 ) {
6817 if transaction.0.is_empty() {
6818 return;
6819 }
6820
6821 let edited_buffers_already_open = {
6822 let other_editors: Vec<Entity<Editor>> = workspace
6823 .read(cx)
6824 .panes()
6825 .iter()
6826 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
6827 .filter(|editor| editor.entity_id() != cx.entity_id())
6828 .collect();
6829
6830 transaction.0.keys().all(|buffer| {
6831 other_editors.iter().any(|editor| {
6832 let multi_buffer = editor.read(cx).buffer();
6833 multi_buffer.read(cx).is_singleton()
6834 && multi_buffer
6835 .read(cx)
6836 .as_singleton()
6837 .map_or(false, |singleton| {
6838 singleton.entity_id() == buffer.entity_id()
6839 })
6840 })
6841 })
6842 };
6843 if !edited_buffers_already_open {
6844 let workspace = workspace.downgrade();
6845 cx.defer_in(window, move |_, window, cx| {
6846 cx.spawn_in(window, async move |editor, cx| {
6847 Self::open_project_transaction(&editor, workspace, transaction, title, cx)
6848 .await
6849 .ok()
6850 })
6851 .detach();
6852 });
6853 }
6854 }
6855
6856 pub async fn open_project_transaction(
6857 editor: &WeakEntity<Editor>,
6858 workspace: WeakEntity<Workspace>,
6859 transaction: ProjectTransaction,
6860 title: String,
6861 cx: &mut AsyncWindowContext,
6862 ) -> Result<()> {
6863 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6864 cx.update(|_, cx| {
6865 entries.sort_unstable_by_key(|(buffer, _)| {
6866 buffer.read(cx).file().map(|f| f.path().clone())
6867 });
6868 })?;
6869 if entries.is_empty() {
6870 return Ok(());
6871 }
6872
6873 // If the project transaction's edits are all contained within this editor, then
6874 // avoid opening a new editor to display them.
6875
6876 if let [(buffer, transaction)] = &*entries {
6877 let excerpt = editor.update(cx, |editor, cx| {
6878 editor
6879 .buffer()
6880 .read(cx)
6881 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6882 })?;
6883 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6884 && excerpted_buffer == *buffer
6885 {
6886 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6887 let excerpt_range = excerpt_range.to_offset(buffer);
6888 buffer
6889 .edited_ranges_for_transaction::<usize>(transaction)
6890 .all(|range| {
6891 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6892 })
6893 });
6894
6895 if all_edits_within_excerpt {
6896 return Ok(());
6897 }
6898 }
6899 }
6900
6901 let mut ranges_to_highlight = Vec::new();
6902 let excerpt_buffer = cx.new(|cx| {
6903 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6904 for (buffer_handle, transaction) in &entries {
6905 let edited_ranges = buffer_handle
6906 .read(cx)
6907 .edited_ranges_for_transaction::<Point>(transaction)
6908 .collect::<Vec<_>>();
6909 let (ranges, _) = multibuffer.set_excerpts_for_path(
6910 PathKey::for_buffer(buffer_handle, cx),
6911 buffer_handle.clone(),
6912 edited_ranges,
6913 multibuffer_context_lines(cx),
6914 cx,
6915 );
6916
6917 ranges_to_highlight.extend(ranges);
6918 }
6919 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6920 multibuffer
6921 });
6922
6923 workspace.update_in(cx, |workspace, window, cx| {
6924 let project = workspace.project().clone();
6925 let editor =
6926 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6927 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6928 editor.update(cx, |editor, cx| {
6929 editor.highlight_background::<Self>(
6930 &ranges_to_highlight,
6931 |_, theme| theme.colors().editor_highlighted_line_background,
6932 cx,
6933 );
6934 });
6935 })?;
6936
6937 Ok(())
6938 }
6939
6940 pub fn clear_code_action_providers(&mut self) {
6941 self.code_action_providers.clear();
6942 self.available_code_actions.take();
6943 }
6944
6945 pub fn add_code_action_provider(
6946 &mut self,
6947 provider: Rc<dyn CodeActionProvider>,
6948 window: &mut Window,
6949 cx: &mut Context<Self>,
6950 ) {
6951 if self
6952 .code_action_providers
6953 .iter()
6954 .any(|existing_provider| existing_provider.id() == provider.id())
6955 {
6956 return;
6957 }
6958
6959 self.code_action_providers.push(provider);
6960 self.refresh_code_actions(window, cx);
6961 }
6962
6963 pub fn remove_code_action_provider(
6964 &mut self,
6965 id: Arc<str>,
6966 window: &mut Window,
6967 cx: &mut Context<Self>,
6968 ) {
6969 self.code_action_providers
6970 .retain(|provider| provider.id() != id);
6971 self.refresh_code_actions(window, cx);
6972 }
6973
6974 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6975 !self.code_action_providers.is_empty()
6976 && EditorSettings::get_global(cx).toolbar.code_actions
6977 }
6978
6979 pub fn has_available_code_actions(&self) -> bool {
6980 self.available_code_actions
6981 .as_ref()
6982 .is_some_and(|(_, actions)| !actions.is_empty())
6983 }
6984
6985 fn render_inline_code_actions(
6986 &self,
6987 icon_size: ui::IconSize,
6988 display_row: DisplayRow,
6989 is_active: bool,
6990 cx: &mut Context<Self>,
6991 ) -> AnyElement {
6992 let show_tooltip = !self.context_menu_visible();
6993 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6994 .icon_size(icon_size)
6995 .shape(ui::IconButtonShape::Square)
6996 .icon_color(ui::Color::Hidden)
6997 .toggle_state(is_active)
6998 .when(show_tooltip, |this| {
6999 this.tooltip({
7000 let focus_handle = self.focus_handle.clone();
7001 move |_window, cx| {
7002 Tooltip::for_action_in(
7003 "Toggle Code Actions",
7004 &ToggleCodeActions {
7005 deployed_from: None,
7006 quick_launch: false,
7007 },
7008 &focus_handle,
7009 cx,
7010 )
7011 }
7012 })
7013 })
7014 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
7015 window.focus(&editor.focus_handle(cx), cx);
7016 editor.toggle_code_actions(
7017 &crate::actions::ToggleCodeActions {
7018 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
7019 display_row,
7020 )),
7021 quick_launch: false,
7022 },
7023 window,
7024 cx,
7025 );
7026 }))
7027 .into_any_element()
7028 }
7029
7030 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
7031 &self.context_menu
7032 }
7033
7034 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7035 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
7036 cx.background_executor()
7037 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
7038 .await;
7039
7040 let (start_buffer, start, _, end, newest_selection) = this
7041 .update(cx, |this, cx| {
7042 let newest_selection = this.selections.newest_anchor().clone();
7043 if newest_selection.head().diff_base_anchor.is_some() {
7044 return None;
7045 }
7046 let display_snapshot = this.display_snapshot(cx);
7047 let newest_selection_adjusted =
7048 this.selections.newest_adjusted(&display_snapshot);
7049 let buffer = this.buffer.read(cx);
7050
7051 let (start_buffer, start) =
7052 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
7053 let (end_buffer, end) =
7054 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
7055
7056 Some((start_buffer, start, end_buffer, end, newest_selection))
7057 })?
7058 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
7059 .context(
7060 "Expected selection to lie in a single buffer when refreshing code actions",
7061 )?;
7062 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
7063 let providers = this.code_action_providers.clone();
7064 let tasks = this
7065 .code_action_providers
7066 .iter()
7067 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
7068 .collect::<Vec<_>>();
7069 (providers, tasks)
7070 })?;
7071
7072 let mut actions = Vec::new();
7073 for (provider, provider_actions) in
7074 providers.into_iter().zip(future::join_all(tasks).await)
7075 {
7076 if let Some(provider_actions) = provider_actions.log_err() {
7077 actions.extend(provider_actions.into_iter().map(|action| {
7078 AvailableCodeAction {
7079 excerpt_id: newest_selection.start.excerpt_id,
7080 action,
7081 provider: provider.clone(),
7082 }
7083 }));
7084 }
7085 }
7086
7087 this.update(cx, |this, cx| {
7088 this.available_code_actions = if actions.is_empty() {
7089 None
7090 } else {
7091 Some((
7092 Location {
7093 buffer: start_buffer,
7094 range: start..end,
7095 },
7096 actions.into(),
7097 ))
7098 };
7099 cx.notify();
7100 })
7101 }));
7102 }
7103
7104 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7105 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
7106 self.show_git_blame_inline = false;
7107
7108 self.show_git_blame_inline_delay_task =
7109 Some(cx.spawn_in(window, async move |this, cx| {
7110 cx.background_executor().timer(delay).await;
7111
7112 this.update(cx, |this, cx| {
7113 this.show_git_blame_inline = true;
7114 cx.notify();
7115 })
7116 .log_err();
7117 }));
7118 }
7119 }
7120
7121 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
7122 let snapshot = self.snapshot(window, cx);
7123 let cursor = self
7124 .selections
7125 .newest::<Point>(&snapshot.display_snapshot)
7126 .head();
7127 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
7128 else {
7129 return;
7130 };
7131
7132 if self.blame.is_none() {
7133 self.start_git_blame(true, window, cx);
7134 }
7135 let Some(blame) = self.blame.as_ref() else {
7136 return;
7137 };
7138
7139 let row_info = RowInfo {
7140 buffer_id: Some(buffer.remote_id()),
7141 buffer_row: Some(point.row),
7142 ..Default::default()
7143 };
7144 let Some((buffer, blame_entry)) = blame
7145 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
7146 .flatten()
7147 else {
7148 return;
7149 };
7150
7151 let anchor = self.selections.newest_anchor().head();
7152 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
7153 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
7154 self.show_blame_popover(
7155 buffer,
7156 &blame_entry,
7157 position + last_bounds.origin,
7158 true,
7159 cx,
7160 );
7161 };
7162 }
7163
7164 fn show_blame_popover(
7165 &mut self,
7166 buffer: BufferId,
7167 blame_entry: &BlameEntry,
7168 position: gpui::Point<Pixels>,
7169 ignore_timeout: bool,
7170 cx: &mut Context<Self>,
7171 ) {
7172 if let Some(state) = &mut self.inline_blame_popover {
7173 state.hide_task.take();
7174 } else {
7175 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
7176 let blame_entry = blame_entry.clone();
7177 let show_task = cx.spawn(async move |editor, cx| {
7178 if !ignore_timeout {
7179 cx.background_executor()
7180 .timer(std::time::Duration::from_millis(blame_popover_delay))
7181 .await;
7182 }
7183 editor
7184 .update(cx, |editor, cx| {
7185 editor.inline_blame_popover_show_task.take();
7186 let Some(blame) = editor.blame.as_ref() else {
7187 return;
7188 };
7189 let blame = blame.read(cx);
7190 let details = blame.details_for_entry(buffer, &blame_entry);
7191 let markdown = cx.new(|cx| {
7192 Markdown::new(
7193 details
7194 .as_ref()
7195 .map(|message| message.message.clone())
7196 .unwrap_or_default(),
7197 None,
7198 None,
7199 cx,
7200 )
7201 });
7202 editor.inline_blame_popover = Some(InlineBlamePopover {
7203 position,
7204 hide_task: None,
7205 popover_bounds: None,
7206 popover_state: InlineBlamePopoverState {
7207 scroll_handle: ScrollHandle::new(),
7208 commit_message: details,
7209 markdown,
7210 },
7211 keyboard_grace: ignore_timeout,
7212 });
7213 cx.notify();
7214 })
7215 .ok();
7216 });
7217 self.inline_blame_popover_show_task = Some(show_task);
7218 }
7219 }
7220
7221 pub fn has_mouse_context_menu(&self) -> bool {
7222 self.mouse_context_menu.is_some()
7223 }
7224
7225 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
7226 self.inline_blame_popover_show_task.take();
7227 if let Some(state) = &mut self.inline_blame_popover {
7228 let hide_task = cx.spawn(async move |editor, cx| {
7229 if !ignore_timeout {
7230 cx.background_executor()
7231 .timer(std::time::Duration::from_millis(100))
7232 .await;
7233 }
7234 editor
7235 .update(cx, |editor, cx| {
7236 editor.inline_blame_popover.take();
7237 cx.notify();
7238 })
7239 .ok();
7240 });
7241 state.hide_task = Some(hide_task);
7242 true
7243 } else {
7244 false
7245 }
7246 }
7247
7248 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
7249 if self.pending_rename.is_some() {
7250 return None;
7251 }
7252
7253 let provider = self.semantics_provider.clone()?;
7254 let buffer = self.buffer.read(cx);
7255 let newest_selection = self.selections.newest_anchor().clone();
7256 let cursor_position = newest_selection.head();
7257 let (cursor_buffer, cursor_buffer_position) =
7258 buffer.text_anchor_for_position(cursor_position, cx)?;
7259 let (tail_buffer, tail_buffer_position) =
7260 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
7261 if cursor_buffer != tail_buffer {
7262 return None;
7263 }
7264
7265 let snapshot = cursor_buffer.read(cx).snapshot();
7266 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
7267 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
7268 if start_word_range != end_word_range {
7269 self.document_highlights_task.take();
7270 self.clear_background_highlights::<DocumentHighlightRead>(cx);
7271 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
7272 return None;
7273 }
7274
7275 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
7276 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
7277 cx.background_executor()
7278 .timer(Duration::from_millis(debounce))
7279 .await;
7280
7281 let highlights = if let Some(highlights) = cx.update(|cx| {
7282 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7283 }) {
7284 highlights.await.log_err()
7285 } else {
7286 None
7287 };
7288
7289 if let Some(highlights) = highlights {
7290 this.update(cx, |this, cx| {
7291 if this.pending_rename.is_some() {
7292 return;
7293 }
7294
7295 let buffer = this.buffer.read(cx);
7296 if buffer
7297 .text_anchor_for_position(cursor_position, cx)
7298 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7299 {
7300 return;
7301 }
7302
7303 let cursor_buffer_snapshot = cursor_buffer.read(cx);
7304 let mut write_ranges = Vec::new();
7305 let mut read_ranges = Vec::new();
7306 for highlight in highlights {
7307 let buffer_id = cursor_buffer.read(cx).remote_id();
7308 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
7309 {
7310 let start = highlight
7311 .range
7312 .start
7313 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7314 let end = highlight
7315 .range
7316 .end
7317 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7318 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7319 continue;
7320 }
7321
7322 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7323 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7324 write_ranges.push(range);
7325 } else {
7326 read_ranges.push(range);
7327 }
7328 }
7329 }
7330
7331 this.highlight_background::<DocumentHighlightRead>(
7332 &read_ranges,
7333 |_, theme| theme.colors().editor_document_highlight_read_background,
7334 cx,
7335 );
7336 this.highlight_background::<DocumentHighlightWrite>(
7337 &write_ranges,
7338 |_, theme| theme.colors().editor_document_highlight_write_background,
7339 cx,
7340 );
7341 cx.notify();
7342 })
7343 .log_err();
7344 }
7345 }));
7346 None
7347 }
7348
7349 fn prepare_highlight_query_from_selection(
7350 &mut self,
7351 window: &Window,
7352 cx: &mut Context<Editor>,
7353 ) -> Option<(String, Range<Anchor>)> {
7354 if matches!(self.mode, EditorMode::SingleLine) {
7355 return None;
7356 }
7357 if !EditorSettings::get_global(cx).selection_highlight {
7358 return None;
7359 }
7360 if self.selections.count() != 1 || self.selections.line_mode() {
7361 return None;
7362 }
7363 let snapshot = self.snapshot(window, cx);
7364 let selection = self.selections.newest::<Point>(&snapshot);
7365 // If the selection spans multiple rows OR it is empty
7366 if selection.start.row != selection.end.row
7367 || selection.start.column == selection.end.column
7368 {
7369 return None;
7370 }
7371 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7372 let query = snapshot
7373 .buffer_snapshot()
7374 .text_for_range(selection_anchor_range.clone())
7375 .collect::<String>();
7376 if query.trim().is_empty() {
7377 return None;
7378 }
7379 Some((query, selection_anchor_range))
7380 }
7381
7382 #[ztracing::instrument(skip_all)]
7383 fn update_selection_occurrence_highlights(
7384 &mut self,
7385 query_text: String,
7386 query_range: Range<Anchor>,
7387 multi_buffer_range_to_query: Range<Point>,
7388 use_debounce: bool,
7389 window: &mut Window,
7390 cx: &mut Context<Editor>,
7391 ) -> Task<()> {
7392 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7393 cx.spawn_in(window, async move |editor, cx| {
7394 if use_debounce {
7395 cx.background_executor()
7396 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7397 .await;
7398 }
7399 let match_task = cx.background_spawn(async move {
7400 let buffer_ranges = multi_buffer_snapshot
7401 .range_to_buffer_ranges(
7402 multi_buffer_range_to_query.start..=multi_buffer_range_to_query.end,
7403 )
7404 .into_iter()
7405 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7406 let mut match_ranges = Vec::new();
7407 let Ok(regex) = project::search::SearchQuery::text(
7408 query_text.clone(),
7409 false,
7410 false,
7411 false,
7412 Default::default(),
7413 Default::default(),
7414 false,
7415 None,
7416 ) else {
7417 return Vec::default();
7418 };
7419 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7420 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7421 match_ranges.extend(
7422 regex
7423 .search(
7424 buffer_snapshot,
7425 Some(search_range.start.0..search_range.end.0),
7426 )
7427 .await
7428 .into_iter()
7429 .filter_map(|match_range| {
7430 let match_start = buffer_snapshot
7431 .anchor_after(search_range.start + match_range.start);
7432 let match_end = buffer_snapshot
7433 .anchor_before(search_range.start + match_range.end);
7434 let match_anchor_range =
7435 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7436 (match_anchor_range != query_range).then_some(match_anchor_range)
7437 }),
7438 );
7439 }
7440 match_ranges
7441 });
7442 let match_ranges = match_task.await;
7443 editor
7444 .update_in(cx, |editor, _, cx| {
7445 if use_debounce {
7446 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7447 editor.debounced_selection_highlight_complete = true;
7448 } else if editor.debounced_selection_highlight_complete {
7449 return;
7450 }
7451 if !match_ranges.is_empty() {
7452 editor.highlight_background::<SelectedTextHighlight>(
7453 &match_ranges,
7454 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7455 cx,
7456 )
7457 }
7458 })
7459 .log_err();
7460 })
7461 }
7462
7463 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7464 struct NewlineFold;
7465 let type_id = std::any::TypeId::of::<NewlineFold>();
7466 if !self.mode.is_single_line() {
7467 return;
7468 }
7469 let snapshot = self.snapshot(window, cx);
7470 if snapshot.buffer_snapshot().max_point().row == 0 {
7471 return;
7472 }
7473 let task = cx.background_spawn(async move {
7474 let new_newlines = snapshot
7475 .buffer_chars_at(MultiBufferOffset(0))
7476 .filter_map(|(c, i)| {
7477 if c == '\n' {
7478 Some(
7479 snapshot.buffer_snapshot().anchor_after(i)
7480 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7481 )
7482 } else {
7483 None
7484 }
7485 })
7486 .collect::<Vec<_>>();
7487 let existing_newlines = snapshot
7488 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7489 .filter_map(|fold| {
7490 if fold.placeholder.type_tag == Some(type_id) {
7491 Some(fold.range.start..fold.range.end)
7492 } else {
7493 None
7494 }
7495 })
7496 .collect::<Vec<_>>();
7497
7498 (new_newlines, existing_newlines)
7499 });
7500 self.folding_newlines = cx.spawn(async move |this, cx| {
7501 let (new_newlines, existing_newlines) = task.await;
7502 if new_newlines == existing_newlines {
7503 return;
7504 }
7505 let placeholder = FoldPlaceholder {
7506 render: Arc::new(move |_, _, cx| {
7507 div()
7508 .bg(cx.theme().status().hint_background)
7509 .border_b_1()
7510 .size_full()
7511 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7512 .border_color(cx.theme().status().hint)
7513 .child("\\n")
7514 .into_any()
7515 }),
7516 constrain_width: false,
7517 merge_adjacent: false,
7518 type_tag: Some(type_id),
7519 };
7520 let creases = new_newlines
7521 .into_iter()
7522 .map(|range| Crease::simple(range, placeholder.clone()))
7523 .collect();
7524 this.update(cx, |this, cx| {
7525 this.display_map.update(cx, |display_map, cx| {
7526 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7527 display_map.fold(creases, cx);
7528 });
7529 })
7530 .ok();
7531 });
7532 }
7533
7534 #[ztracing::instrument(skip_all)]
7535 fn refresh_selected_text_highlights(
7536 &mut self,
7537 on_buffer_edit: bool,
7538 window: &mut Window,
7539 cx: &mut Context<Editor>,
7540 ) {
7541 let Some((query_text, query_range)) =
7542 self.prepare_highlight_query_from_selection(window, cx)
7543 else {
7544 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7545 self.quick_selection_highlight_task.take();
7546 self.debounced_selection_highlight_task.take();
7547 self.debounced_selection_highlight_complete = false;
7548 return;
7549 };
7550 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7551 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7552 let query_changed = self
7553 .quick_selection_highlight_task
7554 .as_ref()
7555 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range);
7556 if query_changed {
7557 self.debounced_selection_highlight_complete = false;
7558 }
7559 if on_buffer_edit || query_changed {
7560 let multi_buffer_visible_start = self
7561 .scroll_manager
7562 .native_anchor(&display_snapshot, cx)
7563 .anchor
7564 .to_point(&multi_buffer_snapshot);
7565 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7566 multi_buffer_visible_start
7567 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7568 Bias::Left,
7569 );
7570 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7571 self.quick_selection_highlight_task = Some((
7572 query_range.clone(),
7573 self.update_selection_occurrence_highlights(
7574 query_text.clone(),
7575 query_range.clone(),
7576 multi_buffer_visible_range,
7577 false,
7578 window,
7579 cx,
7580 ),
7581 ));
7582 }
7583 if on_buffer_edit
7584 || self
7585 .debounced_selection_highlight_task
7586 .as_ref()
7587 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7588 {
7589 let multi_buffer_start = multi_buffer_snapshot
7590 .anchor_before(MultiBufferOffset(0))
7591 .to_point(&multi_buffer_snapshot);
7592 let multi_buffer_end = multi_buffer_snapshot
7593 .anchor_after(multi_buffer_snapshot.len())
7594 .to_point(&multi_buffer_snapshot);
7595 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7596 self.debounced_selection_highlight_task = Some((
7597 query_range.clone(),
7598 self.update_selection_occurrence_highlights(
7599 query_text,
7600 query_range,
7601 multi_buffer_full_range,
7602 true,
7603 window,
7604 cx,
7605 ),
7606 ));
7607 }
7608 }
7609
7610 pub fn refresh_edit_prediction(
7611 &mut self,
7612 debounce: bool,
7613 user_requested: bool,
7614 window: &mut Window,
7615 cx: &mut Context<Self>,
7616 ) -> Option<()> {
7617 if DisableAiSettings::get_global(cx).disable_ai {
7618 return None;
7619 }
7620
7621 let provider = self.edit_prediction_provider()?;
7622 let cursor = self.selections.newest_anchor().head();
7623 let (buffer, cursor_buffer_position) =
7624 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7625
7626 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7627 self.discard_edit_prediction(false, cx);
7628 return None;
7629 }
7630
7631 self.update_visible_edit_prediction(window, cx);
7632
7633 if !user_requested
7634 && (!self.should_show_edit_predictions()
7635 || !self.is_focused(window)
7636 || buffer.read(cx).is_empty())
7637 {
7638 self.discard_edit_prediction(false, cx);
7639 return None;
7640 }
7641
7642 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7643 Some(())
7644 }
7645
7646 fn show_edit_predictions_in_menu(&self) -> bool {
7647 match self.edit_prediction_settings {
7648 EditPredictionSettings::Disabled => false,
7649 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7650 }
7651 }
7652
7653 pub fn edit_predictions_enabled(&self) -> bool {
7654 match self.edit_prediction_settings {
7655 EditPredictionSettings::Disabled => false,
7656 EditPredictionSettings::Enabled { .. } => true,
7657 }
7658 }
7659
7660 fn edit_prediction_requires_modifier(&self) -> bool {
7661 match self.edit_prediction_settings {
7662 EditPredictionSettings::Disabled => false,
7663 EditPredictionSettings::Enabled {
7664 preview_requires_modifier,
7665 ..
7666 } => preview_requires_modifier,
7667 }
7668 }
7669
7670 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7671 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7672 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7673 self.discard_edit_prediction(false, cx);
7674 } else {
7675 let selection = self.selections.newest_anchor();
7676 let cursor = selection.head();
7677
7678 if let Some((buffer, cursor_buffer_position)) =
7679 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7680 {
7681 self.edit_prediction_settings =
7682 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7683 }
7684 }
7685 }
7686
7687 fn edit_prediction_settings_at_position(
7688 &self,
7689 buffer: &Entity<Buffer>,
7690 buffer_position: language::Anchor,
7691 cx: &App,
7692 ) -> EditPredictionSettings {
7693 if !self.mode.is_full()
7694 || !self.show_edit_predictions_override.unwrap_or(true)
7695 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7696 {
7697 return EditPredictionSettings::Disabled;
7698 }
7699
7700 let buffer = buffer.read(cx);
7701
7702 let file = buffer.file();
7703
7704 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7705 return EditPredictionSettings::Disabled;
7706 };
7707
7708 let by_provider = matches!(
7709 self.menu_edit_predictions_policy,
7710 MenuEditPredictionsPolicy::ByProvider
7711 );
7712
7713 let show_in_menu = by_provider
7714 && self
7715 .edit_prediction_provider
7716 .as_ref()
7717 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
7718
7719 let preview_requires_modifier =
7720 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7721
7722 EditPredictionSettings::Enabled {
7723 show_in_menu,
7724 preview_requires_modifier,
7725 }
7726 }
7727
7728 fn should_show_edit_predictions(&self) -> bool {
7729 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7730 }
7731
7732 pub fn edit_prediction_preview_is_active(&self) -> bool {
7733 matches!(
7734 self.edit_prediction_preview,
7735 EditPredictionPreview::Active { .. }
7736 )
7737 }
7738
7739 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7740 let cursor = self.selections.newest_anchor().head();
7741 if let Some((buffer, cursor_position)) =
7742 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7743 {
7744 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7745 } else {
7746 false
7747 }
7748 }
7749
7750 pub fn supports_minimap(&self, cx: &App) -> bool {
7751 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7752 }
7753
7754 fn edit_predictions_enabled_in_buffer(
7755 &self,
7756 buffer: &Entity<Buffer>,
7757 buffer_position: language::Anchor,
7758 cx: &App,
7759 ) -> bool {
7760 maybe!({
7761 if self.read_only(cx) {
7762 return Some(false);
7763 }
7764 let provider = self.edit_prediction_provider()?;
7765 if !provider.is_enabled(buffer, buffer_position, cx) {
7766 return Some(false);
7767 }
7768 let buffer = buffer.read(cx);
7769 let Some(file) = buffer.file() else {
7770 return Some(true);
7771 };
7772 let settings = all_language_settings(Some(file), cx);
7773 Some(settings.edit_predictions_enabled_for_file(file, cx))
7774 })
7775 .unwrap_or(false)
7776 }
7777
7778 pub fn show_edit_prediction(
7779 &mut self,
7780 _: &ShowEditPrediction,
7781 window: &mut Window,
7782 cx: &mut Context<Self>,
7783 ) {
7784 if !self.has_active_edit_prediction() {
7785 self.refresh_edit_prediction(false, true, window, cx);
7786 return;
7787 }
7788
7789 self.update_visible_edit_prediction(window, cx);
7790 }
7791
7792 pub fn display_cursor_names(
7793 &mut self,
7794 _: &DisplayCursorNames,
7795 window: &mut Window,
7796 cx: &mut Context<Self>,
7797 ) {
7798 self.show_cursor_names(window, cx);
7799 }
7800
7801 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7802 self.show_cursor_names = true;
7803 cx.notify();
7804 cx.spawn_in(window, async move |this, cx| {
7805 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7806 this.update(cx, |this, cx| {
7807 this.show_cursor_names = false;
7808 cx.notify()
7809 })
7810 .ok()
7811 })
7812 .detach();
7813 }
7814
7815 pub fn accept_partial_edit_prediction(
7816 &mut self,
7817 granularity: EditPredictionGranularity,
7818 window: &mut Window,
7819 cx: &mut Context<Self>,
7820 ) {
7821 if self.show_edit_predictions_in_menu() {
7822 self.hide_context_menu(window, cx);
7823 }
7824
7825 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7826 return;
7827 };
7828
7829 if !matches!(granularity, EditPredictionGranularity::Full) && self.selections.count() != 1 {
7830 return;
7831 }
7832
7833 match &active_edit_prediction.completion {
7834 EditPrediction::MoveWithin { target, .. } => {
7835 let target = *target;
7836
7837 if matches!(granularity, EditPredictionGranularity::Full) {
7838 if let Some(position_map) = &self.last_position_map {
7839 let target_row = target.to_display_point(&position_map.snapshot).row();
7840 let is_visible = position_map.visible_row_range.contains(&target_row);
7841
7842 if is_visible || !self.edit_prediction_requires_modifier() {
7843 self.unfold_ranges(&[target..target], true, false, cx);
7844 self.change_selections(
7845 SelectionEffects::scroll(Autoscroll::newest()),
7846 window,
7847 cx,
7848 |selections| {
7849 selections.select_anchor_ranges([target..target]);
7850 },
7851 );
7852 self.clear_row_highlights::<EditPredictionPreview>();
7853 self.edit_prediction_preview
7854 .set_previous_scroll_position(None);
7855 } else {
7856 // Highlight and request scroll
7857 self.edit_prediction_preview
7858 .set_previous_scroll_position(Some(
7859 position_map.snapshot.scroll_anchor,
7860 ));
7861 self.highlight_rows::<EditPredictionPreview>(
7862 target..target,
7863 cx.theme().colors().editor_highlighted_line_background,
7864 RowHighlightOptions {
7865 autoscroll: true,
7866 ..Default::default()
7867 },
7868 cx,
7869 );
7870 self.request_autoscroll(Autoscroll::fit(), cx);
7871 }
7872 }
7873 } else {
7874 self.change_selections(
7875 SelectionEffects::scroll(Autoscroll::newest()),
7876 window,
7877 cx,
7878 |selections| {
7879 selections.select_anchor_ranges([target..target]);
7880 },
7881 );
7882 }
7883 }
7884 EditPrediction::MoveOutside { snapshot, target } => {
7885 if let Some(workspace) = self.workspace() {
7886 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7887 .detach_and_log_err(cx);
7888 }
7889 }
7890 EditPrediction::Edit {
7891 edits,
7892 cursor_position,
7893 ..
7894 } => {
7895 self.report_edit_prediction_event(
7896 active_edit_prediction.completion_id.clone(),
7897 true,
7898 cx,
7899 );
7900
7901 match granularity {
7902 EditPredictionGranularity::Full => {
7903 if let Some(provider) = self.edit_prediction_provider() {
7904 provider.accept(cx);
7905 }
7906
7907 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7908
7909 // Compute fallback cursor position BEFORE applying the edit,
7910 // so the anchor tracks through the edit correctly
7911 let fallback_cursor_target = {
7912 let snapshot = self.buffer.read(cx).snapshot(cx);
7913 edits.last().unwrap().0.end.bias_right(&snapshot)
7914 };
7915
7916 self.buffer.update(cx, |buffer, cx| {
7917 buffer.edit(edits.iter().cloned(), None, cx)
7918 });
7919
7920 // Resolve cursor position after the edit is applied
7921 let cursor_target = if let Some((anchor, offset)) = cursor_position {
7922 // The anchor tracks through the edit, then we add the offset
7923 let snapshot = self.buffer.read(cx).snapshot(cx);
7924 let base_offset = anchor.to_offset(&snapshot).0;
7925 let target_offset =
7926 MultiBufferOffset((base_offset + offset).min(snapshot.len().0));
7927 snapshot.anchor_after(target_offset)
7928 } else {
7929 fallback_cursor_target
7930 };
7931
7932 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7933 s.select_anchor_ranges([cursor_target..cursor_target]);
7934 });
7935
7936 let selections = self.selections.disjoint_anchors_arc();
7937 if let Some(transaction_id_now) =
7938 self.buffer.read(cx).last_transaction_id(cx)
7939 {
7940 if transaction_id_prev != Some(transaction_id_now) {
7941 self.selection_history
7942 .insert_transaction(transaction_id_now, selections);
7943 }
7944 }
7945
7946 self.update_visible_edit_prediction(window, cx);
7947 if self.active_edit_prediction.is_none() {
7948 self.refresh_edit_prediction(true, true, window, cx);
7949 }
7950 cx.notify();
7951 }
7952 _ => {
7953 let snapshot = self.buffer.read(cx).snapshot(cx);
7954 let cursor_offset = self
7955 .selections
7956 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
7957 .head();
7958
7959 let insertion = edits.iter().find_map(|(range, text)| {
7960 let range = range.to_offset(&snapshot);
7961 if range.is_empty() && range.start == cursor_offset {
7962 Some(text)
7963 } else {
7964 None
7965 }
7966 });
7967
7968 if let Some(text) = insertion {
7969 let text_to_insert = match granularity {
7970 EditPredictionGranularity::Word => {
7971 let mut partial = text
7972 .chars()
7973 .by_ref()
7974 .take_while(|c| c.is_alphabetic())
7975 .collect::<String>();
7976 if partial.is_empty() {
7977 partial = text
7978 .chars()
7979 .by_ref()
7980 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7981 .collect::<String>();
7982 }
7983 partial
7984 }
7985 EditPredictionGranularity::Line => {
7986 if let Some(line) = text.split_inclusive('\n').next() {
7987 line.to_string()
7988 } else {
7989 text.to_string()
7990 }
7991 }
7992 EditPredictionGranularity::Full => unreachable!(),
7993 };
7994
7995 cx.emit(EditorEvent::InputHandled {
7996 utf16_range_to_replace: None,
7997 text: text_to_insert.clone().into(),
7998 });
7999
8000 self.insert_with_autoindent_mode(&text_to_insert, None, window, cx);
8001 self.refresh_edit_prediction(true, true, window, cx);
8002 cx.notify();
8003 } else {
8004 self.accept_partial_edit_prediction(
8005 EditPredictionGranularity::Full,
8006 window,
8007 cx,
8008 );
8009 }
8010 }
8011 }
8012 }
8013 }
8014
8015 self.edit_prediction_requires_modifier_in_indent_conflict = false;
8016 }
8017
8018 pub fn accept_next_word_edit_prediction(
8019 &mut self,
8020 _: &AcceptNextWordEditPrediction,
8021 window: &mut Window,
8022 cx: &mut Context<Self>,
8023 ) {
8024 self.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
8025 }
8026
8027 pub fn accept_next_line_edit_prediction(
8028 &mut self,
8029 _: &AcceptNextLineEditPrediction,
8030 window: &mut Window,
8031 cx: &mut Context<Self>,
8032 ) {
8033 self.accept_partial_edit_prediction(EditPredictionGranularity::Line, window, cx);
8034 }
8035
8036 pub fn accept_edit_prediction(
8037 &mut self,
8038 _: &AcceptEditPrediction,
8039 window: &mut Window,
8040 cx: &mut Context<Self>,
8041 ) {
8042 self.accept_partial_edit_prediction(EditPredictionGranularity::Full, window, cx);
8043 }
8044
8045 fn discard_edit_prediction(
8046 &mut self,
8047 should_report_edit_prediction_event: bool,
8048 cx: &mut Context<Self>,
8049 ) -> bool {
8050 if should_report_edit_prediction_event {
8051 let completion_id = self
8052 .active_edit_prediction
8053 .as_ref()
8054 .and_then(|active_completion| active_completion.completion_id.clone());
8055
8056 self.report_edit_prediction_event(completion_id, false, cx);
8057 }
8058
8059 if let Some(provider) = self.edit_prediction_provider() {
8060 provider.discard(cx);
8061 }
8062
8063 self.take_active_edit_prediction(cx)
8064 }
8065
8066 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
8067 let Some(provider) = self.edit_prediction_provider() else {
8068 return;
8069 };
8070
8071 let Some((_, buffer, _)) = self
8072 .buffer
8073 .read(cx)
8074 .excerpt_containing(self.selections.newest_anchor().head(), cx)
8075 else {
8076 return;
8077 };
8078
8079 let extension = buffer
8080 .read(cx)
8081 .file()
8082 .and_then(|file| Some(file.path().extension()?.to_string()));
8083
8084 let event_type = match accepted {
8085 true => "Edit Prediction Accepted",
8086 false => "Edit Prediction Discarded",
8087 };
8088 telemetry::event!(
8089 event_type,
8090 provider = provider.name(),
8091 prediction_id = id,
8092 suggestion_accepted = accepted,
8093 file_extension = extension,
8094 );
8095 }
8096
8097 fn open_editor_at_anchor(
8098 snapshot: &language::BufferSnapshot,
8099 target: language::Anchor,
8100 workspace: &Entity<Workspace>,
8101 window: &mut Window,
8102 cx: &mut App,
8103 ) -> Task<Result<()>> {
8104 workspace.update(cx, |workspace, cx| {
8105 let path = snapshot.file().map(|file| file.full_path(cx));
8106 let Some(path) =
8107 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
8108 else {
8109 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
8110 };
8111 let target = text::ToPoint::to_point(&target, snapshot);
8112 let item = workspace.open_path(path, None, true, window, cx);
8113 window.spawn(cx, async move |cx| {
8114 let Some(editor) = item.await?.downcast::<Editor>() else {
8115 return Ok(());
8116 };
8117 editor
8118 .update_in(cx, |editor, window, cx| {
8119 editor.go_to_singleton_buffer_point(target, window, cx);
8120 })
8121 .ok();
8122 anyhow::Ok(())
8123 })
8124 })
8125 }
8126
8127 pub fn has_active_edit_prediction(&self) -> bool {
8128 self.active_edit_prediction.is_some()
8129 }
8130
8131 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
8132 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
8133 return false;
8134 };
8135
8136 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
8137 self.clear_highlights::<EditPredictionHighlight>(cx);
8138 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
8139 true
8140 }
8141
8142 /// Returns true when we're displaying the edit prediction popover below the cursor
8143 /// like we are not previewing and the LSP autocomplete menu is visible
8144 /// or we are in `when_holding_modifier` mode.
8145 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
8146 if self.edit_prediction_preview_is_active()
8147 || !self.show_edit_predictions_in_menu()
8148 || !self.edit_predictions_enabled()
8149 {
8150 return false;
8151 }
8152
8153 if self.has_visible_completions_menu() {
8154 return true;
8155 }
8156
8157 has_completion && self.edit_prediction_requires_modifier()
8158 }
8159
8160 fn handle_modifiers_changed(
8161 &mut self,
8162 modifiers: Modifiers,
8163 position_map: &PositionMap,
8164 window: &mut Window,
8165 cx: &mut Context<Self>,
8166 ) {
8167 // Ensure that the edit prediction preview is updated, even when not
8168 // enabled, if there's an active edit prediction preview.
8169 if self.show_edit_predictions_in_menu()
8170 || matches!(
8171 self.edit_prediction_preview,
8172 EditPredictionPreview::Active { .. }
8173 )
8174 {
8175 self.update_edit_prediction_preview(&modifiers, window, cx);
8176 }
8177
8178 self.update_selection_mode(&modifiers, position_map, window, cx);
8179
8180 let mouse_position = window.mouse_position();
8181 if !position_map.text_hitbox.is_hovered(window) {
8182 return;
8183 }
8184
8185 self.update_hovered_link(
8186 position_map.point_for_position(mouse_position),
8187 &position_map.snapshot,
8188 modifiers,
8189 window,
8190 cx,
8191 )
8192 }
8193
8194 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8195 match EditorSettings::get_global(cx).multi_cursor_modifier {
8196 MultiCursorModifier::Alt => modifiers.secondary(),
8197 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
8198 }
8199 }
8200
8201 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8202 match EditorSettings::get_global(cx).multi_cursor_modifier {
8203 MultiCursorModifier::Alt => modifiers.alt,
8204 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
8205 }
8206 }
8207
8208 fn columnar_selection_mode(
8209 modifiers: &Modifiers,
8210 cx: &mut Context<Self>,
8211 ) -> Option<ColumnarMode> {
8212 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
8213 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
8214 Some(ColumnarMode::FromMouse)
8215 } else if Self::is_alt_pressed(modifiers, cx) {
8216 Some(ColumnarMode::FromSelection)
8217 } else {
8218 None
8219 }
8220 } else {
8221 None
8222 }
8223 }
8224
8225 fn update_selection_mode(
8226 &mut self,
8227 modifiers: &Modifiers,
8228 position_map: &PositionMap,
8229 window: &mut Window,
8230 cx: &mut Context<Self>,
8231 ) {
8232 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
8233 return;
8234 };
8235 if self.selections.pending_anchor().is_none() {
8236 return;
8237 }
8238
8239 let mouse_position = window.mouse_position();
8240 let point_for_position = position_map.point_for_position(mouse_position);
8241 let position = point_for_position.previous_valid;
8242
8243 self.select(
8244 SelectPhase::BeginColumnar {
8245 position,
8246 reset: false,
8247 mode,
8248 goal_column: point_for_position.exact_unclipped.column(),
8249 },
8250 window,
8251 cx,
8252 );
8253 }
8254
8255 fn update_edit_prediction_preview(
8256 &mut self,
8257 modifiers: &Modifiers,
8258 window: &mut Window,
8259 cx: &mut Context<Self>,
8260 ) {
8261 let mut modifiers_held = false;
8262
8263 // Check bindings for all granularities.
8264 // If the user holds the key for Word, Line, or Full, we want to show the preview.
8265 let granularities = [
8266 EditPredictionGranularity::Full,
8267 EditPredictionGranularity::Line,
8268 EditPredictionGranularity::Word,
8269 ];
8270
8271 for granularity in granularities {
8272 if let Some(keystroke) = self
8273 .accept_edit_prediction_keybind(granularity, window, cx)
8274 .keystroke()
8275 {
8276 modifiers_held = modifiers_held
8277 || (keystroke.modifiers() == modifiers && keystroke.modifiers().modified());
8278 }
8279 }
8280
8281 if modifiers_held {
8282 if matches!(
8283 self.edit_prediction_preview,
8284 EditPredictionPreview::Inactive { .. }
8285 ) {
8286 self.edit_prediction_preview = EditPredictionPreview::Active {
8287 previous_scroll_position: None,
8288 since: Instant::now(),
8289 };
8290
8291 self.update_visible_edit_prediction(window, cx);
8292 cx.notify();
8293 }
8294 } else if let EditPredictionPreview::Active {
8295 previous_scroll_position,
8296 since,
8297 } = self.edit_prediction_preview
8298 {
8299 if let (Some(previous_scroll_position), Some(position_map)) =
8300 (previous_scroll_position, self.last_position_map.as_ref())
8301 {
8302 self.set_scroll_position(
8303 previous_scroll_position
8304 .scroll_position(&position_map.snapshot.display_snapshot),
8305 window,
8306 cx,
8307 );
8308 }
8309
8310 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8311 released_too_fast: since.elapsed() < Duration::from_millis(200),
8312 };
8313 self.clear_row_highlights::<EditPredictionPreview>();
8314 self.update_visible_edit_prediction(window, cx);
8315 cx.notify();
8316 }
8317 }
8318
8319 fn update_visible_edit_prediction(
8320 &mut self,
8321 _window: &mut Window,
8322 cx: &mut Context<Self>,
8323 ) -> Option<()> {
8324 if DisableAiSettings::get_global(cx).disable_ai {
8325 return None;
8326 }
8327
8328 if self.ime_transaction.is_some() {
8329 self.discard_edit_prediction(false, cx);
8330 return None;
8331 }
8332
8333 let selection = self.selections.newest_anchor();
8334 let cursor = selection.head();
8335 let multibuffer = self.buffer.read(cx).snapshot(cx);
8336 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8337 let excerpt_id = cursor.excerpt_id;
8338
8339 let show_in_menu = self.show_edit_predictions_in_menu();
8340 let completions_menu_has_precedence = !show_in_menu
8341 && (self.context_menu.borrow().is_some()
8342 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8343
8344 if completions_menu_has_precedence
8345 || !offset_selection.is_empty()
8346 || self
8347 .active_edit_prediction
8348 .as_ref()
8349 .is_some_and(|completion| {
8350 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8351 return false;
8352 };
8353 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8354 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8355 !invalidation_range.contains(&offset_selection.head())
8356 })
8357 {
8358 self.discard_edit_prediction(false, cx);
8359 return None;
8360 }
8361
8362 self.take_active_edit_prediction(cx);
8363 let Some(provider) = self.edit_prediction_provider() else {
8364 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8365 return None;
8366 };
8367
8368 let (buffer, cursor_buffer_position) =
8369 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8370
8371 self.edit_prediction_settings =
8372 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8373
8374 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
8375
8376 if self.edit_prediction_indent_conflict {
8377 let cursor_point = cursor.to_point(&multibuffer);
8378 let mut suggested_indent = None;
8379 multibuffer.suggested_indents_callback(
8380 cursor_point.row..cursor_point.row + 1,
8381 |_, indent| {
8382 suggested_indent = Some(indent);
8383 ControlFlow::Break(())
8384 },
8385 cx,
8386 );
8387
8388 if let Some(indent) = suggested_indent
8389 && indent.len == cursor_point.column
8390 {
8391 self.edit_prediction_indent_conflict = false;
8392 }
8393 }
8394
8395 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8396
8397 let (completion_id, edits, predicted_cursor_position, edit_preview) = match edit_prediction
8398 {
8399 edit_prediction_types::EditPrediction::Local {
8400 id,
8401 edits,
8402 cursor_position,
8403 edit_preview,
8404 } => (id, edits, cursor_position, edit_preview),
8405 edit_prediction_types::EditPrediction::Jump {
8406 id,
8407 snapshot,
8408 target,
8409 } => {
8410 if let Some(provider) = &self.edit_prediction_provider {
8411 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8412 }
8413 self.stale_edit_prediction_in_menu = None;
8414 self.active_edit_prediction = Some(EditPredictionState {
8415 inlay_ids: vec![],
8416 completion: EditPrediction::MoveOutside { snapshot, target },
8417 completion_id: id,
8418 invalidation_range: None,
8419 });
8420 cx.notify();
8421 return Some(());
8422 }
8423 };
8424
8425 let edits = edits
8426 .into_iter()
8427 .flat_map(|(range, new_text)| {
8428 Some((
8429 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8430 new_text,
8431 ))
8432 })
8433 .collect::<Vec<_>>();
8434 if edits.is_empty() {
8435 return None;
8436 }
8437
8438 let cursor_position = predicted_cursor_position.and_then(|predicted| {
8439 let anchor = multibuffer.anchor_in_excerpt(excerpt_id, predicted.anchor)?;
8440 Some((anchor, predicted.offset))
8441 });
8442
8443 let first_edit_start = edits.first().unwrap().0.start;
8444 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8445 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8446
8447 let last_edit_end = edits.last().unwrap().0.end;
8448 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8449 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8450
8451 let cursor_row = cursor.to_point(&multibuffer).row;
8452
8453 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8454
8455 let mut inlay_ids = Vec::new();
8456 let invalidation_row_range;
8457 let move_invalidation_row_range = if cursor_row < edit_start_row {
8458 Some(cursor_row..edit_end_row)
8459 } else if cursor_row > edit_end_row {
8460 Some(edit_start_row..cursor_row)
8461 } else {
8462 None
8463 };
8464 let supports_jump = self
8465 .edit_prediction_provider
8466 .as_ref()
8467 .map(|provider| provider.provider.supports_jump_to_edit())
8468 .unwrap_or(true);
8469
8470 let is_move = supports_jump
8471 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8472 let completion = if is_move {
8473 if let Some(provider) = &self.edit_prediction_provider {
8474 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8475 }
8476 invalidation_row_range =
8477 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8478 let target = first_edit_start;
8479 EditPrediction::MoveWithin { target, snapshot }
8480 } else {
8481 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8482 && !self.edit_predictions_hidden_for_vim_mode;
8483
8484 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8485 if provider.show_tab_accept_marker() {
8486 EditDisplayMode::TabAccept
8487 } else {
8488 EditDisplayMode::Inline
8489 }
8490 } else {
8491 EditDisplayMode::DiffPopover
8492 };
8493
8494 if show_completions_in_buffer {
8495 if let Some(provider) = &self.edit_prediction_provider {
8496 let suggestion_display_type = match display_mode {
8497 EditDisplayMode::DiffPopover => SuggestionDisplayType::DiffPopover,
8498 EditDisplayMode::Inline | EditDisplayMode::TabAccept => {
8499 SuggestionDisplayType::GhostText
8500 }
8501 };
8502 provider.provider.did_show(suggestion_display_type, cx);
8503 }
8504 if edits
8505 .iter()
8506 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8507 {
8508 let mut inlays = Vec::new();
8509 for (range, new_text) in &edits {
8510 let inlay = Inlay::edit_prediction(
8511 post_inc(&mut self.next_inlay_id),
8512 range.start,
8513 new_text.as_ref(),
8514 );
8515 inlay_ids.push(inlay.id);
8516 inlays.push(inlay);
8517 }
8518
8519 self.splice_inlays(&[], inlays, cx);
8520 } else {
8521 let background_color = cx.theme().status().deleted_background;
8522 self.highlight_text::<EditPredictionHighlight>(
8523 edits.iter().map(|(range, _)| range.clone()).collect(),
8524 HighlightStyle {
8525 background_color: Some(background_color),
8526 ..Default::default()
8527 },
8528 cx,
8529 );
8530 }
8531 }
8532
8533 invalidation_row_range = edit_start_row..edit_end_row;
8534
8535 EditPrediction::Edit {
8536 edits,
8537 cursor_position,
8538 edit_preview,
8539 display_mode,
8540 snapshot,
8541 }
8542 };
8543
8544 let invalidation_range = multibuffer
8545 .anchor_before(Point::new(invalidation_row_range.start, 0))
8546 ..multibuffer.anchor_after(Point::new(
8547 invalidation_row_range.end,
8548 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8549 ));
8550
8551 self.stale_edit_prediction_in_menu = None;
8552 self.active_edit_prediction = Some(EditPredictionState {
8553 inlay_ids,
8554 completion,
8555 completion_id,
8556 invalidation_range: Some(invalidation_range),
8557 });
8558
8559 cx.notify();
8560
8561 Some(())
8562 }
8563
8564 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8565 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8566 }
8567
8568 fn clear_tasks(&mut self) {
8569 self.tasks.clear()
8570 }
8571
8572 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8573 if self.tasks.insert(key, value).is_some() {
8574 // This case should hopefully be rare, but just in case...
8575 log::error!(
8576 "multiple different run targets found on a single line, only the last target will be rendered"
8577 )
8578 }
8579 }
8580
8581 /// Get all display points of breakpoints that will be rendered within editor
8582 ///
8583 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8584 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8585 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8586 fn active_breakpoints(
8587 &self,
8588 range: Range<DisplayRow>,
8589 window: &mut Window,
8590 cx: &mut Context<Self>,
8591 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8592 let mut breakpoint_display_points = HashMap::default();
8593
8594 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8595 return breakpoint_display_points;
8596 };
8597
8598 let snapshot = self.snapshot(window, cx);
8599
8600 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8601 let Some(project) = self.project() else {
8602 return breakpoint_display_points;
8603 };
8604
8605 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8606 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8607
8608 for (buffer_snapshot, range, excerpt_id) in
8609 multi_buffer_snapshot.range_to_buffer_ranges(range.start..=range.end)
8610 {
8611 let Some(buffer) = project
8612 .read(cx)
8613 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8614 else {
8615 continue;
8616 };
8617 let breakpoints = breakpoint_store.read(cx).breakpoints(
8618 &buffer,
8619 Some(
8620 buffer_snapshot.anchor_before(range.start)
8621 ..buffer_snapshot.anchor_after(range.end),
8622 ),
8623 buffer_snapshot,
8624 cx,
8625 );
8626 for (breakpoint, state) in breakpoints {
8627 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8628 let position = multi_buffer_anchor
8629 .to_point(&multi_buffer_snapshot)
8630 .to_display_point(&snapshot);
8631
8632 breakpoint_display_points.insert(
8633 position.row(),
8634 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8635 );
8636 }
8637 }
8638
8639 breakpoint_display_points
8640 }
8641
8642 fn breakpoint_context_menu(
8643 &self,
8644 anchor: Anchor,
8645 window: &mut Window,
8646 cx: &mut Context<Self>,
8647 ) -> Entity<ui::ContextMenu> {
8648 let weak_editor = cx.weak_entity();
8649 let focus_handle = self.focus_handle(cx);
8650
8651 let row = self
8652 .buffer
8653 .read(cx)
8654 .snapshot(cx)
8655 .summary_for_anchor::<Point>(&anchor)
8656 .row;
8657
8658 let breakpoint = self
8659 .breakpoint_at_row(row, window, cx)
8660 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8661
8662 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8663 "Edit Log Breakpoint"
8664 } else {
8665 "Set Log Breakpoint"
8666 };
8667
8668 let condition_breakpoint_msg = if breakpoint
8669 .as_ref()
8670 .is_some_and(|bp| bp.1.condition.is_some())
8671 {
8672 "Edit Condition Breakpoint"
8673 } else {
8674 "Set Condition Breakpoint"
8675 };
8676
8677 let hit_condition_breakpoint_msg = if breakpoint
8678 .as_ref()
8679 .is_some_and(|bp| bp.1.hit_condition.is_some())
8680 {
8681 "Edit Hit Condition Breakpoint"
8682 } else {
8683 "Set Hit Condition Breakpoint"
8684 };
8685
8686 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8687 "Unset Breakpoint"
8688 } else {
8689 "Set Breakpoint"
8690 };
8691
8692 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8693
8694 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8695 BreakpointState::Enabled => Some("Disable"),
8696 BreakpointState::Disabled => Some("Enable"),
8697 });
8698
8699 let (anchor, breakpoint) =
8700 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8701
8702 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8703 menu.on_blur_subscription(Subscription::new(|| {}))
8704 .context(focus_handle)
8705 .when(run_to_cursor, |this| {
8706 let weak_editor = weak_editor.clone();
8707 this.entry("Run to cursor", None, move |window, cx| {
8708 weak_editor
8709 .update(cx, |editor, cx| {
8710 editor.change_selections(
8711 SelectionEffects::no_scroll(),
8712 window,
8713 cx,
8714 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8715 );
8716 })
8717 .ok();
8718
8719 window.dispatch_action(Box::new(RunToCursor), cx);
8720 })
8721 .separator()
8722 })
8723 .when_some(toggle_state_msg, |this, msg| {
8724 this.entry(msg, None, {
8725 let weak_editor = weak_editor.clone();
8726 let breakpoint = breakpoint.clone();
8727 move |_window, cx| {
8728 weak_editor
8729 .update(cx, |this, cx| {
8730 this.edit_breakpoint_at_anchor(
8731 anchor,
8732 breakpoint.as_ref().clone(),
8733 BreakpointEditAction::InvertState,
8734 cx,
8735 );
8736 })
8737 .log_err();
8738 }
8739 })
8740 })
8741 .entry(set_breakpoint_msg, None, {
8742 let weak_editor = weak_editor.clone();
8743 let breakpoint = breakpoint.clone();
8744 move |_window, cx| {
8745 weak_editor
8746 .update(cx, |this, cx| {
8747 this.edit_breakpoint_at_anchor(
8748 anchor,
8749 breakpoint.as_ref().clone(),
8750 BreakpointEditAction::Toggle,
8751 cx,
8752 );
8753 })
8754 .log_err();
8755 }
8756 })
8757 .entry(log_breakpoint_msg, None, {
8758 let breakpoint = breakpoint.clone();
8759 let weak_editor = weak_editor.clone();
8760 move |window, cx| {
8761 weak_editor
8762 .update(cx, |this, cx| {
8763 this.add_edit_breakpoint_block(
8764 anchor,
8765 breakpoint.as_ref(),
8766 BreakpointPromptEditAction::Log,
8767 window,
8768 cx,
8769 );
8770 })
8771 .log_err();
8772 }
8773 })
8774 .entry(condition_breakpoint_msg, None, {
8775 let breakpoint = breakpoint.clone();
8776 let weak_editor = weak_editor.clone();
8777 move |window, cx| {
8778 weak_editor
8779 .update(cx, |this, cx| {
8780 this.add_edit_breakpoint_block(
8781 anchor,
8782 breakpoint.as_ref(),
8783 BreakpointPromptEditAction::Condition,
8784 window,
8785 cx,
8786 );
8787 })
8788 .log_err();
8789 }
8790 })
8791 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8792 weak_editor
8793 .update(cx, |this, cx| {
8794 this.add_edit_breakpoint_block(
8795 anchor,
8796 breakpoint.as_ref(),
8797 BreakpointPromptEditAction::HitCondition,
8798 window,
8799 cx,
8800 );
8801 })
8802 .log_err();
8803 })
8804 })
8805 }
8806
8807 fn render_breakpoint(
8808 &self,
8809 position: Anchor,
8810 row: DisplayRow,
8811 breakpoint: &Breakpoint,
8812 state: Option<BreakpointSessionState>,
8813 cx: &mut Context<Self>,
8814 ) -> IconButton {
8815 let is_rejected = state.is_some_and(|s| !s.verified);
8816 // Is it a breakpoint that shows up when hovering over gutter?
8817 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8818 (false, false),
8819 |PhantomBreakpointIndicator {
8820 is_active,
8821 display_row,
8822 collides_with_existing_breakpoint,
8823 }| {
8824 (
8825 is_active && display_row == row,
8826 collides_with_existing_breakpoint,
8827 )
8828 },
8829 );
8830
8831 let (color, icon) = {
8832 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8833 (false, false) => ui::IconName::DebugBreakpoint,
8834 (true, false) => ui::IconName::DebugLogBreakpoint,
8835 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8836 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8837 };
8838
8839 let theme_colors = cx.theme().colors();
8840
8841 let color = if is_phantom {
8842 if collides_with_existing {
8843 Color::Custom(
8844 theme_colors
8845 .debugger_accent
8846 .blend(theme_colors.text.opacity(0.6)),
8847 )
8848 } else {
8849 Color::Hint
8850 }
8851 } else if is_rejected {
8852 Color::Disabled
8853 } else {
8854 Color::Debugger
8855 };
8856
8857 (color, icon)
8858 };
8859
8860 let breakpoint = Arc::from(breakpoint.clone());
8861
8862 let alt_as_text = gpui::Keystroke {
8863 modifiers: Modifiers::secondary_key(),
8864 ..Default::default()
8865 };
8866 let primary_action_text = if breakpoint.is_disabled() {
8867 "Enable breakpoint"
8868 } else if is_phantom && !collides_with_existing {
8869 "Set breakpoint"
8870 } else {
8871 "Unset breakpoint"
8872 };
8873 let focus_handle = self.focus_handle.clone();
8874
8875 let meta = if is_rejected {
8876 SharedString::from("No executable code is associated with this line.")
8877 } else if collides_with_existing && !breakpoint.is_disabled() {
8878 SharedString::from(format!(
8879 "{alt_as_text}-click to disable,\nright-click for more options."
8880 ))
8881 } else {
8882 SharedString::from("Right-click for more options.")
8883 };
8884 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8885 .icon_size(IconSize::XSmall)
8886 .size(ui::ButtonSize::None)
8887 .when(is_rejected, |this| {
8888 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8889 })
8890 .icon_color(color)
8891 .style(ButtonStyle::Transparent)
8892 .on_click(cx.listener({
8893 move |editor, event: &ClickEvent, window, cx| {
8894 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8895 BreakpointEditAction::InvertState
8896 } else {
8897 BreakpointEditAction::Toggle
8898 };
8899
8900 window.focus(&editor.focus_handle(cx), cx);
8901 editor.edit_breakpoint_at_anchor(
8902 position,
8903 breakpoint.as_ref().clone(),
8904 edit_action,
8905 cx,
8906 );
8907 }
8908 }))
8909 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8910 editor.set_breakpoint_context_menu(
8911 row,
8912 Some(position),
8913 event.position(),
8914 window,
8915 cx,
8916 );
8917 }))
8918 .tooltip(move |_window, cx| {
8919 Tooltip::with_meta_in(
8920 primary_action_text,
8921 Some(&ToggleBreakpoint),
8922 meta.clone(),
8923 &focus_handle,
8924 cx,
8925 )
8926 })
8927 }
8928
8929 fn build_tasks_context(
8930 project: &Entity<Project>,
8931 buffer: &Entity<Buffer>,
8932 buffer_row: u32,
8933 tasks: &Arc<RunnableTasks>,
8934 cx: &mut Context<Self>,
8935 ) -> Task<Option<task::TaskContext>> {
8936 let position = Point::new(buffer_row, tasks.column);
8937 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8938 let location = Location {
8939 buffer: buffer.clone(),
8940 range: range_start..range_start,
8941 };
8942 // Fill in the environmental variables from the tree-sitter captures
8943 let mut captured_task_variables = TaskVariables::default();
8944 for (capture_name, value) in tasks.extra_variables.clone() {
8945 captured_task_variables.insert(
8946 task::VariableName::Custom(capture_name.into()),
8947 value.clone(),
8948 );
8949 }
8950 project.update(cx, |project, cx| {
8951 project.task_store().update(cx, |task_store, cx| {
8952 task_store.task_context_for_location(captured_task_variables, location, cx)
8953 })
8954 })
8955 }
8956
8957 pub fn spawn_nearest_task(
8958 &mut self,
8959 action: &SpawnNearestTask,
8960 window: &mut Window,
8961 cx: &mut Context<Self>,
8962 ) {
8963 let Some((workspace, _)) = self.workspace.clone() else {
8964 return;
8965 };
8966 let Some(project) = self.project.clone() else {
8967 return;
8968 };
8969
8970 // Try to find a closest, enclosing node using tree-sitter that has a task
8971 let Some((buffer, buffer_row, tasks)) = self
8972 .find_enclosing_node_task(cx)
8973 // Or find the task that's closest in row-distance.
8974 .or_else(|| self.find_closest_task(cx))
8975 else {
8976 return;
8977 };
8978
8979 let reveal_strategy = action.reveal;
8980 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8981 cx.spawn_in(window, async move |_, cx| {
8982 let context = task_context.await?;
8983 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8984
8985 let resolved = &mut resolved_task.resolved;
8986 resolved.reveal = reveal_strategy;
8987
8988 workspace
8989 .update_in(cx, |workspace, window, cx| {
8990 workspace.schedule_resolved_task(
8991 task_source_kind,
8992 resolved_task,
8993 false,
8994 window,
8995 cx,
8996 );
8997 })
8998 .ok()
8999 })
9000 .detach();
9001 }
9002
9003 fn find_closest_task(
9004 &mut self,
9005 cx: &mut Context<Self>,
9006 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
9007 let cursor_row = self
9008 .selections
9009 .newest_adjusted(&self.display_snapshot(cx))
9010 .head()
9011 .row;
9012
9013 let ((buffer_id, row), tasks) = self
9014 .tasks
9015 .iter()
9016 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
9017
9018 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
9019 let tasks = Arc::new(tasks.to_owned());
9020 Some((buffer, *row, tasks))
9021 }
9022
9023 fn find_enclosing_node_task(
9024 &mut self,
9025 cx: &mut Context<Self>,
9026 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
9027 let snapshot = self.buffer.read(cx).snapshot(cx);
9028 let offset = self
9029 .selections
9030 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
9031 .head();
9032 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
9033 let offset = excerpt.map_offset_to_buffer(offset);
9034 let buffer_id = excerpt.buffer().remote_id();
9035
9036 let layer = excerpt.buffer().syntax_layer_at(offset)?;
9037 let mut cursor = layer.node().walk();
9038
9039 while cursor.goto_first_child_for_byte(offset.0).is_some() {
9040 if cursor.node().end_byte() == offset.0 {
9041 cursor.goto_next_sibling();
9042 }
9043 }
9044
9045 // Ascend to the smallest ancestor that contains the range and has a task.
9046 loop {
9047 let node = cursor.node();
9048 let node_range = node.byte_range();
9049 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
9050
9051 // Check if this node contains our offset
9052 if node_range.start <= offset.0 && node_range.end >= offset.0 {
9053 // If it contains offset, check for task
9054 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
9055 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
9056 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
9057 }
9058 }
9059
9060 if !cursor.goto_parent() {
9061 break;
9062 }
9063 }
9064 None
9065 }
9066
9067 fn render_run_indicator(
9068 &self,
9069 _style: &EditorStyle,
9070 is_active: bool,
9071 row: DisplayRow,
9072 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
9073 cx: &mut Context<Self>,
9074 ) -> IconButton {
9075 let color = Color::Muted;
9076 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
9077
9078 IconButton::new(
9079 ("run_indicator", row.0 as usize),
9080 ui::IconName::PlayOutlined,
9081 )
9082 .shape(ui::IconButtonShape::Square)
9083 .icon_size(IconSize::XSmall)
9084 .icon_color(color)
9085 .toggle_state(is_active)
9086 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
9087 let quick_launch = match e {
9088 ClickEvent::Keyboard(_) => true,
9089 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
9090 };
9091
9092 window.focus(&editor.focus_handle(cx), cx);
9093 editor.toggle_code_actions(
9094 &ToggleCodeActions {
9095 deployed_from: Some(CodeActionSource::RunMenu(row)),
9096 quick_launch,
9097 },
9098 window,
9099 cx,
9100 );
9101 }))
9102 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
9103 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
9104 }))
9105 }
9106
9107 pub fn context_menu_visible(&self) -> bool {
9108 !self.edit_prediction_preview_is_active()
9109 && self
9110 .context_menu
9111 .borrow()
9112 .as_ref()
9113 .is_some_and(|menu| menu.visible())
9114 }
9115
9116 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
9117 self.context_menu
9118 .borrow()
9119 .as_ref()
9120 .map(|menu| menu.origin())
9121 }
9122
9123 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
9124 self.context_menu_options = Some(options);
9125 }
9126
9127 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
9128 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
9129
9130 fn render_edit_prediction_popover(
9131 &mut self,
9132 text_bounds: &Bounds<Pixels>,
9133 content_origin: gpui::Point<Pixels>,
9134 right_margin: Pixels,
9135 editor_snapshot: &EditorSnapshot,
9136 visible_row_range: Range<DisplayRow>,
9137 scroll_top: ScrollOffset,
9138 scroll_bottom: ScrollOffset,
9139 line_layouts: &[LineWithInvisibles],
9140 line_height: Pixels,
9141 scroll_position: gpui::Point<ScrollOffset>,
9142 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9143 newest_selection_head: Option<DisplayPoint>,
9144 editor_width: Pixels,
9145 style: &EditorStyle,
9146 window: &mut Window,
9147 cx: &mut App,
9148 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9149 if self.mode().is_minimap() {
9150 return None;
9151 }
9152 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
9153
9154 if self.edit_prediction_visible_in_cursor_popover(true) {
9155 return None;
9156 }
9157
9158 match &active_edit_prediction.completion {
9159 EditPrediction::MoveWithin { target, .. } => {
9160 let target_display_point = target.to_display_point(editor_snapshot);
9161
9162 if self.edit_prediction_requires_modifier() {
9163 if !self.edit_prediction_preview_is_active() {
9164 return None;
9165 }
9166
9167 self.render_edit_prediction_modifier_jump_popover(
9168 text_bounds,
9169 content_origin,
9170 visible_row_range,
9171 line_layouts,
9172 line_height,
9173 scroll_pixel_position,
9174 newest_selection_head,
9175 target_display_point,
9176 window,
9177 cx,
9178 )
9179 } else {
9180 self.render_edit_prediction_eager_jump_popover(
9181 text_bounds,
9182 content_origin,
9183 editor_snapshot,
9184 visible_row_range,
9185 scroll_top,
9186 scroll_bottom,
9187 line_height,
9188 scroll_pixel_position,
9189 target_display_point,
9190 editor_width,
9191 window,
9192 cx,
9193 )
9194 }
9195 }
9196 EditPrediction::Edit {
9197 display_mode: EditDisplayMode::Inline,
9198 ..
9199 } => None,
9200 EditPrediction::Edit {
9201 display_mode: EditDisplayMode::TabAccept,
9202 edits,
9203 ..
9204 } => {
9205 let range = &edits.first()?.0;
9206 let target_display_point = range.end.to_display_point(editor_snapshot);
9207
9208 self.render_edit_prediction_end_of_line_popover(
9209 "Accept",
9210 editor_snapshot,
9211 visible_row_range,
9212 target_display_point,
9213 line_height,
9214 scroll_pixel_position,
9215 content_origin,
9216 editor_width,
9217 window,
9218 cx,
9219 )
9220 }
9221 EditPrediction::Edit {
9222 edits,
9223 edit_preview,
9224 display_mode: EditDisplayMode::DiffPopover,
9225 snapshot,
9226 ..
9227 } => self.render_edit_prediction_diff_popover(
9228 text_bounds,
9229 content_origin,
9230 right_margin,
9231 editor_snapshot,
9232 visible_row_range,
9233 line_layouts,
9234 line_height,
9235 scroll_position,
9236 scroll_pixel_position,
9237 newest_selection_head,
9238 editor_width,
9239 style,
9240 edits,
9241 edit_preview,
9242 snapshot,
9243 window,
9244 cx,
9245 ),
9246 EditPrediction::MoveOutside { snapshot, .. } => {
9247 let mut element = self
9248 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
9249 .into_any();
9250
9251 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9252 let origin_x = text_bounds.size.width - size.width - px(30.);
9253 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
9254 element.prepaint_at(origin, window, cx);
9255
9256 Some((element, origin))
9257 }
9258 }
9259 }
9260
9261 fn render_edit_prediction_modifier_jump_popover(
9262 &mut self,
9263 text_bounds: &Bounds<Pixels>,
9264 content_origin: gpui::Point<Pixels>,
9265 visible_row_range: Range<DisplayRow>,
9266 line_layouts: &[LineWithInvisibles],
9267 line_height: Pixels,
9268 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9269 newest_selection_head: Option<DisplayPoint>,
9270 target_display_point: DisplayPoint,
9271 window: &mut Window,
9272 cx: &mut App,
9273 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9274 let scrolled_content_origin =
9275 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
9276
9277 const SCROLL_PADDING_Y: Pixels = px(12.);
9278
9279 if target_display_point.row() < visible_row_range.start {
9280 return self.render_edit_prediction_scroll_popover(
9281 |_| SCROLL_PADDING_Y,
9282 IconName::ArrowUp,
9283 visible_row_range,
9284 line_layouts,
9285 newest_selection_head,
9286 scrolled_content_origin,
9287 window,
9288 cx,
9289 );
9290 } else if target_display_point.row() >= visible_row_range.end {
9291 return self.render_edit_prediction_scroll_popover(
9292 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9293 IconName::ArrowDown,
9294 visible_row_range,
9295 line_layouts,
9296 newest_selection_head,
9297 scrolled_content_origin,
9298 window,
9299 cx,
9300 );
9301 }
9302
9303 const POLE_WIDTH: Pixels = px(2.);
9304
9305 let line_layout =
9306 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9307 let target_column = target_display_point.column() as usize;
9308
9309 let target_x = line_layout.x_for_index(target_column);
9310 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9311 - scroll_pixel_position.y;
9312
9313 let flag_on_right = target_x < text_bounds.size.width / 2.;
9314
9315 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9316 border_color.l += 0.001;
9317
9318 let mut element = v_flex()
9319 .items_end()
9320 .when(flag_on_right, |el| el.items_start())
9321 .child(if flag_on_right {
9322 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9323 .rounded_bl(px(0.))
9324 .rounded_tl(px(0.))
9325 .border_l_2()
9326 .border_color(border_color)
9327 } else {
9328 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9329 .rounded_br(px(0.))
9330 .rounded_tr(px(0.))
9331 .border_r_2()
9332 .border_color(border_color)
9333 })
9334 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9335 .into_any();
9336
9337 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9338
9339 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9340 - point(
9341 if flag_on_right {
9342 POLE_WIDTH
9343 } else {
9344 size.width - POLE_WIDTH
9345 },
9346 size.height - line_height,
9347 );
9348
9349 origin.x = origin.x.max(content_origin.x);
9350
9351 element.prepaint_at(origin, window, cx);
9352
9353 Some((element, origin))
9354 }
9355
9356 fn render_edit_prediction_scroll_popover(
9357 &mut self,
9358 to_y: impl Fn(Size<Pixels>) -> Pixels,
9359 scroll_icon: IconName,
9360 visible_row_range: Range<DisplayRow>,
9361 line_layouts: &[LineWithInvisibles],
9362 newest_selection_head: Option<DisplayPoint>,
9363 scrolled_content_origin: gpui::Point<Pixels>,
9364 window: &mut Window,
9365 cx: &mut App,
9366 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9367 let mut element = self
9368 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9369 .into_any();
9370
9371 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9372
9373 let cursor = newest_selection_head?;
9374 let cursor_row_layout =
9375 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9376 let cursor_column = cursor.column() as usize;
9377
9378 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9379
9380 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9381
9382 element.prepaint_at(origin, window, cx);
9383 Some((element, origin))
9384 }
9385
9386 fn render_edit_prediction_eager_jump_popover(
9387 &mut self,
9388 text_bounds: &Bounds<Pixels>,
9389 content_origin: gpui::Point<Pixels>,
9390 editor_snapshot: &EditorSnapshot,
9391 visible_row_range: Range<DisplayRow>,
9392 scroll_top: ScrollOffset,
9393 scroll_bottom: ScrollOffset,
9394 line_height: Pixels,
9395 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9396 target_display_point: DisplayPoint,
9397 editor_width: Pixels,
9398 window: &mut Window,
9399 cx: &mut App,
9400 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9401 if target_display_point.row().as_f64() < scroll_top {
9402 let mut element = self
9403 .render_edit_prediction_line_popover(
9404 "Jump to Edit",
9405 Some(IconName::ArrowUp),
9406 window,
9407 cx,
9408 )
9409 .into_any();
9410
9411 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9412 let offset = point(
9413 (text_bounds.size.width - size.width) / 2.,
9414 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9415 );
9416
9417 let origin = text_bounds.origin + offset;
9418 element.prepaint_at(origin, window, cx);
9419 Some((element, origin))
9420 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9421 let mut element = self
9422 .render_edit_prediction_line_popover(
9423 "Jump to Edit",
9424 Some(IconName::ArrowDown),
9425 window,
9426 cx,
9427 )
9428 .into_any();
9429
9430 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9431 let offset = point(
9432 (text_bounds.size.width - size.width) / 2.,
9433 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9434 );
9435
9436 let origin = text_bounds.origin + offset;
9437 element.prepaint_at(origin, window, cx);
9438 Some((element, origin))
9439 } else {
9440 self.render_edit_prediction_end_of_line_popover(
9441 "Jump to Edit",
9442 editor_snapshot,
9443 visible_row_range,
9444 target_display_point,
9445 line_height,
9446 scroll_pixel_position,
9447 content_origin,
9448 editor_width,
9449 window,
9450 cx,
9451 )
9452 }
9453 }
9454
9455 fn render_edit_prediction_end_of_line_popover(
9456 self: &mut Editor,
9457 label: &'static str,
9458 editor_snapshot: &EditorSnapshot,
9459 visible_row_range: Range<DisplayRow>,
9460 target_display_point: DisplayPoint,
9461 line_height: Pixels,
9462 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9463 content_origin: gpui::Point<Pixels>,
9464 editor_width: Pixels,
9465 window: &mut Window,
9466 cx: &mut App,
9467 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9468 let target_line_end = DisplayPoint::new(
9469 target_display_point.row(),
9470 editor_snapshot.line_len(target_display_point.row()),
9471 );
9472
9473 let mut element = self
9474 .render_edit_prediction_line_popover(label, None, window, cx)
9475 .into_any();
9476
9477 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9478
9479 let line_origin =
9480 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9481
9482 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9483 let mut origin = start_point
9484 + line_origin
9485 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9486 origin.x = origin.x.max(content_origin.x);
9487
9488 let max_x = content_origin.x + editor_width - size.width;
9489
9490 if origin.x > max_x {
9491 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9492
9493 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9494 origin.y += offset;
9495 IconName::ArrowUp
9496 } else {
9497 origin.y -= offset;
9498 IconName::ArrowDown
9499 };
9500
9501 element = self
9502 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9503 .into_any();
9504
9505 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9506
9507 origin.x = content_origin.x + editor_width - size.width - px(2.);
9508 }
9509
9510 element.prepaint_at(origin, window, cx);
9511 Some((element, origin))
9512 }
9513
9514 fn render_edit_prediction_diff_popover(
9515 self: &Editor,
9516 text_bounds: &Bounds<Pixels>,
9517 content_origin: gpui::Point<Pixels>,
9518 right_margin: Pixels,
9519 editor_snapshot: &EditorSnapshot,
9520 visible_row_range: Range<DisplayRow>,
9521 line_layouts: &[LineWithInvisibles],
9522 line_height: Pixels,
9523 scroll_position: gpui::Point<ScrollOffset>,
9524 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9525 newest_selection_head: Option<DisplayPoint>,
9526 editor_width: Pixels,
9527 style: &EditorStyle,
9528 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9529 edit_preview: &Option<language::EditPreview>,
9530 snapshot: &language::BufferSnapshot,
9531 window: &mut Window,
9532 cx: &mut App,
9533 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9534 let edit_start = edits
9535 .first()
9536 .unwrap()
9537 .0
9538 .start
9539 .to_display_point(editor_snapshot);
9540 let edit_end = edits
9541 .last()
9542 .unwrap()
9543 .0
9544 .end
9545 .to_display_point(editor_snapshot);
9546
9547 let is_visible = visible_row_range.contains(&edit_start.row())
9548 || visible_row_range.contains(&edit_end.row());
9549 if !is_visible {
9550 return None;
9551 }
9552
9553 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9554 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9555 } else {
9556 // Fallback for providers without edit_preview
9557 crate::edit_prediction_fallback_text(edits, cx)
9558 };
9559
9560 let styled_text = highlighted_edits.to_styled_text(&style.text);
9561 let line_count = highlighted_edits.text.lines().count();
9562
9563 const BORDER_WIDTH: Pixels = px(1.);
9564
9565 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9566 let has_keybind = keybind.is_some();
9567
9568 let mut element = h_flex()
9569 .items_start()
9570 .child(
9571 h_flex()
9572 .bg(cx.theme().colors().editor_background)
9573 .border(BORDER_WIDTH)
9574 .shadow_xs()
9575 .border_color(cx.theme().colors().border)
9576 .rounded_l_lg()
9577 .when(line_count > 1, |el| el.rounded_br_lg())
9578 .pr_1()
9579 .child(styled_text),
9580 )
9581 .child(
9582 h_flex()
9583 .h(line_height + BORDER_WIDTH * 2.)
9584 .px_1p5()
9585 .gap_1()
9586 // Workaround: For some reason, there's a gap if we don't do this
9587 .ml(-BORDER_WIDTH)
9588 .shadow(vec![gpui::BoxShadow {
9589 color: gpui::black().opacity(0.05),
9590 offset: point(px(1.), px(1.)),
9591 blur_radius: px(2.),
9592 spread_radius: px(0.),
9593 }])
9594 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9595 .border(BORDER_WIDTH)
9596 .border_color(cx.theme().colors().border)
9597 .rounded_r_lg()
9598 .id("edit_prediction_diff_popover_keybind")
9599 .when(!has_keybind, |el| {
9600 let status_colors = cx.theme().status();
9601
9602 el.bg(status_colors.error_background)
9603 .border_color(status_colors.error.opacity(0.6))
9604 .child(Icon::new(IconName::Info).color(Color::Error))
9605 .cursor_default()
9606 .hoverable_tooltip(move |_window, cx| {
9607 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9608 })
9609 })
9610 .children(keybind),
9611 )
9612 .into_any();
9613
9614 let longest_row =
9615 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9616 let longest_line_width = if visible_row_range.contains(&longest_row) {
9617 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9618 } else {
9619 layout_line(
9620 longest_row,
9621 editor_snapshot,
9622 style,
9623 editor_width,
9624 |_| false,
9625 window,
9626 cx,
9627 )
9628 .width
9629 };
9630
9631 let viewport_bounds =
9632 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9633 right: -right_margin,
9634 ..Default::default()
9635 });
9636
9637 let x_after_longest = Pixels::from(
9638 ScrollPixelOffset::from(
9639 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9640 ) - scroll_pixel_position.x,
9641 );
9642
9643 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9644
9645 // Fully visible if it can be displayed within the window (allow overlapping other
9646 // panes). However, this is only allowed if the popover starts within text_bounds.
9647 let can_position_to_the_right = x_after_longest < text_bounds.right()
9648 && x_after_longest + element_bounds.width < viewport_bounds.right();
9649
9650 let mut origin = if can_position_to_the_right {
9651 point(
9652 x_after_longest,
9653 text_bounds.origin.y
9654 + Pixels::from(
9655 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9656 - scroll_pixel_position.y,
9657 ),
9658 )
9659 } else {
9660 let cursor_row = newest_selection_head.map(|head| head.row());
9661 let above_edit = edit_start
9662 .row()
9663 .0
9664 .checked_sub(line_count as u32)
9665 .map(DisplayRow);
9666 let below_edit = Some(edit_end.row() + 1);
9667 let above_cursor =
9668 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9669 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9670
9671 // Place the edit popover adjacent to the edit if there is a location
9672 // available that is onscreen and does not obscure the cursor. Otherwise,
9673 // place it adjacent to the cursor.
9674 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9675 .into_iter()
9676 .flatten()
9677 .find(|&start_row| {
9678 let end_row = start_row + line_count as u32;
9679 visible_row_range.contains(&start_row)
9680 && visible_row_range.contains(&end_row)
9681 && cursor_row
9682 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9683 })?;
9684
9685 content_origin
9686 + point(
9687 Pixels::from(-scroll_pixel_position.x),
9688 Pixels::from(
9689 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9690 ),
9691 )
9692 };
9693
9694 origin.x -= BORDER_WIDTH;
9695
9696 window.defer_draw(element, origin, 1);
9697
9698 // Do not return an element, since it will already be drawn due to defer_draw.
9699 None
9700 }
9701
9702 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9703 px(30.)
9704 }
9705
9706 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9707 if self.read_only(cx) {
9708 cx.theme().players().read_only()
9709 } else {
9710 self.style.as_ref().unwrap().local_player
9711 }
9712 }
9713
9714 fn render_edit_prediction_accept_keybind(
9715 &self,
9716 window: &mut Window,
9717 cx: &mut App,
9718 ) -> Option<AnyElement> {
9719 let accept_binding =
9720 self.accept_edit_prediction_keybind(EditPredictionGranularity::Full, window, cx);
9721 let accept_keystroke = accept_binding.keystroke()?;
9722
9723 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9724
9725 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9726 Color::Accent
9727 } else {
9728 Color::Muted
9729 };
9730
9731 h_flex()
9732 .px_0p5()
9733 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9734 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9735 .text_size(TextSize::XSmall.rems(cx))
9736 .child(h_flex().children(ui::render_modifiers(
9737 accept_keystroke.modifiers(),
9738 PlatformStyle::platform(),
9739 Some(modifiers_color),
9740 Some(IconSize::XSmall.rems().into()),
9741 true,
9742 )))
9743 .when(is_platform_style_mac, |parent| {
9744 parent.child(accept_keystroke.key().to_string())
9745 })
9746 .when(!is_platform_style_mac, |parent| {
9747 parent.child(
9748 Key::new(
9749 util::capitalize(accept_keystroke.key()),
9750 Some(Color::Default),
9751 )
9752 .size(Some(IconSize::XSmall.rems().into())),
9753 )
9754 })
9755 .into_any()
9756 .into()
9757 }
9758
9759 fn render_edit_prediction_line_popover(
9760 &self,
9761 label: impl Into<SharedString>,
9762 icon: Option<IconName>,
9763 window: &mut Window,
9764 cx: &mut App,
9765 ) -> Stateful<Div> {
9766 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9767
9768 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9769 let has_keybind = keybind.is_some();
9770 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
9771
9772 h_flex()
9773 .id("ep-line-popover")
9774 .py_0p5()
9775 .pl_1()
9776 .pr(padding_right)
9777 .gap_1()
9778 .rounded_md()
9779 .border_1()
9780 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9781 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9782 .shadow_xs()
9783 .when(!has_keybind, |el| {
9784 let status_colors = cx.theme().status();
9785
9786 el.bg(status_colors.error_background)
9787 .border_color(status_colors.error.opacity(0.6))
9788 .pl_2()
9789 .child(Icon::new(icons.error).color(Color::Error))
9790 .cursor_default()
9791 .hoverable_tooltip(move |_window, cx| {
9792 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9793 })
9794 })
9795 .children(keybind)
9796 .child(
9797 Label::new(label)
9798 .size(LabelSize::Small)
9799 .when(!has_keybind, |el| {
9800 el.color(cx.theme().status().error.into()).strikethrough()
9801 }),
9802 )
9803 .when(!has_keybind, |el| {
9804 el.child(
9805 h_flex().ml_1().child(
9806 Icon::new(IconName::Info)
9807 .size(IconSize::Small)
9808 .color(cx.theme().status().error.into()),
9809 ),
9810 )
9811 })
9812 .when_some(icon, |element, icon| {
9813 element.child(
9814 div()
9815 .mt(px(1.5))
9816 .child(Icon::new(icon).size(IconSize::Small)),
9817 )
9818 })
9819 }
9820
9821 fn render_edit_prediction_jump_outside_popover(
9822 &self,
9823 snapshot: &BufferSnapshot,
9824 window: &mut Window,
9825 cx: &mut App,
9826 ) -> Stateful<Div> {
9827 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9828 let has_keybind = keybind.is_some();
9829 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
9830
9831 let file_name = snapshot
9832 .file()
9833 .map(|file| SharedString::new(file.file_name(cx)))
9834 .unwrap_or(SharedString::new_static("untitled"));
9835
9836 h_flex()
9837 .id("ep-jump-outside-popover")
9838 .py_1()
9839 .px_2()
9840 .gap_1()
9841 .rounded_md()
9842 .border_1()
9843 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9844 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9845 .shadow_xs()
9846 .when(!has_keybind, |el| {
9847 let status_colors = cx.theme().status();
9848
9849 el.bg(status_colors.error_background)
9850 .border_color(status_colors.error.opacity(0.6))
9851 .pl_2()
9852 .child(Icon::new(icons.error).color(Color::Error))
9853 .cursor_default()
9854 .hoverable_tooltip(move |_window, cx| {
9855 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9856 })
9857 })
9858 .children(keybind)
9859 .child(
9860 Label::new(file_name)
9861 .size(LabelSize::Small)
9862 .buffer_font(cx)
9863 .when(!has_keybind, |el| {
9864 el.color(cx.theme().status().error.into()).strikethrough()
9865 }),
9866 )
9867 .when(!has_keybind, |el| {
9868 el.child(
9869 h_flex().ml_1().child(
9870 Icon::new(IconName::Info)
9871 .size(IconSize::Small)
9872 .color(cx.theme().status().error.into()),
9873 ),
9874 )
9875 })
9876 .child(
9877 div()
9878 .mt(px(1.5))
9879 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
9880 )
9881 }
9882
9883 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9884 let accent_color = cx.theme().colors().text_accent;
9885 let editor_bg_color = cx.theme().colors().editor_background;
9886 editor_bg_color.blend(accent_color.opacity(0.1))
9887 }
9888
9889 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9890 let accent_color = cx.theme().colors().text_accent;
9891 let editor_bg_color = cx.theme().colors().editor_background;
9892 editor_bg_color.blend(accent_color.opacity(0.6))
9893 }
9894 fn get_prediction_provider_icons(
9895 provider: &Option<RegisteredEditPredictionDelegate>,
9896 cx: &App,
9897 ) -> edit_prediction_types::EditPredictionIconSet {
9898 match provider {
9899 Some(provider) => provider.provider.icons(cx),
9900 None => edit_prediction_types::EditPredictionIconSet::new(IconName::ZedPredict),
9901 }
9902 }
9903
9904 fn render_edit_prediction_cursor_popover(
9905 &self,
9906 min_width: Pixels,
9907 max_width: Pixels,
9908 cursor_point: Point,
9909 style: &EditorStyle,
9910 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9911 _window: &Window,
9912 cx: &mut Context<Editor>,
9913 ) -> Option<AnyElement> {
9914 let provider = self.edit_prediction_provider.as_ref()?;
9915 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
9916
9917 let is_refreshing = provider.provider.is_refreshing(cx);
9918
9919 fn pending_completion_container(icon: IconName) -> Div {
9920 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9921 }
9922
9923 let completion = match &self.active_edit_prediction {
9924 Some(prediction) => {
9925 if !self.has_visible_completions_menu() {
9926 const RADIUS: Pixels = px(6.);
9927 const BORDER_WIDTH: Pixels = px(1.);
9928
9929 return Some(
9930 h_flex()
9931 .elevation_2(cx)
9932 .border(BORDER_WIDTH)
9933 .border_color(cx.theme().colors().border)
9934 .when(accept_keystroke.is_none(), |el| {
9935 el.border_color(cx.theme().status().error)
9936 })
9937 .rounded(RADIUS)
9938 .rounded_tl(px(0.))
9939 .overflow_hidden()
9940 .child(div().px_1p5().child(match &prediction.completion {
9941 EditPrediction::MoveWithin { target, snapshot } => {
9942 use text::ToPoint as _;
9943 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9944 {
9945 Icon::new(icons.down)
9946 } else {
9947 Icon::new(icons.up)
9948 }
9949 }
9950 EditPrediction::MoveOutside { .. } => {
9951 // TODO [zeta2] custom icon for external jump?
9952 Icon::new(icons.base)
9953 }
9954 EditPrediction::Edit { .. } => Icon::new(icons.base),
9955 }))
9956 .child(
9957 h_flex()
9958 .gap_1()
9959 .py_1()
9960 .px_2()
9961 .rounded_r(RADIUS - BORDER_WIDTH)
9962 .border_l_1()
9963 .border_color(cx.theme().colors().border)
9964 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9965 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9966 el.child(
9967 Label::new("Hold")
9968 .size(LabelSize::Small)
9969 .when(accept_keystroke.is_none(), |el| {
9970 el.strikethrough()
9971 })
9972 .line_height_style(LineHeightStyle::UiLabel),
9973 )
9974 })
9975 .id("edit_prediction_cursor_popover_keybind")
9976 .when(accept_keystroke.is_none(), |el| {
9977 let status_colors = cx.theme().status();
9978
9979 el.bg(status_colors.error_background)
9980 .border_color(status_colors.error.opacity(0.6))
9981 .child(Icon::new(IconName::Info).color(Color::Error))
9982 .cursor_default()
9983 .hoverable_tooltip(move |_window, cx| {
9984 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9985 .into()
9986 })
9987 })
9988 .when_some(
9989 accept_keystroke.as_ref(),
9990 |el, accept_keystroke| {
9991 el.child(h_flex().children(ui::render_modifiers(
9992 accept_keystroke.modifiers(),
9993 PlatformStyle::platform(),
9994 Some(Color::Default),
9995 Some(IconSize::XSmall.rems().into()),
9996 false,
9997 )))
9998 },
9999 ),
10000 )
10001 .into_any(),
10002 );
10003 }
10004
10005 self.render_edit_prediction_cursor_popover_preview(
10006 prediction,
10007 cursor_point,
10008 style,
10009 cx,
10010 )?
10011 }
10012
10013 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
10014 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
10015 stale_completion,
10016 cursor_point,
10017 style,
10018 cx,
10019 )?,
10020
10021 None => pending_completion_container(icons.base)
10022 .child(Label::new("...").size(LabelSize::Small)),
10023 },
10024
10025 None => pending_completion_container(icons.base)
10026 .child(Label::new("...").size(LabelSize::Small)),
10027 };
10028
10029 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
10030 completion
10031 .with_animation(
10032 "loading-completion",
10033 Animation::new(Duration::from_secs(2))
10034 .repeat()
10035 .with_easing(pulsating_between(0.4, 0.8)),
10036 |label, delta| label.opacity(delta),
10037 )
10038 .into_any_element()
10039 } else {
10040 completion.into_any_element()
10041 };
10042
10043 let has_completion = self.active_edit_prediction.is_some();
10044
10045 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
10046 Some(
10047 h_flex()
10048 .min_w(min_width)
10049 .max_w(max_width)
10050 .flex_1()
10051 .elevation_2(cx)
10052 .border_color(cx.theme().colors().border)
10053 .child(
10054 div()
10055 .flex_1()
10056 .py_1()
10057 .px_2()
10058 .overflow_hidden()
10059 .child(completion),
10060 )
10061 .when_some(accept_keystroke, |el, accept_keystroke| {
10062 if !accept_keystroke.modifiers().modified() {
10063 return el;
10064 }
10065
10066 el.child(
10067 h_flex()
10068 .h_full()
10069 .border_l_1()
10070 .rounded_r_lg()
10071 .border_color(cx.theme().colors().border)
10072 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10073 .gap_1()
10074 .py_1()
10075 .px_2()
10076 .child(
10077 h_flex()
10078 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
10079 .when(is_platform_style_mac, |parent| parent.gap_1())
10080 .child(h_flex().children(ui::render_modifiers(
10081 accept_keystroke.modifiers(),
10082 PlatformStyle::platform(),
10083 Some(if !has_completion {
10084 Color::Muted
10085 } else {
10086 Color::Default
10087 }),
10088 None,
10089 false,
10090 ))),
10091 )
10092 .child(Label::new("Preview").into_any_element())
10093 .opacity(if has_completion { 1.0 } else { 0.4 }),
10094 )
10095 })
10096 .into_any(),
10097 )
10098 }
10099
10100 fn render_edit_prediction_cursor_popover_preview(
10101 &self,
10102 completion: &EditPredictionState,
10103 cursor_point: Point,
10104 style: &EditorStyle,
10105 cx: &mut Context<Editor>,
10106 ) -> Option<Div> {
10107 use text::ToPoint as _;
10108
10109 fn render_relative_row_jump(
10110 prefix: impl Into<String>,
10111 current_row: u32,
10112 target_row: u32,
10113 ) -> Div {
10114 let (row_diff, arrow) = if target_row < current_row {
10115 (current_row - target_row, IconName::ArrowUp)
10116 } else {
10117 (target_row - current_row, IconName::ArrowDown)
10118 };
10119
10120 h_flex()
10121 .child(
10122 Label::new(format!("{}{}", prefix.into(), row_diff))
10123 .color(Color::Muted)
10124 .size(LabelSize::Small),
10125 )
10126 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
10127 }
10128
10129 let supports_jump = self
10130 .edit_prediction_provider
10131 .as_ref()
10132 .map(|provider| provider.provider.supports_jump_to_edit())
10133 .unwrap_or(true);
10134
10135 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10136
10137 match &completion.completion {
10138 EditPrediction::MoveWithin {
10139 target, snapshot, ..
10140 } => {
10141 if !supports_jump {
10142 return None;
10143 }
10144
10145 Some(
10146 h_flex()
10147 .px_2()
10148 .gap_2()
10149 .flex_1()
10150 .child(
10151 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
10152 Icon::new(icons.down)
10153 } else {
10154 Icon::new(icons.up)
10155 },
10156 )
10157 .child(Label::new("Jump to Edit")),
10158 )
10159 }
10160 EditPrediction::MoveOutside { snapshot, .. } => {
10161 let file_name = snapshot
10162 .file()
10163 .map(|file| file.file_name(cx))
10164 .unwrap_or("untitled");
10165 Some(
10166 h_flex()
10167 .px_2()
10168 .gap_2()
10169 .flex_1()
10170 .child(Icon::new(icons.base))
10171 .child(Label::new(format!("Jump to {file_name}"))),
10172 )
10173 }
10174 EditPrediction::Edit {
10175 edits,
10176 edit_preview,
10177 snapshot,
10178 ..
10179 } => {
10180 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
10181
10182 let (highlighted_edits, has_more_lines) =
10183 if let Some(edit_preview) = edit_preview.as_ref() {
10184 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
10185 .first_line_preview()
10186 } else {
10187 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
10188 };
10189
10190 let styled_text = gpui::StyledText::new(highlighted_edits.text)
10191 .with_default_highlights(&style.text, highlighted_edits.highlights);
10192
10193 let preview = h_flex()
10194 .gap_1()
10195 .min_w_16()
10196 .child(styled_text)
10197 .when(has_more_lines, |parent| parent.child("…"));
10198
10199 let left = if supports_jump && first_edit_row != cursor_point.row {
10200 render_relative_row_jump("", cursor_point.row, first_edit_row)
10201 .into_any_element()
10202 } else {
10203 Icon::new(icons.base).into_any_element()
10204 };
10205
10206 Some(
10207 h_flex()
10208 .h_full()
10209 .flex_1()
10210 .gap_2()
10211 .pr_1()
10212 .overflow_x_hidden()
10213 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
10214 .child(left)
10215 .child(preview),
10216 )
10217 }
10218 }
10219 }
10220
10221 pub fn render_context_menu(
10222 &mut self,
10223 max_height_in_lines: u32,
10224 window: &mut Window,
10225 cx: &mut Context<Editor>,
10226 ) -> Option<AnyElement> {
10227 let menu = self.context_menu.borrow();
10228 let menu = menu.as_ref()?;
10229 if !menu.visible() {
10230 return None;
10231 };
10232 self.style
10233 .as_ref()
10234 .map(|style| menu.render(style, max_height_in_lines, window, cx))
10235 }
10236
10237 fn render_context_menu_aside(
10238 &mut self,
10239 max_size: Size<Pixels>,
10240 window: &mut Window,
10241 cx: &mut Context<Editor>,
10242 ) -> Option<AnyElement> {
10243 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
10244 if menu.visible() {
10245 menu.render_aside(max_size, window, cx)
10246 } else {
10247 None
10248 }
10249 })
10250 }
10251
10252 fn hide_context_menu(
10253 &mut self,
10254 window: &mut Window,
10255 cx: &mut Context<Self>,
10256 ) -> Option<CodeContextMenu> {
10257 cx.notify();
10258 self.completion_tasks.clear();
10259 let context_menu = self.context_menu.borrow_mut().take();
10260 self.stale_edit_prediction_in_menu.take();
10261 self.update_visible_edit_prediction(window, cx);
10262 if let Some(CodeContextMenu::Completions(_)) = &context_menu
10263 && let Some(completion_provider) = &self.completion_provider
10264 {
10265 completion_provider.selection_changed(None, window, cx);
10266 }
10267 context_menu
10268 }
10269
10270 fn show_snippet_choices(
10271 &mut self,
10272 choices: &Vec<String>,
10273 selection: Range<Anchor>,
10274 cx: &mut Context<Self>,
10275 ) {
10276 let Some((_, buffer, _)) = self
10277 .buffer()
10278 .read(cx)
10279 .excerpt_containing(selection.start, cx)
10280 else {
10281 return;
10282 };
10283 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
10284 else {
10285 return;
10286 };
10287 if buffer != end_buffer {
10288 log::error!("expected anchor range to have matching buffer IDs");
10289 return;
10290 }
10291
10292 let id = post_inc(&mut self.next_completion_id);
10293 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10294 let mut context_menu = self.context_menu.borrow_mut();
10295 let old_menu = context_menu.take();
10296 *context_menu = Some(CodeContextMenu::Completions(
10297 CompletionsMenu::new_snippet_choices(
10298 id,
10299 true,
10300 choices,
10301 selection,
10302 buffer,
10303 old_menu.map(|menu| menu.primary_scroll_handle()),
10304 snippet_sort_order,
10305 ),
10306 ));
10307 }
10308
10309 pub fn insert_snippet(
10310 &mut self,
10311 insertion_ranges: &[Range<MultiBufferOffset>],
10312 snippet: Snippet,
10313 window: &mut Window,
10314 cx: &mut Context<Self>,
10315 ) -> Result<()> {
10316 struct Tabstop<T> {
10317 is_end_tabstop: bool,
10318 ranges: Vec<Range<T>>,
10319 choices: Option<Vec<String>>,
10320 }
10321
10322 let tabstops = self.buffer.update(cx, |buffer, cx| {
10323 let snippet_text: Arc<str> = snippet.text.clone().into();
10324 let edits = insertion_ranges
10325 .iter()
10326 .cloned()
10327 .map(|range| (range, snippet_text.clone()));
10328 let autoindent_mode = AutoindentMode::Block {
10329 original_indent_columns: Vec::new(),
10330 };
10331 buffer.edit(edits, Some(autoindent_mode), cx);
10332
10333 let snapshot = &*buffer.read(cx);
10334 let snippet = &snippet;
10335 snippet
10336 .tabstops
10337 .iter()
10338 .map(|tabstop| {
10339 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10340 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10341 });
10342 let mut tabstop_ranges = tabstop
10343 .ranges
10344 .iter()
10345 .flat_map(|tabstop_range| {
10346 let mut delta = 0_isize;
10347 insertion_ranges.iter().map(move |insertion_range| {
10348 let insertion_start = insertion_range.start + delta;
10349 delta += snippet.text.len() as isize
10350 - (insertion_range.end - insertion_range.start) as isize;
10351
10352 let start =
10353 (insertion_start + tabstop_range.start).min(snapshot.len());
10354 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10355 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10356 })
10357 })
10358 .collect::<Vec<_>>();
10359 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10360
10361 Tabstop {
10362 is_end_tabstop,
10363 ranges: tabstop_ranges,
10364 choices: tabstop.choices.clone(),
10365 }
10366 })
10367 .collect::<Vec<_>>()
10368 });
10369 if let Some(tabstop) = tabstops.first() {
10370 self.change_selections(Default::default(), window, cx, |s| {
10371 // Reverse order so that the first range is the newest created selection.
10372 // Completions will use it and autoscroll will prioritize it.
10373 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10374 });
10375
10376 if let Some(choices) = &tabstop.choices
10377 && let Some(selection) = tabstop.ranges.first()
10378 {
10379 self.show_snippet_choices(choices, selection.clone(), cx)
10380 }
10381
10382 // If we're already at the last tabstop and it's at the end of the snippet,
10383 // we're done, we don't need to keep the state around.
10384 if !tabstop.is_end_tabstop {
10385 let choices = tabstops
10386 .iter()
10387 .map(|tabstop| tabstop.choices.clone())
10388 .collect();
10389
10390 let ranges = tabstops
10391 .into_iter()
10392 .map(|tabstop| tabstop.ranges)
10393 .collect::<Vec<_>>();
10394
10395 self.snippet_stack.push(SnippetState {
10396 active_index: 0,
10397 ranges,
10398 choices,
10399 });
10400 }
10401
10402 // Check whether the just-entered snippet ends with an auto-closable bracket.
10403 if self.autoclose_regions.is_empty() {
10404 let snapshot = self.buffer.read(cx).snapshot(cx);
10405 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10406 let selection_head = selection.head();
10407 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10408 continue;
10409 };
10410
10411 let mut bracket_pair = None;
10412 let max_lookup_length = scope
10413 .brackets()
10414 .map(|(pair, _)| {
10415 pair.start
10416 .as_str()
10417 .chars()
10418 .count()
10419 .max(pair.end.as_str().chars().count())
10420 })
10421 .max();
10422 if let Some(max_lookup_length) = max_lookup_length {
10423 let next_text = snapshot
10424 .chars_at(selection_head)
10425 .take(max_lookup_length)
10426 .collect::<String>();
10427 let prev_text = snapshot
10428 .reversed_chars_at(selection_head)
10429 .take(max_lookup_length)
10430 .collect::<String>();
10431
10432 for (pair, enabled) in scope.brackets() {
10433 if enabled
10434 && pair.close
10435 && prev_text.starts_with(pair.start.as_str())
10436 && next_text.starts_with(pair.end.as_str())
10437 {
10438 bracket_pair = Some(pair.clone());
10439 break;
10440 }
10441 }
10442 }
10443
10444 if let Some(pair) = bracket_pair {
10445 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10446 let autoclose_enabled =
10447 self.use_autoclose && snapshot_settings.use_autoclose;
10448 if autoclose_enabled {
10449 let start = snapshot.anchor_after(selection_head);
10450 let end = snapshot.anchor_after(selection_head);
10451 self.autoclose_regions.push(AutocloseRegion {
10452 selection_id: selection.id,
10453 range: start..end,
10454 pair,
10455 });
10456 }
10457 }
10458 }
10459 }
10460 }
10461 Ok(())
10462 }
10463
10464 pub fn move_to_next_snippet_tabstop(
10465 &mut self,
10466 window: &mut Window,
10467 cx: &mut Context<Self>,
10468 ) -> bool {
10469 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10470 }
10471
10472 pub fn move_to_prev_snippet_tabstop(
10473 &mut self,
10474 window: &mut Window,
10475 cx: &mut Context<Self>,
10476 ) -> bool {
10477 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10478 }
10479
10480 pub fn move_to_snippet_tabstop(
10481 &mut self,
10482 bias: Bias,
10483 window: &mut Window,
10484 cx: &mut Context<Self>,
10485 ) -> bool {
10486 if let Some(mut snippet) = self.snippet_stack.pop() {
10487 match bias {
10488 Bias::Left => {
10489 if snippet.active_index > 0 {
10490 snippet.active_index -= 1;
10491 } else {
10492 self.snippet_stack.push(snippet);
10493 return false;
10494 }
10495 }
10496 Bias::Right => {
10497 if snippet.active_index + 1 < snippet.ranges.len() {
10498 snippet.active_index += 1;
10499 } else {
10500 self.snippet_stack.push(snippet);
10501 return false;
10502 }
10503 }
10504 }
10505 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10506 self.change_selections(Default::default(), window, cx, |s| {
10507 // Reverse order so that the first range is the newest created selection.
10508 // Completions will use it and autoscroll will prioritize it.
10509 s.select_ranges(current_ranges.iter().rev().cloned())
10510 });
10511
10512 if let Some(choices) = &snippet.choices[snippet.active_index]
10513 && let Some(selection) = current_ranges.first()
10514 {
10515 self.show_snippet_choices(choices, selection.clone(), cx);
10516 }
10517
10518 // If snippet state is not at the last tabstop, push it back on the stack
10519 if snippet.active_index + 1 < snippet.ranges.len() {
10520 self.snippet_stack.push(snippet);
10521 }
10522 return true;
10523 }
10524 }
10525
10526 false
10527 }
10528
10529 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10530 self.transact(window, cx, |this, window, cx| {
10531 this.select_all(&SelectAll, window, cx);
10532 this.insert("", window, cx);
10533 });
10534 }
10535
10536 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10537 if self.read_only(cx) {
10538 return;
10539 }
10540 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10541 self.transact(window, cx, |this, window, cx| {
10542 this.select_autoclose_pair(window, cx);
10543
10544 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10545
10546 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10547 if !this.linked_edit_ranges.is_empty() {
10548 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10549 let snapshot = this.buffer.read(cx).snapshot(cx);
10550
10551 for selection in selections.iter() {
10552 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10553 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10554 if selection_start.buffer_id != selection_end.buffer_id {
10555 continue;
10556 }
10557 if let Some(ranges) =
10558 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10559 {
10560 for (buffer, entries) in ranges {
10561 linked_ranges.entry(buffer).or_default().extend(entries);
10562 }
10563 }
10564 }
10565 }
10566
10567 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10568 for selection in &mut selections {
10569 if selection.is_empty() {
10570 let old_head = selection.head();
10571 let mut new_head =
10572 movement::left(&display_map, old_head.to_display_point(&display_map))
10573 .to_point(&display_map);
10574 if let Some((buffer, line_buffer_range)) = display_map
10575 .buffer_snapshot()
10576 .buffer_line_for_row(MultiBufferRow(old_head.row))
10577 {
10578 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10579 let indent_len = match indent_size.kind {
10580 IndentKind::Space => {
10581 buffer.settings_at(line_buffer_range.start, cx).tab_size
10582 }
10583 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10584 };
10585 if old_head.column <= indent_size.len && old_head.column > 0 {
10586 let indent_len = indent_len.get();
10587 new_head = cmp::min(
10588 new_head,
10589 MultiBufferPoint::new(
10590 old_head.row,
10591 ((old_head.column - 1) / indent_len) * indent_len,
10592 ),
10593 );
10594 }
10595 }
10596
10597 selection.set_head(new_head, SelectionGoal::None);
10598 }
10599 }
10600
10601 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10602 this.insert("", window, cx);
10603 let empty_str: Arc<str> = Arc::from("");
10604 for (buffer, edits) in linked_ranges {
10605 let snapshot = buffer.read(cx).snapshot();
10606 use text::ToPoint as TP;
10607
10608 let edits = edits
10609 .into_iter()
10610 .map(|range| {
10611 let end_point = TP::to_point(&range.end, &snapshot);
10612 let mut start_point = TP::to_point(&range.start, &snapshot);
10613
10614 if end_point == start_point {
10615 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10616 .saturating_sub(1);
10617 start_point =
10618 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10619 };
10620
10621 (start_point..end_point, empty_str.clone())
10622 })
10623 .sorted_by_key(|(range, _)| range.start)
10624 .collect::<Vec<_>>();
10625 buffer.update(cx, |this, cx| {
10626 this.edit(edits, None, cx);
10627 })
10628 }
10629 this.refresh_edit_prediction(true, false, window, cx);
10630 refresh_linked_ranges(this, window, cx);
10631 });
10632 }
10633
10634 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10635 if self.read_only(cx) {
10636 return;
10637 }
10638 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10639 self.transact(window, cx, |this, window, cx| {
10640 this.change_selections(Default::default(), window, cx, |s| {
10641 s.move_with(|map, selection| {
10642 if selection.is_empty() {
10643 let cursor = movement::right(map, selection.head());
10644 selection.end = cursor;
10645 selection.reversed = true;
10646 selection.goal = SelectionGoal::None;
10647 }
10648 })
10649 });
10650 this.insert("", window, cx);
10651 this.refresh_edit_prediction(true, false, window, cx);
10652 });
10653 }
10654
10655 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10656 if self.mode.is_single_line() {
10657 cx.propagate();
10658 return;
10659 }
10660
10661 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10662 if self.move_to_prev_snippet_tabstop(window, cx) {
10663 return;
10664 }
10665 self.outdent(&Outdent, window, cx);
10666 }
10667
10668 pub fn next_snippet_tabstop(
10669 &mut self,
10670 _: &NextSnippetTabstop,
10671 window: &mut Window,
10672 cx: &mut Context<Self>,
10673 ) {
10674 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10675 cx.propagate();
10676 return;
10677 }
10678
10679 if self.move_to_next_snippet_tabstop(window, cx) {
10680 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10681 return;
10682 }
10683 cx.propagate();
10684 }
10685
10686 pub fn previous_snippet_tabstop(
10687 &mut self,
10688 _: &PreviousSnippetTabstop,
10689 window: &mut Window,
10690 cx: &mut Context<Self>,
10691 ) {
10692 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10693 cx.propagate();
10694 return;
10695 }
10696
10697 if self.move_to_prev_snippet_tabstop(window, cx) {
10698 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10699 return;
10700 }
10701 cx.propagate();
10702 }
10703
10704 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10705 if self.mode.is_single_line() {
10706 cx.propagate();
10707 return;
10708 }
10709
10710 if self.move_to_next_snippet_tabstop(window, cx) {
10711 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10712 return;
10713 }
10714 if self.read_only(cx) {
10715 return;
10716 }
10717 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10718 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10719 let buffer = self.buffer.read(cx);
10720 let snapshot = buffer.snapshot(cx);
10721 let rows_iter = selections.iter().map(|s| s.head().row);
10722 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10723
10724 let has_some_cursor_in_whitespace = selections
10725 .iter()
10726 .filter(|selection| selection.is_empty())
10727 .any(|selection| {
10728 let cursor = selection.head();
10729 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10730 cursor.column < current_indent.len
10731 });
10732
10733 let mut edits = Vec::new();
10734 let mut prev_edited_row = 0;
10735 let mut row_delta = 0;
10736 for selection in &mut selections {
10737 if selection.start.row != prev_edited_row {
10738 row_delta = 0;
10739 }
10740 prev_edited_row = selection.end.row;
10741
10742 // If cursor is after a list prefix, make selection non-empty to trigger line indent
10743 if selection.is_empty() {
10744 let cursor = selection.head();
10745 let settings = buffer.language_settings_at(cursor, cx);
10746 if settings.indent_list_on_tab {
10747 if let Some(language) = snapshot.language_scope_at(Point::new(cursor.row, 0)) {
10748 if is_list_prefix_row(MultiBufferRow(cursor.row), &snapshot, &language) {
10749 row_delta = Self::indent_selection(
10750 buffer, &snapshot, selection, &mut edits, row_delta, cx,
10751 );
10752 continue;
10753 }
10754 }
10755 }
10756 }
10757
10758 // If the selection is non-empty, then increase the indentation of the selected lines.
10759 if !selection.is_empty() {
10760 row_delta =
10761 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10762 continue;
10763 }
10764
10765 let cursor = selection.head();
10766 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10767 if let Some(suggested_indent) =
10768 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10769 {
10770 // Don't do anything if already at suggested indent
10771 // and there is any other cursor which is not
10772 if has_some_cursor_in_whitespace
10773 && cursor.column == current_indent.len
10774 && current_indent.len == suggested_indent.len
10775 {
10776 continue;
10777 }
10778
10779 // Adjust line and move cursor to suggested indent
10780 // if cursor is not at suggested indent
10781 if cursor.column < suggested_indent.len
10782 && cursor.column <= current_indent.len
10783 && current_indent.len <= suggested_indent.len
10784 {
10785 selection.start = Point::new(cursor.row, suggested_indent.len);
10786 selection.end = selection.start;
10787 if row_delta == 0 {
10788 edits.extend(Buffer::edit_for_indent_size_adjustment(
10789 cursor.row,
10790 current_indent,
10791 suggested_indent,
10792 ));
10793 row_delta = suggested_indent.len - current_indent.len;
10794 }
10795 continue;
10796 }
10797
10798 // If current indent is more than suggested indent
10799 // only move cursor to current indent and skip indent
10800 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10801 selection.start = Point::new(cursor.row, current_indent.len);
10802 selection.end = selection.start;
10803 continue;
10804 }
10805 }
10806
10807 // Otherwise, insert a hard or soft tab.
10808 let settings = buffer.language_settings_at(cursor, cx);
10809 let tab_size = if settings.hard_tabs {
10810 IndentSize::tab()
10811 } else {
10812 let tab_size = settings.tab_size.get();
10813 let indent_remainder = snapshot
10814 .text_for_range(Point::new(cursor.row, 0)..cursor)
10815 .flat_map(str::chars)
10816 .fold(row_delta % tab_size, |counter: u32, c| {
10817 if c == '\t' {
10818 0
10819 } else {
10820 (counter + 1) % tab_size
10821 }
10822 });
10823
10824 let chars_to_next_tab_stop = tab_size - indent_remainder;
10825 IndentSize::spaces(chars_to_next_tab_stop)
10826 };
10827 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10828 selection.end = selection.start;
10829 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10830 row_delta += tab_size.len;
10831 }
10832
10833 self.transact(window, cx, |this, window, cx| {
10834 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10835 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10836 this.refresh_edit_prediction(true, false, window, cx);
10837 });
10838 }
10839
10840 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10841 if self.read_only(cx) {
10842 return;
10843 }
10844 if self.mode.is_single_line() {
10845 cx.propagate();
10846 return;
10847 }
10848
10849 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10850 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10851 let mut prev_edited_row = 0;
10852 let mut row_delta = 0;
10853 let mut edits = Vec::new();
10854 let buffer = self.buffer.read(cx);
10855 let snapshot = buffer.snapshot(cx);
10856 for selection in &mut selections {
10857 if selection.start.row != prev_edited_row {
10858 row_delta = 0;
10859 }
10860 prev_edited_row = selection.end.row;
10861
10862 row_delta =
10863 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10864 }
10865
10866 self.transact(window, cx, |this, window, cx| {
10867 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10868 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10869 });
10870 }
10871
10872 fn indent_selection(
10873 buffer: &MultiBuffer,
10874 snapshot: &MultiBufferSnapshot,
10875 selection: &mut Selection<Point>,
10876 edits: &mut Vec<(Range<Point>, String)>,
10877 delta_for_start_row: u32,
10878 cx: &App,
10879 ) -> u32 {
10880 let settings = buffer.language_settings_at(selection.start, cx);
10881 let tab_size = settings.tab_size.get();
10882 let indent_kind = if settings.hard_tabs {
10883 IndentKind::Tab
10884 } else {
10885 IndentKind::Space
10886 };
10887 let mut start_row = selection.start.row;
10888 let mut end_row = selection.end.row + 1;
10889
10890 // If a selection ends at the beginning of a line, don't indent
10891 // that last line.
10892 if selection.end.column == 0 && selection.end.row > selection.start.row {
10893 end_row -= 1;
10894 }
10895
10896 // Avoid re-indenting a row that has already been indented by a
10897 // previous selection, but still update this selection's column
10898 // to reflect that indentation.
10899 if delta_for_start_row > 0 {
10900 start_row += 1;
10901 selection.start.column += delta_for_start_row;
10902 if selection.end.row == selection.start.row {
10903 selection.end.column += delta_for_start_row;
10904 }
10905 }
10906
10907 let mut delta_for_end_row = 0;
10908 let has_multiple_rows = start_row + 1 != end_row;
10909 for row in start_row..end_row {
10910 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10911 let indent_delta = match (current_indent.kind, indent_kind) {
10912 (IndentKind::Space, IndentKind::Space) => {
10913 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10914 IndentSize::spaces(columns_to_next_tab_stop)
10915 }
10916 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10917 (_, IndentKind::Tab) => IndentSize::tab(),
10918 };
10919
10920 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10921 0
10922 } else {
10923 selection.start.column
10924 };
10925 let row_start = Point::new(row, start);
10926 edits.push((
10927 row_start..row_start,
10928 indent_delta.chars().collect::<String>(),
10929 ));
10930
10931 // Update this selection's endpoints to reflect the indentation.
10932 if row == selection.start.row {
10933 selection.start.column += indent_delta.len;
10934 }
10935 if row == selection.end.row {
10936 selection.end.column += indent_delta.len;
10937 delta_for_end_row = indent_delta.len;
10938 }
10939 }
10940
10941 if selection.start.row == selection.end.row {
10942 delta_for_start_row + delta_for_end_row
10943 } else {
10944 delta_for_end_row
10945 }
10946 }
10947
10948 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10949 if self.read_only(cx) {
10950 return;
10951 }
10952 if self.mode.is_single_line() {
10953 cx.propagate();
10954 return;
10955 }
10956
10957 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10958 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10959 let selections = self.selections.all::<Point>(&display_map);
10960 let mut deletion_ranges = Vec::new();
10961 let mut last_outdent = None;
10962 {
10963 let buffer = self.buffer.read(cx);
10964 let snapshot = buffer.snapshot(cx);
10965 for selection in &selections {
10966 let settings = buffer.language_settings_at(selection.start, cx);
10967 let tab_size = settings.tab_size.get();
10968 let mut rows = selection.spanned_rows(false, &display_map);
10969
10970 // Avoid re-outdenting a row that has already been outdented by a
10971 // previous selection.
10972 if let Some(last_row) = last_outdent
10973 && last_row == rows.start
10974 {
10975 rows.start = rows.start.next_row();
10976 }
10977 let has_multiple_rows = rows.len() > 1;
10978 for row in rows.iter_rows() {
10979 let indent_size = snapshot.indent_size_for_line(row);
10980 if indent_size.len > 0 {
10981 let deletion_len = match indent_size.kind {
10982 IndentKind::Space => {
10983 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10984 if columns_to_prev_tab_stop == 0 {
10985 tab_size
10986 } else {
10987 columns_to_prev_tab_stop
10988 }
10989 }
10990 IndentKind::Tab => 1,
10991 };
10992 let start = if has_multiple_rows
10993 || deletion_len > selection.start.column
10994 || indent_size.len < selection.start.column
10995 {
10996 0
10997 } else {
10998 selection.start.column - deletion_len
10999 };
11000 deletion_ranges.push(
11001 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
11002 );
11003 last_outdent = Some(row);
11004 }
11005 }
11006 }
11007 }
11008
11009 self.transact(window, cx, |this, window, cx| {
11010 this.buffer.update(cx, |buffer, cx| {
11011 let empty_str: Arc<str> = Arc::default();
11012 buffer.edit(
11013 deletion_ranges
11014 .into_iter()
11015 .map(|range| (range, empty_str.clone())),
11016 None,
11017 cx,
11018 );
11019 });
11020 let selections = this
11021 .selections
11022 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11023 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11024 });
11025 }
11026
11027 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
11028 if self.read_only(cx) {
11029 return;
11030 }
11031 if self.mode.is_single_line() {
11032 cx.propagate();
11033 return;
11034 }
11035
11036 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11037 let selections = self
11038 .selections
11039 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11040 .into_iter()
11041 .map(|s| s.range());
11042
11043 self.transact(window, cx, |this, window, cx| {
11044 this.buffer.update(cx, |buffer, cx| {
11045 buffer.autoindent_ranges(selections, cx);
11046 });
11047 let selections = this
11048 .selections
11049 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11050 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11051 });
11052 }
11053
11054 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
11055 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11056 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11057 let selections = self.selections.all::<Point>(&display_map);
11058
11059 let mut new_cursors = Vec::new();
11060 let mut edit_ranges = Vec::new();
11061 let mut selections = selections.iter().peekable();
11062 while let Some(selection) = selections.next() {
11063 let mut rows = selection.spanned_rows(false, &display_map);
11064
11065 // Accumulate contiguous regions of rows that we want to delete.
11066 while let Some(next_selection) = selections.peek() {
11067 let next_rows = next_selection.spanned_rows(false, &display_map);
11068 if next_rows.start <= rows.end {
11069 rows.end = next_rows.end;
11070 selections.next().unwrap();
11071 } else {
11072 break;
11073 }
11074 }
11075
11076 let buffer = display_map.buffer_snapshot();
11077 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
11078 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
11079 // If there's a line after the range, delete the \n from the end of the row range
11080 (
11081 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
11082 rows.end,
11083 )
11084 } else {
11085 // If there isn't a line after the range, delete the \n from the line before the
11086 // start of the row range
11087 edit_start = edit_start.saturating_sub_usize(1);
11088 (buffer.len(), rows.start.previous_row())
11089 };
11090
11091 let text_layout_details = self.text_layout_details(window, cx);
11092 let x = display_map.x_for_display_point(
11093 selection.head().to_display_point(&display_map),
11094 &text_layout_details,
11095 );
11096 let row = Point::new(target_row.0, 0)
11097 .to_display_point(&display_map)
11098 .row();
11099 let column = display_map.display_column_for_x(row, x, &text_layout_details);
11100
11101 new_cursors.push((
11102 selection.id,
11103 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
11104 SelectionGoal::None,
11105 ));
11106 edit_ranges.push(edit_start..edit_end);
11107 }
11108
11109 self.transact(window, cx, |this, window, cx| {
11110 let buffer = this.buffer.update(cx, |buffer, cx| {
11111 let empty_str: Arc<str> = Arc::default();
11112 buffer.edit(
11113 edit_ranges
11114 .into_iter()
11115 .map(|range| (range, empty_str.clone())),
11116 None,
11117 cx,
11118 );
11119 buffer.snapshot(cx)
11120 });
11121 let new_selections = new_cursors
11122 .into_iter()
11123 .map(|(id, cursor, goal)| {
11124 let cursor = cursor.to_point(&buffer);
11125 Selection {
11126 id,
11127 start: cursor,
11128 end: cursor,
11129 reversed: false,
11130 goal,
11131 }
11132 })
11133 .collect();
11134
11135 this.change_selections(Default::default(), window, cx, |s| {
11136 s.select(new_selections);
11137 });
11138 });
11139 }
11140
11141 pub fn join_lines_impl(
11142 &mut self,
11143 insert_whitespace: bool,
11144 window: &mut Window,
11145 cx: &mut Context<Self>,
11146 ) {
11147 if self.read_only(cx) {
11148 return;
11149 }
11150 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
11151 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
11152 let start = MultiBufferRow(selection.start.row);
11153 // Treat single line selections as if they include the next line. Otherwise this action
11154 // would do nothing for single line selections individual cursors.
11155 let end = if selection.start.row == selection.end.row {
11156 MultiBufferRow(selection.start.row + 1)
11157 } else {
11158 MultiBufferRow(selection.end.row)
11159 };
11160
11161 if let Some(last_row_range) = row_ranges.last_mut()
11162 && start <= last_row_range.end
11163 {
11164 last_row_range.end = end;
11165 continue;
11166 }
11167 row_ranges.push(start..end);
11168 }
11169
11170 let snapshot = self.buffer.read(cx).snapshot(cx);
11171 let mut cursor_positions = Vec::new();
11172 for row_range in &row_ranges {
11173 let anchor = snapshot.anchor_before(Point::new(
11174 row_range.end.previous_row().0,
11175 snapshot.line_len(row_range.end.previous_row()),
11176 ));
11177 cursor_positions.push(anchor..anchor);
11178 }
11179
11180 self.transact(window, cx, |this, window, cx| {
11181 for row_range in row_ranges.into_iter().rev() {
11182 for row in row_range.iter_rows().rev() {
11183 let end_of_line = Point::new(row.0, snapshot.line_len(row));
11184 let next_line_row = row.next_row();
11185 let indent = snapshot.indent_size_for_line(next_line_row);
11186 let start_of_next_line = Point::new(next_line_row.0, indent.len);
11187
11188 let replace =
11189 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
11190 " "
11191 } else {
11192 ""
11193 };
11194
11195 this.buffer.update(cx, |buffer, cx| {
11196 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
11197 });
11198 }
11199 }
11200
11201 this.change_selections(Default::default(), window, cx, |s| {
11202 s.select_anchor_ranges(cursor_positions)
11203 });
11204 });
11205 }
11206
11207 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
11208 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11209 self.join_lines_impl(true, window, cx);
11210 }
11211
11212 pub fn sort_lines_case_sensitive(
11213 &mut self,
11214 _: &SortLinesCaseSensitive,
11215 window: &mut Window,
11216 cx: &mut Context<Self>,
11217 ) {
11218 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
11219 }
11220
11221 pub fn sort_lines_by_length(
11222 &mut self,
11223 _: &SortLinesByLength,
11224 window: &mut Window,
11225 cx: &mut Context<Self>,
11226 ) {
11227 self.manipulate_immutable_lines(window, cx, |lines| {
11228 lines.sort_by_key(|&line| line.chars().count())
11229 })
11230 }
11231
11232 pub fn sort_lines_case_insensitive(
11233 &mut self,
11234 _: &SortLinesCaseInsensitive,
11235 window: &mut Window,
11236 cx: &mut Context<Self>,
11237 ) {
11238 self.manipulate_immutable_lines(window, cx, |lines| {
11239 lines.sort_by_key(|line| line.to_lowercase())
11240 })
11241 }
11242
11243 pub fn unique_lines_case_insensitive(
11244 &mut self,
11245 _: &UniqueLinesCaseInsensitive,
11246 window: &mut Window,
11247 cx: &mut Context<Self>,
11248 ) {
11249 self.manipulate_immutable_lines(window, cx, |lines| {
11250 let mut seen = HashSet::default();
11251 lines.retain(|line| seen.insert(line.to_lowercase()));
11252 })
11253 }
11254
11255 pub fn unique_lines_case_sensitive(
11256 &mut self,
11257 _: &UniqueLinesCaseSensitive,
11258 window: &mut Window,
11259 cx: &mut Context<Self>,
11260 ) {
11261 self.manipulate_immutable_lines(window, cx, |lines| {
11262 let mut seen = HashSet::default();
11263 lines.retain(|line| seen.insert(*line));
11264 })
11265 }
11266
11267 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
11268 let snapshot = self.buffer.read(cx).snapshot(cx);
11269 for selection in self.selections.disjoint_anchors_arc().iter() {
11270 if snapshot
11271 .language_at(selection.start)
11272 .and_then(|lang| lang.config().wrap_characters.as_ref())
11273 .is_some()
11274 {
11275 return true;
11276 }
11277 }
11278 false
11279 }
11280
11281 fn wrap_selections_in_tag(
11282 &mut self,
11283 _: &WrapSelectionsInTag,
11284 window: &mut Window,
11285 cx: &mut Context<Self>,
11286 ) {
11287 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11288
11289 let snapshot = self.buffer.read(cx).snapshot(cx);
11290
11291 let mut edits = Vec::new();
11292 let mut boundaries = Vec::new();
11293
11294 for selection in self
11295 .selections
11296 .all_adjusted(&self.display_snapshot(cx))
11297 .iter()
11298 {
11299 let Some(wrap_config) = snapshot
11300 .language_at(selection.start)
11301 .and_then(|lang| lang.config().wrap_characters.clone())
11302 else {
11303 continue;
11304 };
11305
11306 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
11307 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11308
11309 let start_before = snapshot.anchor_before(selection.start);
11310 let end_after = snapshot.anchor_after(selection.end);
11311
11312 edits.push((start_before..start_before, open_tag));
11313 edits.push((end_after..end_after, close_tag));
11314
11315 boundaries.push((
11316 start_before,
11317 end_after,
11318 wrap_config.start_prefix.len(),
11319 wrap_config.end_suffix.len(),
11320 ));
11321 }
11322
11323 if edits.is_empty() {
11324 return;
11325 }
11326
11327 self.transact(window, cx, |this, window, cx| {
11328 let buffer = this.buffer.update(cx, |buffer, cx| {
11329 buffer.edit(edits, None, cx);
11330 buffer.snapshot(cx)
11331 });
11332
11333 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11334 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11335 boundaries.into_iter()
11336 {
11337 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11338 let close_offset = end_after
11339 .to_offset(&buffer)
11340 .saturating_sub_usize(end_suffix_len);
11341 new_selections.push(open_offset..open_offset);
11342 new_selections.push(close_offset..close_offset);
11343 }
11344
11345 this.change_selections(Default::default(), window, cx, |s| {
11346 s.select_ranges(new_selections);
11347 });
11348
11349 this.request_autoscroll(Autoscroll::fit(), cx);
11350 });
11351 }
11352
11353 pub fn toggle_read_only(
11354 &mut self,
11355 _: &workspace::ToggleReadOnlyFile,
11356 _: &mut Window,
11357 cx: &mut Context<Self>,
11358 ) {
11359 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
11360 buffer.update(cx, |buffer, cx| {
11361 buffer.set_capability(
11362 match buffer.capability() {
11363 Capability::ReadWrite => Capability::Read,
11364 Capability::Read => Capability::ReadWrite,
11365 Capability::ReadOnly => Capability::ReadOnly,
11366 },
11367 cx,
11368 );
11369 })
11370 }
11371 }
11372
11373 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11374 let Some(project) = self.project.clone() else {
11375 return;
11376 };
11377 self.reload(project, window, cx)
11378 .detach_and_notify_err(window, cx);
11379 }
11380
11381 pub fn restore_file(
11382 &mut self,
11383 _: &::git::RestoreFile,
11384 window: &mut Window,
11385 cx: &mut Context<Self>,
11386 ) {
11387 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11388 let mut buffer_ids = HashSet::default();
11389 let snapshot = self.buffer().read(cx).snapshot(cx);
11390 for selection in self
11391 .selections
11392 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11393 {
11394 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11395 }
11396
11397 let buffer = self.buffer().read(cx);
11398 let ranges = buffer_ids
11399 .into_iter()
11400 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11401 .collect::<Vec<_>>();
11402
11403 self.restore_hunks_in_ranges(ranges, window, cx);
11404 }
11405
11406 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11407 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11408 let selections = self
11409 .selections
11410 .all(&self.display_snapshot(cx))
11411 .into_iter()
11412 .map(|s| s.range())
11413 .collect();
11414 self.restore_hunks_in_ranges(selections, window, cx);
11415 }
11416
11417 pub fn restore_hunks_in_ranges(
11418 &mut self,
11419 ranges: Vec<Range<Point>>,
11420 window: &mut Window,
11421 cx: &mut Context<Editor>,
11422 ) {
11423 if self.delegate_stage_and_restore {
11424 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11425 if !hunks.is_empty() {
11426 cx.emit(EditorEvent::RestoreRequested { hunks });
11427 }
11428 return;
11429 }
11430 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11431 self.transact(window, cx, |editor, window, cx| {
11432 editor.restore_diff_hunks(hunks, cx);
11433 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
11434 selections.refresh()
11435 });
11436 });
11437 }
11438
11439 pub(crate) fn restore_diff_hunks(&self, hunks: Vec<MultiBufferDiffHunk>, cx: &mut App) {
11440 let mut revert_changes = HashMap::default();
11441 let chunk_by = hunks.into_iter().chunk_by(|hunk| hunk.buffer_id);
11442 for (buffer_id, hunks) in &chunk_by {
11443 let hunks = hunks.collect::<Vec<_>>();
11444 for hunk in &hunks {
11445 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11446 }
11447 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11448 }
11449 if !revert_changes.is_empty() {
11450 self.buffer().update(cx, |multi_buffer, cx| {
11451 for (buffer_id, changes) in revert_changes {
11452 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
11453 buffer.update(cx, |buffer, cx| {
11454 buffer.edit(
11455 changes
11456 .into_iter()
11457 .map(|(range, text)| (range, text.to_string())),
11458 None,
11459 cx,
11460 );
11461 });
11462 }
11463 }
11464 });
11465 }
11466 }
11467
11468 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11469 if let Some(status) = self
11470 .addons
11471 .iter()
11472 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11473 {
11474 return Some(status);
11475 }
11476 self.project
11477 .as_ref()?
11478 .read(cx)
11479 .status_for_buffer_id(buffer_id, cx)
11480 }
11481
11482 pub fn open_active_item_in_terminal(
11483 &mut self,
11484 _: &OpenInTerminal,
11485 window: &mut Window,
11486 cx: &mut Context<Self>,
11487 ) {
11488 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11489 let project_path = buffer.read(cx).project_path(cx)?;
11490 let project = self.project()?.read(cx);
11491 let entry = project.entry_for_path(&project_path, cx)?;
11492 let parent = match &entry.canonical_path {
11493 Some(canonical_path) => canonical_path.to_path_buf(),
11494 None => project.absolute_path(&project_path, cx)?,
11495 }
11496 .parent()?
11497 .to_path_buf();
11498 Some(parent)
11499 }) {
11500 window.dispatch_action(
11501 OpenTerminal {
11502 working_directory,
11503 local: false,
11504 }
11505 .boxed_clone(),
11506 cx,
11507 );
11508 }
11509 }
11510
11511 fn set_breakpoint_context_menu(
11512 &mut self,
11513 display_row: DisplayRow,
11514 position: Option<Anchor>,
11515 clicked_point: gpui::Point<Pixels>,
11516 window: &mut Window,
11517 cx: &mut Context<Self>,
11518 ) {
11519 let source = self
11520 .buffer
11521 .read(cx)
11522 .snapshot(cx)
11523 .anchor_before(Point::new(display_row.0, 0u32));
11524
11525 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11526
11527 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11528 self,
11529 source,
11530 clicked_point,
11531 context_menu,
11532 window,
11533 cx,
11534 );
11535 }
11536
11537 fn add_edit_breakpoint_block(
11538 &mut self,
11539 anchor: Anchor,
11540 breakpoint: &Breakpoint,
11541 edit_action: BreakpointPromptEditAction,
11542 window: &mut Window,
11543 cx: &mut Context<Self>,
11544 ) {
11545 let weak_editor = cx.weak_entity();
11546 let bp_prompt = cx.new(|cx| {
11547 BreakpointPromptEditor::new(
11548 weak_editor,
11549 anchor,
11550 breakpoint.clone(),
11551 edit_action,
11552 window,
11553 cx,
11554 )
11555 });
11556
11557 let height = bp_prompt.update(cx, |this, cx| {
11558 this.prompt
11559 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11560 });
11561 let cloned_prompt = bp_prompt.clone();
11562 let blocks = vec![BlockProperties {
11563 style: BlockStyle::Sticky,
11564 placement: BlockPlacement::Above(anchor),
11565 height: Some(height),
11566 render: Arc::new(move |cx| {
11567 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11568 cloned_prompt.clone().into_any_element()
11569 }),
11570 priority: 0,
11571 }];
11572
11573 let focus_handle = bp_prompt.focus_handle(cx);
11574 window.focus(&focus_handle, cx);
11575
11576 let block_ids = self.insert_blocks(blocks, None, cx);
11577 bp_prompt.update(cx, |prompt, _| {
11578 prompt.add_block_ids(block_ids);
11579 });
11580 }
11581
11582 pub(crate) fn breakpoint_at_row(
11583 &self,
11584 row: u32,
11585 window: &mut Window,
11586 cx: &mut Context<Self>,
11587 ) -> Option<(Anchor, Breakpoint)> {
11588 let snapshot = self.snapshot(window, cx);
11589 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11590
11591 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11592 }
11593
11594 pub(crate) fn breakpoint_at_anchor(
11595 &self,
11596 breakpoint_position: Anchor,
11597 snapshot: &EditorSnapshot,
11598 cx: &mut Context<Self>,
11599 ) -> Option<(Anchor, Breakpoint)> {
11600 let buffer = self
11601 .buffer
11602 .read(cx)
11603 .buffer_for_anchor(breakpoint_position, cx)?;
11604
11605 let enclosing_excerpt = breakpoint_position.excerpt_id;
11606 let buffer_snapshot = buffer.read(cx).snapshot();
11607
11608 let row = buffer_snapshot
11609 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11610 .row;
11611
11612 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11613 let anchor_end = snapshot
11614 .buffer_snapshot()
11615 .anchor_after(Point::new(row, line_len));
11616
11617 self.breakpoint_store
11618 .as_ref()?
11619 .read_with(cx, |breakpoint_store, cx| {
11620 breakpoint_store
11621 .breakpoints(
11622 &buffer,
11623 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11624 &buffer_snapshot,
11625 cx,
11626 )
11627 .next()
11628 .and_then(|(bp, _)| {
11629 let breakpoint_row = buffer_snapshot
11630 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11631 .row;
11632
11633 if breakpoint_row == row {
11634 snapshot
11635 .buffer_snapshot()
11636 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11637 .map(|position| (position, bp.bp.clone()))
11638 } else {
11639 None
11640 }
11641 })
11642 })
11643 }
11644
11645 pub fn edit_log_breakpoint(
11646 &mut self,
11647 _: &EditLogBreakpoint,
11648 window: &mut Window,
11649 cx: &mut Context<Self>,
11650 ) {
11651 if self.breakpoint_store.is_none() {
11652 return;
11653 }
11654
11655 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11656 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11657 message: None,
11658 state: BreakpointState::Enabled,
11659 condition: None,
11660 hit_condition: None,
11661 });
11662
11663 self.add_edit_breakpoint_block(
11664 anchor,
11665 &breakpoint,
11666 BreakpointPromptEditAction::Log,
11667 window,
11668 cx,
11669 );
11670 }
11671 }
11672
11673 fn breakpoints_at_cursors(
11674 &self,
11675 window: &mut Window,
11676 cx: &mut Context<Self>,
11677 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11678 let snapshot = self.snapshot(window, cx);
11679 let cursors = self
11680 .selections
11681 .disjoint_anchors_arc()
11682 .iter()
11683 .map(|selection| {
11684 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11685
11686 let breakpoint_position = self
11687 .breakpoint_at_row(cursor_position.row, window, cx)
11688 .map(|bp| bp.0)
11689 .unwrap_or_else(|| {
11690 snapshot
11691 .display_snapshot
11692 .buffer_snapshot()
11693 .anchor_after(Point::new(cursor_position.row, 0))
11694 });
11695
11696 let breakpoint = self
11697 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11698 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11699
11700 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11701 })
11702 // 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.
11703 .collect::<HashMap<Anchor, _>>();
11704
11705 cursors.into_iter().collect()
11706 }
11707
11708 pub fn enable_breakpoint(
11709 &mut self,
11710 _: &crate::actions::EnableBreakpoint,
11711 window: &mut Window,
11712 cx: &mut Context<Self>,
11713 ) {
11714 if self.breakpoint_store.is_none() {
11715 return;
11716 }
11717
11718 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11719 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11720 continue;
11721 };
11722 self.edit_breakpoint_at_anchor(
11723 anchor,
11724 breakpoint,
11725 BreakpointEditAction::InvertState,
11726 cx,
11727 );
11728 }
11729 }
11730
11731 pub fn disable_breakpoint(
11732 &mut self,
11733 _: &crate::actions::DisableBreakpoint,
11734 window: &mut Window,
11735 cx: &mut Context<Self>,
11736 ) {
11737 if self.breakpoint_store.is_none() {
11738 return;
11739 }
11740
11741 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11742 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11743 continue;
11744 };
11745 self.edit_breakpoint_at_anchor(
11746 anchor,
11747 breakpoint,
11748 BreakpointEditAction::InvertState,
11749 cx,
11750 );
11751 }
11752 }
11753
11754 pub fn toggle_breakpoint(
11755 &mut self,
11756 _: &crate::actions::ToggleBreakpoint,
11757 window: &mut Window,
11758 cx: &mut Context<Self>,
11759 ) {
11760 if self.breakpoint_store.is_none() {
11761 return;
11762 }
11763
11764 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11765 if let Some(breakpoint) = breakpoint {
11766 self.edit_breakpoint_at_anchor(
11767 anchor,
11768 breakpoint,
11769 BreakpointEditAction::Toggle,
11770 cx,
11771 );
11772 } else {
11773 self.edit_breakpoint_at_anchor(
11774 anchor,
11775 Breakpoint::new_standard(),
11776 BreakpointEditAction::Toggle,
11777 cx,
11778 );
11779 }
11780 }
11781 }
11782
11783 pub fn edit_breakpoint_at_anchor(
11784 &mut self,
11785 breakpoint_position: Anchor,
11786 breakpoint: Breakpoint,
11787 edit_action: BreakpointEditAction,
11788 cx: &mut Context<Self>,
11789 ) {
11790 let Some(breakpoint_store) = &self.breakpoint_store else {
11791 return;
11792 };
11793
11794 let Some(buffer) = self
11795 .buffer
11796 .read(cx)
11797 .buffer_for_anchor(breakpoint_position, cx)
11798 else {
11799 return;
11800 };
11801
11802 breakpoint_store.update(cx, |breakpoint_store, cx| {
11803 breakpoint_store.toggle_breakpoint(
11804 buffer,
11805 BreakpointWithPosition {
11806 position: breakpoint_position.text_anchor,
11807 bp: breakpoint,
11808 },
11809 edit_action,
11810 cx,
11811 );
11812 });
11813
11814 cx.notify();
11815 }
11816
11817 #[cfg(any(test, feature = "test-support"))]
11818 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11819 self.breakpoint_store.clone()
11820 }
11821
11822 pub fn prepare_restore_change(
11823 &self,
11824 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11825 hunk: &MultiBufferDiffHunk,
11826 cx: &mut App,
11827 ) -> Option<()> {
11828 if hunk.is_created_file() {
11829 return None;
11830 }
11831 let buffer = self.buffer.read(cx);
11832 let diff = buffer.diff_for(hunk.buffer_id)?;
11833 let buffer = buffer.buffer(hunk.buffer_id)?;
11834 let buffer = buffer.read(cx);
11835 let original_text = diff
11836 .read(cx)
11837 .base_text(cx)
11838 .as_rope()
11839 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
11840 let buffer_snapshot = buffer.snapshot();
11841 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11842 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11843 probe
11844 .0
11845 .start
11846 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11847 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11848 }) {
11849 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11850 Some(())
11851 } else {
11852 None
11853 }
11854 }
11855
11856 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11857 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11858 }
11859
11860 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11861 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11862 }
11863
11864 pub fn rotate_selections_forward(
11865 &mut self,
11866 _: &RotateSelectionsForward,
11867 window: &mut Window,
11868 cx: &mut Context<Self>,
11869 ) {
11870 self.rotate_selections(window, cx, false)
11871 }
11872
11873 pub fn rotate_selections_backward(
11874 &mut self,
11875 _: &RotateSelectionsBackward,
11876 window: &mut Window,
11877 cx: &mut Context<Self>,
11878 ) {
11879 self.rotate_selections(window, cx, true)
11880 }
11881
11882 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
11883 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11884 let display_snapshot = self.display_snapshot(cx);
11885 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
11886
11887 if selections.len() < 2 {
11888 return;
11889 }
11890
11891 let (edits, new_selections) = {
11892 let buffer = self.buffer.read(cx).read(cx);
11893 let has_selections = selections.iter().any(|s| !s.is_empty());
11894 if has_selections {
11895 let mut selected_texts: Vec<String> = selections
11896 .iter()
11897 .map(|selection| {
11898 buffer
11899 .text_for_range(selection.start..selection.end)
11900 .collect()
11901 })
11902 .collect();
11903
11904 if reverse {
11905 selected_texts.rotate_left(1);
11906 } else {
11907 selected_texts.rotate_right(1);
11908 }
11909
11910 let mut offset_delta: i64 = 0;
11911 let mut new_selections = Vec::new();
11912 let edits: Vec<_> = selections
11913 .iter()
11914 .zip(selected_texts.iter())
11915 .map(|(selection, new_text)| {
11916 let old_len = (selection.end.0 - selection.start.0) as i64;
11917 let new_len = new_text.len() as i64;
11918 let adjusted_start =
11919 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
11920 let adjusted_end =
11921 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
11922
11923 new_selections.push(Selection {
11924 id: selection.id,
11925 start: adjusted_start,
11926 end: adjusted_end,
11927 reversed: selection.reversed,
11928 goal: selection.goal,
11929 });
11930
11931 offset_delta += new_len - old_len;
11932 (selection.start..selection.end, new_text.clone())
11933 })
11934 .collect();
11935 (edits, new_selections)
11936 } else {
11937 let mut all_rows: Vec<u32> = selections
11938 .iter()
11939 .map(|selection| buffer.offset_to_point(selection.start).row)
11940 .collect();
11941 all_rows.sort_unstable();
11942 all_rows.dedup();
11943
11944 if all_rows.len() < 2 {
11945 return;
11946 }
11947
11948 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
11949 .iter()
11950 .map(|&row| {
11951 let start = Point::new(row, 0);
11952 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11953 buffer.point_to_offset(start)..buffer.point_to_offset(end)
11954 })
11955 .collect();
11956
11957 let mut line_texts: Vec<String> = line_ranges
11958 .iter()
11959 .map(|range| buffer.text_for_range(range.clone()).collect())
11960 .collect();
11961
11962 if reverse {
11963 line_texts.rotate_left(1);
11964 } else {
11965 line_texts.rotate_right(1);
11966 }
11967
11968 let edits = line_ranges
11969 .iter()
11970 .zip(line_texts.iter())
11971 .map(|(range, new_text)| (range.clone(), new_text.clone()))
11972 .collect();
11973
11974 let num_rows = all_rows.len();
11975 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
11976 .iter()
11977 .enumerate()
11978 .map(|(i, &row)| (row, i))
11979 .collect();
11980
11981 // Compute new line start offsets after rotation (handles CRLF)
11982 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
11983 let first_line_start = line_ranges[0].start.0;
11984 let mut new_line_starts: Vec<usize> = vec![first_line_start];
11985 for text in line_texts.iter().take(num_rows - 1) {
11986 let prev_start = *new_line_starts.last().unwrap();
11987 new_line_starts.push(prev_start + text.len() + newline_len);
11988 }
11989
11990 let new_selections = selections
11991 .iter()
11992 .map(|selection| {
11993 let point = buffer.offset_to_point(selection.start);
11994 let old_index = row_to_index[&point.row];
11995 let new_index = if reverse {
11996 (old_index + num_rows - 1) % num_rows
11997 } else {
11998 (old_index + 1) % num_rows
11999 };
12000 let new_offset =
12001 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
12002 Selection {
12003 id: selection.id,
12004 start: new_offset,
12005 end: new_offset,
12006 reversed: selection.reversed,
12007 goal: selection.goal,
12008 }
12009 })
12010 .collect();
12011
12012 (edits, new_selections)
12013 }
12014 };
12015
12016 self.transact(window, cx, |this, window, cx| {
12017 this.buffer.update(cx, |buffer, cx| {
12018 buffer.edit(edits, None, cx);
12019 });
12020 this.change_selections(Default::default(), window, cx, |s| {
12021 s.select(new_selections);
12022 });
12023 });
12024 }
12025
12026 fn manipulate_lines<M>(
12027 &mut self,
12028 window: &mut Window,
12029 cx: &mut Context<Self>,
12030 mut manipulate: M,
12031 ) where
12032 M: FnMut(&str) -> LineManipulationResult,
12033 {
12034 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12035
12036 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12037 let buffer = self.buffer.read(cx).snapshot(cx);
12038
12039 let mut edits = Vec::new();
12040
12041 let selections = self.selections.all::<Point>(&display_map);
12042 let mut selections = selections.iter().peekable();
12043 let mut contiguous_row_selections = Vec::new();
12044 let mut new_selections = Vec::new();
12045 let mut added_lines = 0;
12046 let mut removed_lines = 0;
12047
12048 while let Some(selection) = selections.next() {
12049 let (start_row, end_row) = consume_contiguous_rows(
12050 &mut contiguous_row_selections,
12051 selection,
12052 &display_map,
12053 &mut selections,
12054 );
12055
12056 let start_point = Point::new(start_row.0, 0);
12057 let end_point = Point::new(
12058 end_row.previous_row().0,
12059 buffer.line_len(end_row.previous_row()),
12060 );
12061 let text = buffer
12062 .text_for_range(start_point..end_point)
12063 .collect::<String>();
12064
12065 let LineManipulationResult {
12066 new_text,
12067 line_count_before,
12068 line_count_after,
12069 } = manipulate(&text);
12070
12071 edits.push((start_point..end_point, new_text));
12072
12073 // Selections must change based on added and removed line count
12074 let start_row =
12075 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
12076 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
12077 new_selections.push(Selection {
12078 id: selection.id,
12079 start: start_row,
12080 end: end_row,
12081 goal: SelectionGoal::None,
12082 reversed: selection.reversed,
12083 });
12084
12085 if line_count_after > line_count_before {
12086 added_lines += line_count_after - line_count_before;
12087 } else if line_count_before > line_count_after {
12088 removed_lines += line_count_before - line_count_after;
12089 }
12090 }
12091
12092 self.transact(window, cx, |this, window, cx| {
12093 let buffer = this.buffer.update(cx, |buffer, cx| {
12094 buffer.edit(edits, None, cx);
12095 buffer.snapshot(cx)
12096 });
12097
12098 // Recalculate offsets on newly edited buffer
12099 let new_selections = new_selections
12100 .iter()
12101 .map(|s| {
12102 let start_point = Point::new(s.start.0, 0);
12103 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
12104 Selection {
12105 id: s.id,
12106 start: buffer.point_to_offset(start_point),
12107 end: buffer.point_to_offset(end_point),
12108 goal: s.goal,
12109 reversed: s.reversed,
12110 }
12111 })
12112 .collect();
12113
12114 this.change_selections(Default::default(), window, cx, |s| {
12115 s.select(new_selections);
12116 });
12117
12118 this.request_autoscroll(Autoscroll::fit(), cx);
12119 });
12120 }
12121
12122 fn manipulate_immutable_lines<Fn>(
12123 &mut self,
12124 window: &mut Window,
12125 cx: &mut Context<Self>,
12126 mut callback: Fn,
12127 ) where
12128 Fn: FnMut(&mut Vec<&str>),
12129 {
12130 self.manipulate_lines(window, cx, |text| {
12131 let mut lines: Vec<&str> = text.split('\n').collect();
12132 let line_count_before = lines.len();
12133
12134 callback(&mut lines);
12135
12136 LineManipulationResult {
12137 new_text: lines.join("\n"),
12138 line_count_before,
12139 line_count_after: lines.len(),
12140 }
12141 });
12142 }
12143
12144 fn manipulate_mutable_lines<Fn>(
12145 &mut self,
12146 window: &mut Window,
12147 cx: &mut Context<Self>,
12148 mut callback: Fn,
12149 ) where
12150 Fn: FnMut(&mut Vec<Cow<'_, str>>),
12151 {
12152 self.manipulate_lines(window, cx, |text| {
12153 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
12154 let line_count_before = lines.len();
12155
12156 callback(&mut lines);
12157
12158 LineManipulationResult {
12159 new_text: lines.join("\n"),
12160 line_count_before,
12161 line_count_after: lines.len(),
12162 }
12163 });
12164 }
12165
12166 pub fn convert_indentation_to_spaces(
12167 &mut self,
12168 _: &ConvertIndentationToSpaces,
12169 window: &mut Window,
12170 cx: &mut Context<Self>,
12171 ) {
12172 let settings = self.buffer.read(cx).language_settings(cx);
12173 let tab_size = settings.tab_size.get() as usize;
12174
12175 self.manipulate_mutable_lines(window, cx, |lines| {
12176 // Allocates a reasonably sized scratch buffer once for the whole loop
12177 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12178 // Avoids recomputing spaces that could be inserted many times
12179 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12180 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12181 .collect();
12182
12183 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12184 let mut chars = line.as_ref().chars();
12185 let mut col = 0;
12186 let mut changed = false;
12187
12188 for ch in chars.by_ref() {
12189 match ch {
12190 ' ' => {
12191 reindented_line.push(' ');
12192 col += 1;
12193 }
12194 '\t' => {
12195 // \t are converted to spaces depending on the current column
12196 let spaces_len = tab_size - (col % tab_size);
12197 reindented_line.extend(&space_cache[spaces_len - 1]);
12198 col += spaces_len;
12199 changed = true;
12200 }
12201 _ => {
12202 // If we dont append before break, the character is consumed
12203 reindented_line.push(ch);
12204 break;
12205 }
12206 }
12207 }
12208
12209 if !changed {
12210 reindented_line.clear();
12211 continue;
12212 }
12213 // Append the rest of the line and replace old reference with new one
12214 reindented_line.extend(chars);
12215 *line = Cow::Owned(reindented_line.clone());
12216 reindented_line.clear();
12217 }
12218 });
12219 }
12220
12221 pub fn convert_indentation_to_tabs(
12222 &mut self,
12223 _: &ConvertIndentationToTabs,
12224 window: &mut Window,
12225 cx: &mut Context<Self>,
12226 ) {
12227 let settings = self.buffer.read(cx).language_settings(cx);
12228 let tab_size = settings.tab_size.get() as usize;
12229
12230 self.manipulate_mutable_lines(window, cx, |lines| {
12231 // Allocates a reasonably sized buffer once for the whole loop
12232 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12233 // Avoids recomputing spaces that could be inserted many times
12234 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12235 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12236 .collect();
12237
12238 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12239 let mut chars = line.chars();
12240 let mut spaces_count = 0;
12241 let mut first_non_indent_char = None;
12242 let mut changed = false;
12243
12244 for ch in chars.by_ref() {
12245 match ch {
12246 ' ' => {
12247 // Keep track of spaces. Append \t when we reach tab_size
12248 spaces_count += 1;
12249 changed = true;
12250 if spaces_count == tab_size {
12251 reindented_line.push('\t');
12252 spaces_count = 0;
12253 }
12254 }
12255 '\t' => {
12256 reindented_line.push('\t');
12257 spaces_count = 0;
12258 }
12259 _ => {
12260 // Dont append it yet, we might have remaining spaces
12261 first_non_indent_char = Some(ch);
12262 break;
12263 }
12264 }
12265 }
12266
12267 if !changed {
12268 reindented_line.clear();
12269 continue;
12270 }
12271 // Remaining spaces that didn't make a full tab stop
12272 if spaces_count > 0 {
12273 reindented_line.extend(&space_cache[spaces_count - 1]);
12274 }
12275 // If we consume an extra character that was not indentation, add it back
12276 if let Some(extra_char) = first_non_indent_char {
12277 reindented_line.push(extra_char);
12278 }
12279 // Append the rest of the line and replace old reference with new one
12280 reindented_line.extend(chars);
12281 *line = Cow::Owned(reindented_line.clone());
12282 reindented_line.clear();
12283 }
12284 });
12285 }
12286
12287 pub fn convert_to_upper_case(
12288 &mut self,
12289 _: &ConvertToUpperCase,
12290 window: &mut Window,
12291 cx: &mut Context<Self>,
12292 ) {
12293 self.manipulate_text(window, cx, |text| text.to_uppercase())
12294 }
12295
12296 pub fn convert_to_lower_case(
12297 &mut self,
12298 _: &ConvertToLowerCase,
12299 window: &mut Window,
12300 cx: &mut Context<Self>,
12301 ) {
12302 self.manipulate_text(window, cx, |text| text.to_lowercase())
12303 }
12304
12305 pub fn convert_to_title_case(
12306 &mut self,
12307 _: &ConvertToTitleCase,
12308 window: &mut Window,
12309 cx: &mut Context<Self>,
12310 ) {
12311 self.manipulate_text(window, cx, |text| {
12312 text.split('\n')
12313 .map(|line| line.to_case(Case::Title))
12314 .join("\n")
12315 })
12316 }
12317
12318 pub fn convert_to_snake_case(
12319 &mut self,
12320 _: &ConvertToSnakeCase,
12321 window: &mut Window,
12322 cx: &mut Context<Self>,
12323 ) {
12324 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
12325 }
12326
12327 pub fn convert_to_kebab_case(
12328 &mut self,
12329 _: &ConvertToKebabCase,
12330 window: &mut Window,
12331 cx: &mut Context<Self>,
12332 ) {
12333 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
12334 }
12335
12336 pub fn convert_to_upper_camel_case(
12337 &mut self,
12338 _: &ConvertToUpperCamelCase,
12339 window: &mut Window,
12340 cx: &mut Context<Self>,
12341 ) {
12342 self.manipulate_text(window, cx, |text| {
12343 text.split('\n')
12344 .map(|line| line.to_case(Case::UpperCamel))
12345 .join("\n")
12346 })
12347 }
12348
12349 pub fn convert_to_lower_camel_case(
12350 &mut self,
12351 _: &ConvertToLowerCamelCase,
12352 window: &mut Window,
12353 cx: &mut Context<Self>,
12354 ) {
12355 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
12356 }
12357
12358 pub fn convert_to_opposite_case(
12359 &mut self,
12360 _: &ConvertToOppositeCase,
12361 window: &mut Window,
12362 cx: &mut Context<Self>,
12363 ) {
12364 self.manipulate_text(window, cx, |text| {
12365 text.chars()
12366 .fold(String::with_capacity(text.len()), |mut t, c| {
12367 if c.is_uppercase() {
12368 t.extend(c.to_lowercase());
12369 } else {
12370 t.extend(c.to_uppercase());
12371 }
12372 t
12373 })
12374 })
12375 }
12376
12377 pub fn convert_to_sentence_case(
12378 &mut self,
12379 _: &ConvertToSentenceCase,
12380 window: &mut Window,
12381 cx: &mut Context<Self>,
12382 ) {
12383 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
12384 }
12385
12386 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12387 self.manipulate_text(window, cx, |text| {
12388 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12389 if has_upper_case_characters {
12390 text.to_lowercase()
12391 } else {
12392 text.to_uppercase()
12393 }
12394 })
12395 }
12396
12397 pub fn convert_to_rot13(
12398 &mut self,
12399 _: &ConvertToRot13,
12400 window: &mut Window,
12401 cx: &mut Context<Self>,
12402 ) {
12403 self.manipulate_text(window, cx, |text| {
12404 text.chars()
12405 .map(|c| match c {
12406 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12407 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12408 _ => c,
12409 })
12410 .collect()
12411 })
12412 }
12413
12414 pub fn convert_to_rot47(
12415 &mut self,
12416 _: &ConvertToRot47,
12417 window: &mut Window,
12418 cx: &mut Context<Self>,
12419 ) {
12420 self.manipulate_text(window, cx, |text| {
12421 text.chars()
12422 .map(|c| {
12423 let code_point = c as u32;
12424 if code_point >= 33 && code_point <= 126 {
12425 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12426 }
12427 c
12428 })
12429 .collect()
12430 })
12431 }
12432
12433 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12434 where
12435 Fn: FnMut(&str) -> String,
12436 {
12437 let buffer = self.buffer.read(cx).snapshot(cx);
12438
12439 let mut new_selections = Vec::new();
12440 let mut edits = Vec::new();
12441 let mut selection_adjustment = 0isize;
12442
12443 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12444 let selection_is_empty = selection.is_empty();
12445
12446 let (start, end) = if selection_is_empty {
12447 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12448 (word_range.start, word_range.end)
12449 } else {
12450 (
12451 buffer.point_to_offset(selection.start),
12452 buffer.point_to_offset(selection.end),
12453 )
12454 };
12455
12456 let text = buffer.text_for_range(start..end).collect::<String>();
12457 let old_length = text.len() as isize;
12458 let text = callback(&text);
12459
12460 new_selections.push(Selection {
12461 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12462 end: MultiBufferOffset(
12463 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12464 ),
12465 goal: SelectionGoal::None,
12466 id: selection.id,
12467 reversed: selection.reversed,
12468 });
12469
12470 selection_adjustment += old_length - text.len() as isize;
12471
12472 edits.push((start..end, text));
12473 }
12474
12475 self.transact(window, cx, |this, window, cx| {
12476 this.buffer.update(cx, |buffer, cx| {
12477 buffer.edit(edits, None, cx);
12478 });
12479
12480 this.change_selections(Default::default(), window, cx, |s| {
12481 s.select(new_selections);
12482 });
12483
12484 this.request_autoscroll(Autoscroll::fit(), cx);
12485 });
12486 }
12487
12488 pub fn move_selection_on_drop(
12489 &mut self,
12490 selection: &Selection<Anchor>,
12491 target: DisplayPoint,
12492 is_cut: bool,
12493 window: &mut Window,
12494 cx: &mut Context<Self>,
12495 ) {
12496 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12497 let buffer = display_map.buffer_snapshot();
12498 let mut edits = Vec::new();
12499 let insert_point = display_map
12500 .clip_point(target, Bias::Left)
12501 .to_point(&display_map);
12502 let text = buffer
12503 .text_for_range(selection.start..selection.end)
12504 .collect::<String>();
12505 if is_cut {
12506 edits.push(((selection.start..selection.end), String::new()));
12507 }
12508 let insert_anchor = buffer.anchor_before(insert_point);
12509 edits.push(((insert_anchor..insert_anchor), text));
12510 let last_edit_start = insert_anchor.bias_left(buffer);
12511 let last_edit_end = insert_anchor.bias_right(buffer);
12512 self.transact(window, cx, |this, window, cx| {
12513 this.buffer.update(cx, |buffer, cx| {
12514 buffer.edit(edits, None, cx);
12515 });
12516 this.change_selections(Default::default(), window, cx, |s| {
12517 s.select_anchor_ranges([last_edit_start..last_edit_end]);
12518 });
12519 });
12520 }
12521
12522 pub fn clear_selection_drag_state(&mut self) {
12523 self.selection_drag_state = SelectionDragState::None;
12524 }
12525
12526 pub fn duplicate(
12527 &mut self,
12528 upwards: bool,
12529 whole_lines: bool,
12530 window: &mut Window,
12531 cx: &mut Context<Self>,
12532 ) {
12533 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12534
12535 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12536 let buffer = display_map.buffer_snapshot();
12537 let selections = self.selections.all::<Point>(&display_map);
12538
12539 let mut edits = Vec::new();
12540 let mut selections_iter = selections.iter().peekable();
12541 while let Some(selection) = selections_iter.next() {
12542 let mut rows = selection.spanned_rows(false, &display_map);
12543 // duplicate line-wise
12544 if whole_lines || selection.start == selection.end {
12545 // Avoid duplicating the same lines twice.
12546 while let Some(next_selection) = selections_iter.peek() {
12547 let next_rows = next_selection.spanned_rows(false, &display_map);
12548 if next_rows.start < rows.end {
12549 rows.end = next_rows.end;
12550 selections_iter.next().unwrap();
12551 } else {
12552 break;
12553 }
12554 }
12555
12556 // Copy the text from the selected row region and splice it either at the start
12557 // or end of the region.
12558 let start = Point::new(rows.start.0, 0);
12559 let end = Point::new(
12560 rows.end.previous_row().0,
12561 buffer.line_len(rows.end.previous_row()),
12562 );
12563
12564 let mut text = buffer.text_for_range(start..end).collect::<String>();
12565
12566 let insert_location = if upwards {
12567 // When duplicating upward, we need to insert before the current line.
12568 // If we're on the last line and it doesn't end with a newline,
12569 // we need to add a newline before the duplicated content.
12570 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
12571 && buffer.max_point().column > 0
12572 && !text.ends_with('\n');
12573
12574 if needs_leading_newline {
12575 text.insert(0, '\n');
12576 end
12577 } else {
12578 text.push('\n');
12579 Point::new(rows.start.0, 0)
12580 }
12581 } else {
12582 text.push('\n');
12583 start
12584 };
12585 edits.push((insert_location..insert_location, text));
12586 } else {
12587 // duplicate character-wise
12588 let start = selection.start;
12589 let end = selection.end;
12590 let text = buffer.text_for_range(start..end).collect::<String>();
12591 edits.push((selection.end..selection.end, text));
12592 }
12593 }
12594
12595 self.transact(window, cx, |this, window, cx| {
12596 this.buffer.update(cx, |buffer, cx| {
12597 buffer.edit(edits, None, cx);
12598 });
12599
12600 // When duplicating upward with whole lines, move the cursor to the duplicated line
12601 if upwards && whole_lines {
12602 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
12603
12604 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12605 let mut new_ranges = Vec::new();
12606 let selections = s.all::<Point>(&display_map);
12607 let mut selections_iter = selections.iter().peekable();
12608
12609 while let Some(first_selection) = selections_iter.next() {
12610 // Group contiguous selections together to find the total row span
12611 let mut group_selections = vec![first_selection];
12612 let mut rows = first_selection.spanned_rows(false, &display_map);
12613
12614 while let Some(next_selection) = selections_iter.peek() {
12615 let next_rows = next_selection.spanned_rows(false, &display_map);
12616 if next_rows.start < rows.end {
12617 rows.end = next_rows.end;
12618 group_selections.push(selections_iter.next().unwrap());
12619 } else {
12620 break;
12621 }
12622 }
12623
12624 let row_count = rows.end.0 - rows.start.0;
12625
12626 // Move all selections in this group up by the total number of duplicated rows
12627 for selection in group_selections {
12628 let new_start = Point::new(
12629 selection.start.row.saturating_sub(row_count),
12630 selection.start.column,
12631 );
12632
12633 let new_end = Point::new(
12634 selection.end.row.saturating_sub(row_count),
12635 selection.end.column,
12636 );
12637
12638 new_ranges.push(new_start..new_end);
12639 }
12640 }
12641
12642 s.select_ranges(new_ranges);
12643 });
12644 }
12645
12646 this.request_autoscroll(Autoscroll::fit(), cx);
12647 });
12648 }
12649
12650 pub fn duplicate_line_up(
12651 &mut self,
12652 _: &DuplicateLineUp,
12653 window: &mut Window,
12654 cx: &mut Context<Self>,
12655 ) {
12656 self.duplicate(true, true, window, cx);
12657 }
12658
12659 pub fn duplicate_line_down(
12660 &mut self,
12661 _: &DuplicateLineDown,
12662 window: &mut Window,
12663 cx: &mut Context<Self>,
12664 ) {
12665 self.duplicate(false, true, window, cx);
12666 }
12667
12668 pub fn duplicate_selection(
12669 &mut self,
12670 _: &DuplicateSelection,
12671 window: &mut Window,
12672 cx: &mut Context<Self>,
12673 ) {
12674 self.duplicate(false, false, window, cx);
12675 }
12676
12677 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12678 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12679 if self.mode.is_single_line() {
12680 cx.propagate();
12681 return;
12682 }
12683
12684 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12685 let buffer = self.buffer.read(cx).snapshot(cx);
12686
12687 let mut edits = Vec::new();
12688 let mut unfold_ranges = Vec::new();
12689 let mut refold_creases = Vec::new();
12690
12691 let selections = self.selections.all::<Point>(&display_map);
12692 let mut selections = selections.iter().peekable();
12693 let mut contiguous_row_selections = Vec::new();
12694 let mut new_selections = Vec::new();
12695
12696 while let Some(selection) = selections.next() {
12697 // Find all the selections that span a contiguous row range
12698 let (start_row, end_row) = consume_contiguous_rows(
12699 &mut contiguous_row_selections,
12700 selection,
12701 &display_map,
12702 &mut selections,
12703 );
12704
12705 // Move the text spanned by the row range to be before the line preceding the row range
12706 if start_row.0 > 0 {
12707 let range_to_move = Point::new(
12708 start_row.previous_row().0,
12709 buffer.line_len(start_row.previous_row()),
12710 )
12711 ..Point::new(
12712 end_row.previous_row().0,
12713 buffer.line_len(end_row.previous_row()),
12714 );
12715 let insertion_point = display_map
12716 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12717 .0;
12718
12719 // Don't move lines across excerpts
12720 if buffer
12721 .excerpt_containing(insertion_point..range_to_move.end)
12722 .is_some()
12723 {
12724 let text = buffer
12725 .text_for_range(range_to_move.clone())
12726 .flat_map(|s| s.chars())
12727 .skip(1)
12728 .chain(['\n'])
12729 .collect::<String>();
12730
12731 edits.push((
12732 buffer.anchor_after(range_to_move.start)
12733 ..buffer.anchor_before(range_to_move.end),
12734 String::new(),
12735 ));
12736 let insertion_anchor = buffer.anchor_after(insertion_point);
12737 edits.push((insertion_anchor..insertion_anchor, text));
12738
12739 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12740
12741 // Move selections up
12742 new_selections.extend(contiguous_row_selections.drain(..).map(
12743 |mut selection| {
12744 selection.start.row -= row_delta;
12745 selection.end.row -= row_delta;
12746 selection
12747 },
12748 ));
12749
12750 // Move folds up
12751 unfold_ranges.push(range_to_move.clone());
12752 for fold in display_map.folds_in_range(
12753 buffer.anchor_before(range_to_move.start)
12754 ..buffer.anchor_after(range_to_move.end),
12755 ) {
12756 let mut start = fold.range.start.to_point(&buffer);
12757 let mut end = fold.range.end.to_point(&buffer);
12758 start.row -= row_delta;
12759 end.row -= row_delta;
12760 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12761 }
12762 }
12763 }
12764
12765 // If we didn't move line(s), preserve the existing selections
12766 new_selections.append(&mut contiguous_row_selections);
12767 }
12768
12769 self.transact(window, cx, |this, window, cx| {
12770 this.unfold_ranges(&unfold_ranges, true, true, cx);
12771 this.buffer.update(cx, |buffer, cx| {
12772 for (range, text) in edits {
12773 buffer.edit([(range, text)], None, cx);
12774 }
12775 });
12776 this.fold_creases(refold_creases, true, window, cx);
12777 this.change_selections(Default::default(), window, cx, |s| {
12778 s.select(new_selections);
12779 })
12780 });
12781 }
12782
12783 pub fn move_line_down(
12784 &mut self,
12785 _: &MoveLineDown,
12786 window: &mut Window,
12787 cx: &mut Context<Self>,
12788 ) {
12789 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12790 if self.mode.is_single_line() {
12791 cx.propagate();
12792 return;
12793 }
12794
12795 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12796 let buffer = self.buffer.read(cx).snapshot(cx);
12797
12798 let mut edits = Vec::new();
12799 let mut unfold_ranges = Vec::new();
12800 let mut refold_creases = Vec::new();
12801
12802 let selections = self.selections.all::<Point>(&display_map);
12803 let mut selections = selections.iter().peekable();
12804 let mut contiguous_row_selections = Vec::new();
12805 let mut new_selections = Vec::new();
12806
12807 while let Some(selection) = selections.next() {
12808 // Find all the selections that span a contiguous row range
12809 let (start_row, end_row) = consume_contiguous_rows(
12810 &mut contiguous_row_selections,
12811 selection,
12812 &display_map,
12813 &mut selections,
12814 );
12815
12816 // Move the text spanned by the row range to be after the last line of the row range
12817 if end_row.0 <= buffer.max_point().row {
12818 let range_to_move =
12819 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12820 let insertion_point = display_map
12821 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12822 .0;
12823
12824 // Don't move lines across excerpt boundaries
12825 if buffer
12826 .excerpt_containing(range_to_move.start..insertion_point)
12827 .is_some()
12828 {
12829 let mut text = String::from("\n");
12830 text.extend(buffer.text_for_range(range_to_move.clone()));
12831 text.pop(); // Drop trailing newline
12832 edits.push((
12833 buffer.anchor_after(range_to_move.start)
12834 ..buffer.anchor_before(range_to_move.end),
12835 String::new(),
12836 ));
12837 let insertion_anchor = buffer.anchor_after(insertion_point);
12838 edits.push((insertion_anchor..insertion_anchor, text));
12839
12840 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12841
12842 // Move selections down
12843 new_selections.extend(contiguous_row_selections.drain(..).map(
12844 |mut selection| {
12845 selection.start.row += row_delta;
12846 selection.end.row += row_delta;
12847 selection
12848 },
12849 ));
12850
12851 // Move folds down
12852 unfold_ranges.push(range_to_move.clone());
12853 for fold in display_map.folds_in_range(
12854 buffer.anchor_before(range_to_move.start)
12855 ..buffer.anchor_after(range_to_move.end),
12856 ) {
12857 let mut start = fold.range.start.to_point(&buffer);
12858 let mut end = fold.range.end.to_point(&buffer);
12859 start.row += row_delta;
12860 end.row += row_delta;
12861 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12862 }
12863 }
12864 }
12865
12866 // If we didn't move line(s), preserve the existing selections
12867 new_selections.append(&mut contiguous_row_selections);
12868 }
12869
12870 self.transact(window, cx, |this, window, cx| {
12871 this.unfold_ranges(&unfold_ranges, true, true, cx);
12872 this.buffer.update(cx, |buffer, cx| {
12873 for (range, text) in edits {
12874 buffer.edit([(range, text)], None, cx);
12875 }
12876 });
12877 this.fold_creases(refold_creases, true, window, cx);
12878 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12879 });
12880 }
12881
12882 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12883 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12884 let text_layout_details = &self.text_layout_details(window, cx);
12885 self.transact(window, cx, |this, window, cx| {
12886 let edits = this.change_selections(Default::default(), window, cx, |s| {
12887 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
12888 s.move_with(|display_map, selection| {
12889 if !selection.is_empty() {
12890 return;
12891 }
12892
12893 let mut head = selection.head();
12894 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12895 if head.column() == display_map.line_len(head.row()) {
12896 transpose_offset = display_map
12897 .buffer_snapshot()
12898 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12899 }
12900
12901 if transpose_offset == MultiBufferOffset(0) {
12902 return;
12903 }
12904
12905 *head.column_mut() += 1;
12906 head = display_map.clip_point(head, Bias::Right);
12907 let goal = SelectionGoal::HorizontalPosition(
12908 display_map
12909 .x_for_display_point(head, text_layout_details)
12910 .into(),
12911 );
12912 selection.collapse_to(head, goal);
12913
12914 let transpose_start = display_map
12915 .buffer_snapshot()
12916 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12917 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12918 let transpose_end = display_map
12919 .buffer_snapshot()
12920 .clip_offset(transpose_offset + 1usize, Bias::Right);
12921 if let Some(ch) = display_map
12922 .buffer_snapshot()
12923 .chars_at(transpose_start)
12924 .next()
12925 {
12926 edits.push((transpose_start..transpose_offset, String::new()));
12927 edits.push((transpose_end..transpose_end, ch.to_string()));
12928 }
12929 }
12930 });
12931 edits
12932 });
12933 this.buffer
12934 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12935 let selections = this
12936 .selections
12937 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12938 this.change_selections(Default::default(), window, cx, |s| {
12939 s.select(selections);
12940 });
12941 });
12942 }
12943
12944 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12945 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12946 if self.mode.is_single_line() {
12947 cx.propagate();
12948 return;
12949 }
12950
12951 self.rewrap_impl(RewrapOptions::default(), cx)
12952 }
12953
12954 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12955 let buffer = self.buffer.read(cx).snapshot(cx);
12956 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12957
12958 #[derive(Clone, Debug, PartialEq)]
12959 enum CommentFormat {
12960 /// single line comment, with prefix for line
12961 Line(String),
12962 /// single line within a block comment, with prefix for line
12963 BlockLine(String),
12964 /// a single line of a block comment that includes the initial delimiter
12965 BlockCommentWithStart(BlockCommentConfig),
12966 /// a single line of a block comment that includes the ending delimiter
12967 BlockCommentWithEnd(BlockCommentConfig),
12968 }
12969
12970 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12971 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12972 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12973 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12974 .peekable();
12975
12976 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12977 row
12978 } else {
12979 return Vec::new();
12980 };
12981
12982 let language_settings = buffer.language_settings_at(selection.head(), cx);
12983 let language_scope = buffer.language_scope_at(selection.head());
12984
12985 let indent_and_prefix_for_row =
12986 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12987 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12988 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12989 &language_scope
12990 {
12991 let indent_end = Point::new(row, indent.len);
12992 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12993 let line_text_after_indent = buffer
12994 .text_for_range(indent_end..line_end)
12995 .collect::<String>();
12996
12997 let is_within_comment_override = buffer
12998 .language_scope_at(indent_end)
12999 .is_some_and(|scope| scope.override_name() == Some("comment"));
13000 let comment_delimiters = if is_within_comment_override {
13001 // we are within a comment syntax node, but we don't
13002 // yet know what kind of comment: block, doc or line
13003 match (
13004 language_scope.documentation_comment(),
13005 language_scope.block_comment(),
13006 ) {
13007 (Some(config), _) | (_, Some(config))
13008 if buffer.contains_str_at(indent_end, &config.start) =>
13009 {
13010 Some(CommentFormat::BlockCommentWithStart(config.clone()))
13011 }
13012 (Some(config), _) | (_, Some(config))
13013 if line_text_after_indent.ends_with(config.end.as_ref()) =>
13014 {
13015 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
13016 }
13017 (Some(config), _) | (_, Some(config))
13018 if buffer.contains_str_at(indent_end, &config.prefix) =>
13019 {
13020 Some(CommentFormat::BlockLine(config.prefix.to_string()))
13021 }
13022 (_, _) => language_scope
13023 .line_comment_prefixes()
13024 .iter()
13025 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13026 .map(|prefix| CommentFormat::Line(prefix.to_string())),
13027 }
13028 } else {
13029 // we not in an overridden comment node, but we may
13030 // be within a non-overridden line comment node
13031 language_scope
13032 .line_comment_prefixes()
13033 .iter()
13034 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13035 .map(|prefix| CommentFormat::Line(prefix.to_string()))
13036 };
13037
13038 let rewrap_prefix = language_scope
13039 .rewrap_prefixes()
13040 .iter()
13041 .find_map(|prefix_regex| {
13042 prefix_regex.find(&line_text_after_indent).map(|mat| {
13043 if mat.start() == 0 {
13044 Some(mat.as_str().to_string())
13045 } else {
13046 None
13047 }
13048 })
13049 })
13050 .flatten();
13051 (comment_delimiters, rewrap_prefix)
13052 } else {
13053 (None, None)
13054 };
13055 (indent, comment_prefix, rewrap_prefix)
13056 };
13057
13058 let mut ranges = Vec::new();
13059 let from_empty_selection = selection.is_empty();
13060
13061 let mut current_range_start = first_row;
13062 let mut prev_row = first_row;
13063 let (
13064 mut current_range_indent,
13065 mut current_range_comment_delimiters,
13066 mut current_range_rewrap_prefix,
13067 ) = indent_and_prefix_for_row(first_row);
13068
13069 for row in non_blank_rows_iter.skip(1) {
13070 let has_paragraph_break = row > prev_row + 1;
13071
13072 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
13073 indent_and_prefix_for_row(row);
13074
13075 let has_indent_change = row_indent != current_range_indent;
13076 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
13077
13078 let has_boundary_change = has_comment_change
13079 || row_rewrap_prefix.is_some()
13080 || (has_indent_change && current_range_comment_delimiters.is_some());
13081
13082 if has_paragraph_break || has_boundary_change {
13083 ranges.push((
13084 language_settings.clone(),
13085 Point::new(current_range_start, 0)
13086 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13087 current_range_indent,
13088 current_range_comment_delimiters.clone(),
13089 current_range_rewrap_prefix.clone(),
13090 from_empty_selection,
13091 ));
13092 current_range_start = row;
13093 current_range_indent = row_indent;
13094 current_range_comment_delimiters = row_comment_delimiters;
13095 current_range_rewrap_prefix = row_rewrap_prefix;
13096 }
13097 prev_row = row;
13098 }
13099
13100 ranges.push((
13101 language_settings.clone(),
13102 Point::new(current_range_start, 0)
13103 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13104 current_range_indent,
13105 current_range_comment_delimiters,
13106 current_range_rewrap_prefix,
13107 from_empty_selection,
13108 ));
13109
13110 ranges
13111 });
13112
13113 let mut edits = Vec::new();
13114 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
13115
13116 for (
13117 language_settings,
13118 wrap_range,
13119 mut indent_size,
13120 comment_prefix,
13121 rewrap_prefix,
13122 from_empty_selection,
13123 ) in wrap_ranges
13124 {
13125 let mut start_row = wrap_range.start.row;
13126 let mut end_row = wrap_range.end.row;
13127
13128 // Skip selections that overlap with a range that has already been rewrapped.
13129 let selection_range = start_row..end_row;
13130 if rewrapped_row_ranges
13131 .iter()
13132 .any(|range| range.overlaps(&selection_range))
13133 {
13134 continue;
13135 }
13136
13137 let tab_size = language_settings.tab_size;
13138
13139 let (line_prefix, inside_comment) = match &comment_prefix {
13140 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
13141 (Some(prefix.as_str()), true)
13142 }
13143 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
13144 (Some(prefix.as_ref()), true)
13145 }
13146 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13147 start: _,
13148 end: _,
13149 prefix,
13150 tab_size,
13151 })) => {
13152 indent_size.len += tab_size;
13153 (Some(prefix.as_ref()), true)
13154 }
13155 None => (None, false),
13156 };
13157 let indent_prefix = indent_size.chars().collect::<String>();
13158 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
13159
13160 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
13161 RewrapBehavior::InComments => inside_comment,
13162 RewrapBehavior::InSelections => !wrap_range.is_empty(),
13163 RewrapBehavior::Anywhere => true,
13164 };
13165
13166 let should_rewrap = options.override_language_settings
13167 || allow_rewrap_based_on_language
13168 || self.hard_wrap.is_some();
13169 if !should_rewrap {
13170 continue;
13171 }
13172
13173 if from_empty_selection {
13174 'expand_upwards: while start_row > 0 {
13175 let prev_row = start_row - 1;
13176 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
13177 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
13178 && !buffer.is_line_blank(MultiBufferRow(prev_row))
13179 {
13180 start_row = prev_row;
13181 } else {
13182 break 'expand_upwards;
13183 }
13184 }
13185
13186 'expand_downwards: while end_row < buffer.max_point().row {
13187 let next_row = end_row + 1;
13188 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
13189 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
13190 && !buffer.is_line_blank(MultiBufferRow(next_row))
13191 {
13192 end_row = next_row;
13193 } else {
13194 break 'expand_downwards;
13195 }
13196 }
13197 }
13198
13199 let start = Point::new(start_row, 0);
13200 let start_offset = ToOffset::to_offset(&start, &buffer);
13201 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
13202 let selection_text = buffer.text_for_range(start..end).collect::<String>();
13203 let mut first_line_delimiter = None;
13204 let mut last_line_delimiter = None;
13205 let Some(lines_without_prefixes) = selection_text
13206 .lines()
13207 .enumerate()
13208 .map(|(ix, line)| {
13209 let line_trimmed = line.trim_start();
13210 if rewrap_prefix.is_some() && ix > 0 {
13211 Ok(line_trimmed)
13212 } else if let Some(
13213 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13214 start,
13215 prefix,
13216 end,
13217 tab_size,
13218 })
13219 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
13220 start,
13221 prefix,
13222 end,
13223 tab_size,
13224 }),
13225 ) = &comment_prefix
13226 {
13227 let line_trimmed = line_trimmed
13228 .strip_prefix(start.as_ref())
13229 .map(|s| {
13230 let mut indent_size = indent_size;
13231 indent_size.len -= tab_size;
13232 let indent_prefix: String = indent_size.chars().collect();
13233 first_line_delimiter = Some((indent_prefix, start));
13234 s.trim_start()
13235 })
13236 .unwrap_or(line_trimmed);
13237 let line_trimmed = line_trimmed
13238 .strip_suffix(end.as_ref())
13239 .map(|s| {
13240 last_line_delimiter = Some(end);
13241 s.trim_end()
13242 })
13243 .unwrap_or(line_trimmed);
13244 let line_trimmed = line_trimmed
13245 .strip_prefix(prefix.as_ref())
13246 .unwrap_or(line_trimmed);
13247 Ok(line_trimmed)
13248 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
13249 line_trimmed.strip_prefix(prefix).with_context(|| {
13250 format!("line did not start with prefix {prefix:?}: {line:?}")
13251 })
13252 } else {
13253 line_trimmed
13254 .strip_prefix(&line_prefix.trim_start())
13255 .with_context(|| {
13256 format!("line did not start with prefix {line_prefix:?}: {line:?}")
13257 })
13258 }
13259 })
13260 .collect::<Result<Vec<_>, _>>()
13261 .log_err()
13262 else {
13263 continue;
13264 };
13265
13266 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
13267 buffer
13268 .language_settings_at(Point::new(start_row, 0), cx)
13269 .preferred_line_length as usize
13270 });
13271
13272 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
13273 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
13274 } else {
13275 line_prefix.clone()
13276 };
13277
13278 let wrapped_text = {
13279 let mut wrapped_text = wrap_with_prefix(
13280 line_prefix,
13281 subsequent_lines_prefix,
13282 lines_without_prefixes.join("\n"),
13283 wrap_column,
13284 tab_size,
13285 options.preserve_existing_whitespace,
13286 );
13287
13288 if let Some((indent, delimiter)) = first_line_delimiter {
13289 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
13290 }
13291 if let Some(last_line) = last_line_delimiter {
13292 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
13293 }
13294
13295 wrapped_text
13296 };
13297
13298 // TODO: should always use char-based diff while still supporting cursor behavior that
13299 // matches vim.
13300 let mut diff_options = DiffOptions::default();
13301 if options.override_language_settings {
13302 diff_options.max_word_diff_len = 0;
13303 diff_options.max_word_diff_line_count = 0;
13304 } else {
13305 diff_options.max_word_diff_len = usize::MAX;
13306 diff_options.max_word_diff_line_count = usize::MAX;
13307 }
13308
13309 for (old_range, new_text) in
13310 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
13311 {
13312 let edit_start = buffer.anchor_after(start_offset + old_range.start);
13313 let edit_end = buffer.anchor_after(start_offset + old_range.end);
13314 edits.push((edit_start..edit_end, new_text));
13315 }
13316
13317 rewrapped_row_ranges.push(start_row..=end_row);
13318 }
13319
13320 self.buffer
13321 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13322 }
13323
13324 pub fn cut_common(
13325 &mut self,
13326 cut_no_selection_line: bool,
13327 window: &mut Window,
13328 cx: &mut Context<Self>,
13329 ) -> ClipboardItem {
13330 let mut text = String::new();
13331 let buffer = self.buffer.read(cx).snapshot(cx);
13332 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13333 let mut clipboard_selections = Vec::with_capacity(selections.len());
13334 {
13335 let max_point = buffer.max_point();
13336 let mut is_first = true;
13337 let mut prev_selection_was_entire_line = false;
13338 for selection in &mut selections {
13339 let is_entire_line =
13340 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
13341 if is_entire_line {
13342 selection.start = Point::new(selection.start.row, 0);
13343 if !selection.is_empty() && selection.end.column == 0 {
13344 selection.end = cmp::min(max_point, selection.end);
13345 } else {
13346 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
13347 }
13348 selection.goal = SelectionGoal::None;
13349 }
13350 if is_first {
13351 is_first = false;
13352 } else if !prev_selection_was_entire_line {
13353 text += "\n";
13354 }
13355 prev_selection_was_entire_line = is_entire_line;
13356 let mut len = 0;
13357 for chunk in buffer.text_for_range(selection.start..selection.end) {
13358 text.push_str(chunk);
13359 len += chunk.len();
13360 }
13361
13362 clipboard_selections.push(ClipboardSelection::for_buffer(
13363 len,
13364 is_entire_line,
13365 selection.range(),
13366 &buffer,
13367 self.project.as_ref(),
13368 cx,
13369 ));
13370 }
13371 }
13372
13373 self.transact(window, cx, |this, window, cx| {
13374 this.change_selections(Default::default(), window, cx, |s| {
13375 s.select(selections);
13376 });
13377 this.insert("", window, cx);
13378 });
13379 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13380 }
13381
13382 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13383 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13384 let item = self.cut_common(true, window, cx);
13385 cx.write_to_clipboard(item);
13386 }
13387
13388 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13389 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13390 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13391 s.move_with(|snapshot, sel| {
13392 if sel.is_empty() {
13393 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13394 }
13395 if sel.is_empty() {
13396 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13397 }
13398 });
13399 });
13400 let item = self.cut_common(false, window, cx);
13401 cx.set_global(KillRing(item))
13402 }
13403
13404 pub fn kill_ring_yank(
13405 &mut self,
13406 _: &KillRingYank,
13407 window: &mut Window,
13408 cx: &mut Context<Self>,
13409 ) {
13410 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13411 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13412 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13413 (kill_ring.text().to_string(), kill_ring.metadata_json())
13414 } else {
13415 return;
13416 }
13417 } else {
13418 return;
13419 };
13420 self.do_paste(&text, metadata, false, window, cx);
13421 }
13422
13423 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13424 self.do_copy(true, cx);
13425 }
13426
13427 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13428 self.do_copy(false, cx);
13429 }
13430
13431 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13432 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13433 let buffer = self.buffer.read(cx).read(cx);
13434 let mut text = String::new();
13435
13436 let mut clipboard_selections = Vec::with_capacity(selections.len());
13437 {
13438 let max_point = buffer.max_point();
13439 let mut is_first = true;
13440 let mut prev_selection_was_entire_line = false;
13441 for selection in &selections {
13442 let mut start = selection.start;
13443 let mut end = selection.end;
13444 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13445 let mut add_trailing_newline = false;
13446 if is_entire_line {
13447 start = Point::new(start.row, 0);
13448 let next_line_start = Point::new(end.row + 1, 0);
13449 if next_line_start <= max_point {
13450 end = next_line_start;
13451 } else {
13452 // We're on the last line without a trailing newline.
13453 // Copy to the end of the line and add a newline afterwards.
13454 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13455 add_trailing_newline = true;
13456 }
13457 }
13458
13459 let mut trimmed_selections = Vec::new();
13460 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
13461 let row = MultiBufferRow(start.row);
13462 let first_indent = buffer.indent_size_for_line(row);
13463 if first_indent.len == 0 || start.column > first_indent.len {
13464 trimmed_selections.push(start..end);
13465 } else {
13466 trimmed_selections.push(
13467 Point::new(row.0, first_indent.len)
13468 ..Point::new(row.0, buffer.line_len(row)),
13469 );
13470 for row in start.row + 1..=end.row {
13471 let mut line_len = buffer.line_len(MultiBufferRow(row));
13472 if row == end.row {
13473 line_len = end.column;
13474 }
13475 if line_len == 0 {
13476 trimmed_selections
13477 .push(Point::new(row, 0)..Point::new(row, line_len));
13478 continue;
13479 }
13480 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
13481 if row_indent_size.len >= first_indent.len {
13482 trimmed_selections.push(
13483 Point::new(row, first_indent.len)..Point::new(row, line_len),
13484 );
13485 } else {
13486 trimmed_selections.clear();
13487 trimmed_selections.push(start..end);
13488 break;
13489 }
13490 }
13491 }
13492 } else {
13493 trimmed_selections.push(start..end);
13494 }
13495
13496 let is_multiline_trim = trimmed_selections.len() > 1;
13497 for trimmed_range in trimmed_selections {
13498 if is_first {
13499 is_first = false;
13500 } else if is_multiline_trim || !prev_selection_was_entire_line {
13501 text += "\n";
13502 }
13503 prev_selection_was_entire_line = is_entire_line && !is_multiline_trim;
13504 let mut len = 0;
13505 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
13506 text.push_str(chunk);
13507 len += chunk.len();
13508 }
13509 if add_trailing_newline {
13510 text.push('\n');
13511 len += 1;
13512 }
13513 clipboard_selections.push(ClipboardSelection::for_buffer(
13514 len,
13515 is_entire_line,
13516 trimmed_range,
13517 &buffer,
13518 self.project.as_ref(),
13519 cx,
13520 ));
13521 }
13522 }
13523 }
13524
13525 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
13526 text,
13527 clipboard_selections,
13528 ));
13529 }
13530
13531 pub fn do_paste(
13532 &mut self,
13533 text: &String,
13534 clipboard_selections: Option<Vec<ClipboardSelection>>,
13535 handle_entire_lines: bool,
13536 window: &mut Window,
13537 cx: &mut Context<Self>,
13538 ) {
13539 if self.read_only(cx) {
13540 return;
13541 }
13542
13543 let clipboard_text = Cow::Borrowed(text.as_str());
13544
13545 self.transact(window, cx, |this, window, cx| {
13546 let had_active_edit_prediction = this.has_active_edit_prediction();
13547 let display_map = this.display_snapshot(cx);
13548 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
13549 let cursor_offset = this
13550 .selections
13551 .last::<MultiBufferOffset>(&display_map)
13552 .head();
13553
13554 if let Some(mut clipboard_selections) = clipboard_selections {
13555 let all_selections_were_entire_line =
13556 clipboard_selections.iter().all(|s| s.is_entire_line);
13557 let first_selection_indent_column =
13558 clipboard_selections.first().map(|s| s.first_line_indent);
13559 if clipboard_selections.len() != old_selections.len() {
13560 clipboard_selections.drain(..);
13561 }
13562 let mut auto_indent_on_paste = true;
13563
13564 this.buffer.update(cx, |buffer, cx| {
13565 let snapshot = buffer.read(cx);
13566 auto_indent_on_paste = snapshot
13567 .language_settings_at(cursor_offset, cx)
13568 .auto_indent_on_paste;
13569
13570 let mut start_offset = 0;
13571 let mut edits = Vec::new();
13572 let mut original_indent_columns = Vec::new();
13573 for (ix, selection) in old_selections.iter().enumerate() {
13574 let to_insert;
13575 let entire_line;
13576 let original_indent_column;
13577 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
13578 let end_offset = start_offset + clipboard_selection.len;
13579 to_insert = &clipboard_text[start_offset..end_offset];
13580 entire_line = clipboard_selection.is_entire_line;
13581 start_offset = if entire_line {
13582 end_offset
13583 } else {
13584 end_offset + 1
13585 };
13586 original_indent_column = Some(clipboard_selection.first_line_indent);
13587 } else {
13588 to_insert = &*clipboard_text;
13589 entire_line = all_selections_were_entire_line;
13590 original_indent_column = first_selection_indent_column
13591 }
13592
13593 let (range, to_insert) =
13594 if selection.is_empty() && handle_entire_lines && entire_line {
13595 // If the corresponding selection was empty when this slice of the
13596 // clipboard text was written, then the entire line containing the
13597 // selection was copied. If this selection is also currently empty,
13598 // then paste the line before the current line of the buffer.
13599 let column = selection.start.to_point(&snapshot).column as usize;
13600 let line_start = selection.start - column;
13601 (line_start..line_start, Cow::Borrowed(to_insert))
13602 } else {
13603 let language = snapshot.language_at(selection.head());
13604 let range = selection.range();
13605 if let Some(language) = language
13606 && language.name() == "Markdown".into()
13607 {
13608 edit_for_markdown_paste(
13609 &snapshot,
13610 range,
13611 to_insert,
13612 url::Url::parse(to_insert).ok(),
13613 )
13614 } else {
13615 (range, Cow::Borrowed(to_insert))
13616 }
13617 };
13618
13619 edits.push((range, to_insert));
13620 original_indent_columns.push(original_indent_column);
13621 }
13622 drop(snapshot);
13623
13624 buffer.edit(
13625 edits,
13626 if auto_indent_on_paste {
13627 Some(AutoindentMode::Block {
13628 original_indent_columns,
13629 })
13630 } else {
13631 None
13632 },
13633 cx,
13634 );
13635 });
13636
13637 let selections = this
13638 .selections
13639 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13640 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
13641 } else {
13642 let url = url::Url::parse(&clipboard_text).ok();
13643
13644 let auto_indent_mode = if !clipboard_text.is_empty() {
13645 Some(AutoindentMode::Block {
13646 original_indent_columns: Vec::new(),
13647 })
13648 } else {
13649 None
13650 };
13651
13652 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
13653 let snapshot = buffer.snapshot(cx);
13654
13655 let anchors = old_selections
13656 .iter()
13657 .map(|s| {
13658 let anchor = snapshot.anchor_after(s.head());
13659 s.map(|_| anchor)
13660 })
13661 .collect::<Vec<_>>();
13662
13663 let mut edits = Vec::new();
13664
13665 for selection in old_selections.iter() {
13666 let language = snapshot.language_at(selection.head());
13667 let range = selection.range();
13668
13669 let (edit_range, edit_text) = if let Some(language) = language
13670 && language.name() == "Markdown".into()
13671 {
13672 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
13673 } else {
13674 (range, clipboard_text.clone())
13675 };
13676
13677 edits.push((edit_range, edit_text));
13678 }
13679
13680 drop(snapshot);
13681 buffer.edit(edits, auto_indent_mode, cx);
13682
13683 anchors
13684 });
13685
13686 this.change_selections(Default::default(), window, cx, |s| {
13687 s.select_anchors(selection_anchors);
13688 });
13689 }
13690
13691 // 🤔 | .. | show_in_menu |
13692 // | .. | true true
13693 // | had_edit_prediction | false true
13694
13695 let trigger_in_words =
13696 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13697
13698 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13699 });
13700 }
13701
13702 pub fn diff_clipboard_with_selection(
13703 &mut self,
13704 _: &DiffClipboardWithSelection,
13705 window: &mut Window,
13706 cx: &mut Context<Self>,
13707 ) {
13708 let selections = self
13709 .selections
13710 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13711
13712 if selections.is_empty() {
13713 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13714 return;
13715 };
13716
13717 let clipboard_text = match cx.read_from_clipboard() {
13718 Some(item) => match item.entries().first() {
13719 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13720 _ => None,
13721 },
13722 None => None,
13723 };
13724
13725 let Some(clipboard_text) = clipboard_text else {
13726 log::warn!("Clipboard doesn't contain text.");
13727 return;
13728 };
13729
13730 window.dispatch_action(
13731 Box::new(DiffClipboardWithSelectionData {
13732 clipboard_text,
13733 editor: cx.entity(),
13734 }),
13735 cx,
13736 );
13737 }
13738
13739 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13740 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13741 if let Some(item) = cx.read_from_clipboard() {
13742 let entries = item.entries();
13743
13744 match entries.first() {
13745 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13746 // of all the pasted entries.
13747 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13748 .do_paste(
13749 clipboard_string.text(),
13750 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13751 true,
13752 window,
13753 cx,
13754 ),
13755 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13756 }
13757 }
13758 }
13759
13760 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13761 if self.read_only(cx) {
13762 return;
13763 }
13764
13765 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13766
13767 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13768 if let Some((selections, _)) =
13769 self.selection_history.transaction(transaction_id).cloned()
13770 {
13771 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13772 s.select_anchors(selections.to_vec());
13773 });
13774 } else {
13775 log::error!(
13776 "No entry in selection_history found for undo. \
13777 This may correspond to a bug where undo does not update the selection. \
13778 If this is occurring, please add details to \
13779 https://github.com/zed-industries/zed/issues/22692"
13780 );
13781 }
13782 self.request_autoscroll(Autoscroll::fit(), cx);
13783 self.unmark_text(window, cx);
13784 self.refresh_edit_prediction(true, false, window, cx);
13785 cx.emit(EditorEvent::Edited { transaction_id });
13786 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13787 }
13788 }
13789
13790 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13791 if self.read_only(cx) {
13792 return;
13793 }
13794
13795 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13796
13797 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13798 if let Some((_, Some(selections))) =
13799 self.selection_history.transaction(transaction_id).cloned()
13800 {
13801 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13802 s.select_anchors(selections.to_vec());
13803 });
13804 } else {
13805 log::error!(
13806 "No entry in selection_history found for redo. \
13807 This may correspond to a bug where undo does not update the selection. \
13808 If this is occurring, please add details to \
13809 https://github.com/zed-industries/zed/issues/22692"
13810 );
13811 }
13812 self.request_autoscroll(Autoscroll::fit(), cx);
13813 self.unmark_text(window, cx);
13814 self.refresh_edit_prediction(true, false, window, cx);
13815 cx.emit(EditorEvent::Edited { transaction_id });
13816 }
13817 }
13818
13819 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13820 self.buffer
13821 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13822 }
13823
13824 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13825 self.buffer
13826 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13827 }
13828
13829 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13830 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13831 self.change_selections(Default::default(), window, cx, |s| {
13832 s.move_with(|map, selection| {
13833 let cursor = if selection.is_empty() {
13834 movement::left(map, selection.start)
13835 } else {
13836 selection.start
13837 };
13838 selection.collapse_to(cursor, SelectionGoal::None);
13839 });
13840 })
13841 }
13842
13843 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13844 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13845 self.change_selections(Default::default(), window, cx, |s| {
13846 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13847 })
13848 }
13849
13850 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13851 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13852 self.change_selections(Default::default(), window, cx, |s| {
13853 s.move_with(|map, selection| {
13854 let cursor = if selection.is_empty() {
13855 movement::right(map, selection.end)
13856 } else {
13857 selection.end
13858 };
13859 selection.collapse_to(cursor, SelectionGoal::None)
13860 });
13861 })
13862 }
13863
13864 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13865 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13866 self.change_selections(Default::default(), window, cx, |s| {
13867 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13868 });
13869 }
13870
13871 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13872 if self.take_rename(true, window, cx).is_some() {
13873 return;
13874 }
13875
13876 if self.mode.is_single_line() {
13877 cx.propagate();
13878 return;
13879 }
13880
13881 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13882
13883 let text_layout_details = &self.text_layout_details(window, cx);
13884 let selection_count = self.selections.count();
13885 let first_selection = self.selections.first_anchor();
13886
13887 self.change_selections(Default::default(), window, cx, |s| {
13888 s.move_with(|map, selection| {
13889 if !selection.is_empty() {
13890 selection.goal = SelectionGoal::None;
13891 }
13892 let (cursor, goal) = movement::up(
13893 map,
13894 selection.start,
13895 selection.goal,
13896 false,
13897 text_layout_details,
13898 );
13899 selection.collapse_to(cursor, goal);
13900 });
13901 });
13902
13903 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13904 {
13905 cx.propagate();
13906 }
13907 }
13908
13909 pub fn move_up_by_lines(
13910 &mut self,
13911 action: &MoveUpByLines,
13912 window: &mut Window,
13913 cx: &mut Context<Self>,
13914 ) {
13915 if self.take_rename(true, window, cx).is_some() {
13916 return;
13917 }
13918
13919 if self.mode.is_single_line() {
13920 cx.propagate();
13921 return;
13922 }
13923
13924 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13925
13926 let text_layout_details = &self.text_layout_details(window, cx);
13927
13928 self.change_selections(Default::default(), window, cx, |s| {
13929 s.move_with(|map, selection| {
13930 if !selection.is_empty() {
13931 selection.goal = SelectionGoal::None;
13932 }
13933 let (cursor, goal) = movement::up_by_rows(
13934 map,
13935 selection.start,
13936 action.lines,
13937 selection.goal,
13938 false,
13939 text_layout_details,
13940 );
13941 selection.collapse_to(cursor, goal);
13942 });
13943 })
13944 }
13945
13946 pub fn move_down_by_lines(
13947 &mut self,
13948 action: &MoveDownByLines,
13949 window: &mut Window,
13950 cx: &mut Context<Self>,
13951 ) {
13952 if self.take_rename(true, window, cx).is_some() {
13953 return;
13954 }
13955
13956 if self.mode.is_single_line() {
13957 cx.propagate();
13958 return;
13959 }
13960
13961 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13962
13963 let text_layout_details = &self.text_layout_details(window, cx);
13964
13965 self.change_selections(Default::default(), window, cx, |s| {
13966 s.move_with(|map, selection| {
13967 if !selection.is_empty() {
13968 selection.goal = SelectionGoal::None;
13969 }
13970 let (cursor, goal) = movement::down_by_rows(
13971 map,
13972 selection.start,
13973 action.lines,
13974 selection.goal,
13975 false,
13976 text_layout_details,
13977 );
13978 selection.collapse_to(cursor, goal);
13979 });
13980 })
13981 }
13982
13983 pub fn select_down_by_lines(
13984 &mut self,
13985 action: &SelectDownByLines,
13986 window: &mut Window,
13987 cx: &mut Context<Self>,
13988 ) {
13989 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13990 let text_layout_details = &self.text_layout_details(window, cx);
13991 self.change_selections(Default::default(), window, cx, |s| {
13992 s.move_heads_with(|map, head, goal| {
13993 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13994 })
13995 })
13996 }
13997
13998 pub fn select_up_by_lines(
13999 &mut self,
14000 action: &SelectUpByLines,
14001 window: &mut Window,
14002 cx: &mut Context<Self>,
14003 ) {
14004 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14005 let text_layout_details = &self.text_layout_details(window, cx);
14006 self.change_selections(Default::default(), window, cx, |s| {
14007 s.move_heads_with(|map, head, goal| {
14008 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
14009 })
14010 })
14011 }
14012
14013 pub fn select_page_up(
14014 &mut self,
14015 _: &SelectPageUp,
14016 window: &mut Window,
14017 cx: &mut Context<Self>,
14018 ) {
14019 let Some(row_count) = self.visible_row_count() else {
14020 return;
14021 };
14022
14023 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14024
14025 let text_layout_details = &self.text_layout_details(window, cx);
14026
14027 self.change_selections(Default::default(), window, cx, |s| {
14028 s.move_heads_with(|map, head, goal| {
14029 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
14030 })
14031 })
14032 }
14033
14034 pub fn move_page_up(
14035 &mut self,
14036 action: &MovePageUp,
14037 window: &mut Window,
14038 cx: &mut Context<Self>,
14039 ) {
14040 if self.take_rename(true, window, cx).is_some() {
14041 return;
14042 }
14043
14044 if self
14045 .context_menu
14046 .borrow_mut()
14047 .as_mut()
14048 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
14049 .unwrap_or(false)
14050 {
14051 return;
14052 }
14053
14054 if matches!(self.mode, EditorMode::SingleLine) {
14055 cx.propagate();
14056 return;
14057 }
14058
14059 let Some(row_count) = self.visible_row_count() else {
14060 return;
14061 };
14062
14063 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14064
14065 let effects = if action.center_cursor {
14066 SelectionEffects::scroll(Autoscroll::center())
14067 } else {
14068 SelectionEffects::default()
14069 };
14070
14071 let text_layout_details = &self.text_layout_details(window, cx);
14072
14073 self.change_selections(effects, window, cx, |s| {
14074 s.move_with(|map, selection| {
14075 if !selection.is_empty() {
14076 selection.goal = SelectionGoal::None;
14077 }
14078 let (cursor, goal) = movement::up_by_rows(
14079 map,
14080 selection.end,
14081 row_count,
14082 selection.goal,
14083 false,
14084 text_layout_details,
14085 );
14086 selection.collapse_to(cursor, goal);
14087 });
14088 });
14089 }
14090
14091 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
14092 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14093 let text_layout_details = &self.text_layout_details(window, cx);
14094 self.change_selections(Default::default(), window, cx, |s| {
14095 s.move_heads_with(|map, head, goal| {
14096 movement::up(map, head, goal, false, text_layout_details)
14097 })
14098 })
14099 }
14100
14101 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
14102 self.take_rename(true, window, cx);
14103
14104 if self.mode.is_single_line() {
14105 cx.propagate();
14106 return;
14107 }
14108
14109 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14110
14111 let text_layout_details = &self.text_layout_details(window, cx);
14112 let selection_count = self.selections.count();
14113 let first_selection = self.selections.first_anchor();
14114
14115 self.change_selections(Default::default(), window, cx, |s| {
14116 s.move_with(|map, selection| {
14117 if !selection.is_empty() {
14118 selection.goal = SelectionGoal::None;
14119 }
14120 let (cursor, goal) = movement::down(
14121 map,
14122 selection.end,
14123 selection.goal,
14124 false,
14125 text_layout_details,
14126 );
14127 selection.collapse_to(cursor, goal);
14128 });
14129 });
14130
14131 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14132 {
14133 cx.propagate();
14134 }
14135 }
14136
14137 pub fn select_page_down(
14138 &mut self,
14139 _: &SelectPageDown,
14140 window: &mut Window,
14141 cx: &mut Context<Self>,
14142 ) {
14143 let Some(row_count) = self.visible_row_count() else {
14144 return;
14145 };
14146
14147 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14148
14149 let text_layout_details = &self.text_layout_details(window, cx);
14150
14151 self.change_selections(Default::default(), window, cx, |s| {
14152 s.move_heads_with(|map, head, goal| {
14153 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
14154 })
14155 })
14156 }
14157
14158 pub fn move_page_down(
14159 &mut self,
14160 action: &MovePageDown,
14161 window: &mut Window,
14162 cx: &mut Context<Self>,
14163 ) {
14164 if self.take_rename(true, window, cx).is_some() {
14165 return;
14166 }
14167
14168 if self
14169 .context_menu
14170 .borrow_mut()
14171 .as_mut()
14172 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
14173 .unwrap_or(false)
14174 {
14175 return;
14176 }
14177
14178 if matches!(self.mode, EditorMode::SingleLine) {
14179 cx.propagate();
14180 return;
14181 }
14182
14183 let Some(row_count) = self.visible_row_count() else {
14184 return;
14185 };
14186
14187 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14188
14189 let effects = if action.center_cursor {
14190 SelectionEffects::scroll(Autoscroll::center())
14191 } else {
14192 SelectionEffects::default()
14193 };
14194
14195 let text_layout_details = &self.text_layout_details(window, cx);
14196 self.change_selections(effects, window, cx, |s| {
14197 s.move_with(|map, selection| {
14198 if !selection.is_empty() {
14199 selection.goal = SelectionGoal::None;
14200 }
14201 let (cursor, goal) = movement::down_by_rows(
14202 map,
14203 selection.end,
14204 row_count,
14205 selection.goal,
14206 false,
14207 text_layout_details,
14208 );
14209 selection.collapse_to(cursor, goal);
14210 });
14211 });
14212 }
14213
14214 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
14215 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14216 let text_layout_details = &self.text_layout_details(window, cx);
14217 self.change_selections(Default::default(), window, cx, |s| {
14218 s.move_heads_with(|map, head, goal| {
14219 movement::down(map, head, goal, false, text_layout_details)
14220 })
14221 });
14222 }
14223
14224 pub fn context_menu_first(
14225 &mut self,
14226 _: &ContextMenuFirst,
14227 window: &mut Window,
14228 cx: &mut Context<Self>,
14229 ) {
14230 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14231 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
14232 }
14233 }
14234
14235 pub fn context_menu_prev(
14236 &mut self,
14237 _: &ContextMenuPrevious,
14238 window: &mut Window,
14239 cx: &mut Context<Self>,
14240 ) {
14241 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14242 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
14243 }
14244 }
14245
14246 pub fn context_menu_next(
14247 &mut self,
14248 _: &ContextMenuNext,
14249 window: &mut Window,
14250 cx: &mut Context<Self>,
14251 ) {
14252 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14253 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
14254 }
14255 }
14256
14257 pub fn context_menu_last(
14258 &mut self,
14259 _: &ContextMenuLast,
14260 window: &mut Window,
14261 cx: &mut Context<Self>,
14262 ) {
14263 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14264 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
14265 }
14266 }
14267
14268 pub fn signature_help_prev(
14269 &mut self,
14270 _: &SignatureHelpPrevious,
14271 _: &mut Window,
14272 cx: &mut Context<Self>,
14273 ) {
14274 if let Some(popover) = self.signature_help_state.popover_mut() {
14275 if popover.current_signature == 0 {
14276 popover.current_signature = popover.signatures.len() - 1;
14277 } else {
14278 popover.current_signature -= 1;
14279 }
14280 cx.notify();
14281 }
14282 }
14283
14284 pub fn signature_help_next(
14285 &mut self,
14286 _: &SignatureHelpNext,
14287 _: &mut Window,
14288 cx: &mut Context<Self>,
14289 ) {
14290 if let Some(popover) = self.signature_help_state.popover_mut() {
14291 if popover.current_signature + 1 == popover.signatures.len() {
14292 popover.current_signature = 0;
14293 } else {
14294 popover.current_signature += 1;
14295 }
14296 cx.notify();
14297 }
14298 }
14299
14300 pub fn move_to_previous_word_start(
14301 &mut self,
14302 _: &MoveToPreviousWordStart,
14303 window: &mut Window,
14304 cx: &mut Context<Self>,
14305 ) {
14306 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14307 self.change_selections(Default::default(), window, cx, |s| {
14308 s.move_cursors_with(|map, head, _| {
14309 (
14310 movement::previous_word_start(map, head),
14311 SelectionGoal::None,
14312 )
14313 });
14314 })
14315 }
14316
14317 pub fn move_to_previous_subword_start(
14318 &mut self,
14319 _: &MoveToPreviousSubwordStart,
14320 window: &mut Window,
14321 cx: &mut Context<Self>,
14322 ) {
14323 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14324 self.change_selections(Default::default(), window, cx, |s| {
14325 s.move_cursors_with(|map, head, _| {
14326 (
14327 movement::previous_subword_start(map, head),
14328 SelectionGoal::None,
14329 )
14330 });
14331 })
14332 }
14333
14334 pub fn select_to_previous_word_start(
14335 &mut self,
14336 _: &SelectToPreviousWordStart,
14337 window: &mut Window,
14338 cx: &mut Context<Self>,
14339 ) {
14340 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14341 self.change_selections(Default::default(), window, cx, |s| {
14342 s.move_heads_with(|map, head, _| {
14343 (
14344 movement::previous_word_start(map, head),
14345 SelectionGoal::None,
14346 )
14347 });
14348 })
14349 }
14350
14351 pub fn select_to_previous_subword_start(
14352 &mut self,
14353 _: &SelectToPreviousSubwordStart,
14354 window: &mut Window,
14355 cx: &mut Context<Self>,
14356 ) {
14357 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14358 self.change_selections(Default::default(), window, cx, |s| {
14359 s.move_heads_with(|map, head, _| {
14360 (
14361 movement::previous_subword_start(map, head),
14362 SelectionGoal::None,
14363 )
14364 });
14365 })
14366 }
14367
14368 pub fn delete_to_previous_word_start(
14369 &mut self,
14370 action: &DeleteToPreviousWordStart,
14371 window: &mut Window,
14372 cx: &mut Context<Self>,
14373 ) {
14374 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14375 self.transact(window, cx, |this, window, cx| {
14376 this.select_autoclose_pair(window, cx);
14377 this.change_selections(Default::default(), window, cx, |s| {
14378 s.move_with(|map, selection| {
14379 if selection.is_empty() {
14380 let mut cursor = if action.ignore_newlines {
14381 movement::previous_word_start(map, selection.head())
14382 } else {
14383 movement::previous_word_start_or_newline(map, selection.head())
14384 };
14385 cursor = movement::adjust_greedy_deletion(
14386 map,
14387 selection.head(),
14388 cursor,
14389 action.ignore_brackets,
14390 );
14391 selection.set_head(cursor, SelectionGoal::None);
14392 }
14393 });
14394 });
14395 this.insert("", window, cx);
14396 });
14397 }
14398
14399 pub fn delete_to_previous_subword_start(
14400 &mut self,
14401 action: &DeleteToPreviousSubwordStart,
14402 window: &mut Window,
14403 cx: &mut Context<Self>,
14404 ) {
14405 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14406 self.transact(window, cx, |this, window, cx| {
14407 this.select_autoclose_pair(window, cx);
14408 this.change_selections(Default::default(), window, cx, |s| {
14409 s.move_with(|map, selection| {
14410 if selection.is_empty() {
14411 let mut cursor = if action.ignore_newlines {
14412 movement::previous_subword_start(map, selection.head())
14413 } else {
14414 movement::previous_subword_start_or_newline(map, selection.head())
14415 };
14416 cursor = movement::adjust_greedy_deletion(
14417 map,
14418 selection.head(),
14419 cursor,
14420 action.ignore_brackets,
14421 );
14422 selection.set_head(cursor, SelectionGoal::None);
14423 }
14424 });
14425 });
14426 this.insert("", window, cx);
14427 });
14428 }
14429
14430 pub fn move_to_next_word_end(
14431 &mut self,
14432 _: &MoveToNextWordEnd,
14433 window: &mut Window,
14434 cx: &mut Context<Self>,
14435 ) {
14436 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14437 self.change_selections(Default::default(), window, cx, |s| {
14438 s.move_cursors_with(|map, head, _| {
14439 (movement::next_word_end(map, head), SelectionGoal::None)
14440 });
14441 })
14442 }
14443
14444 pub fn move_to_next_subword_end(
14445 &mut self,
14446 _: &MoveToNextSubwordEnd,
14447 window: &mut Window,
14448 cx: &mut Context<Self>,
14449 ) {
14450 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14451 self.change_selections(Default::default(), window, cx, |s| {
14452 s.move_cursors_with(|map, head, _| {
14453 (movement::next_subword_end(map, head), SelectionGoal::None)
14454 });
14455 })
14456 }
14457
14458 pub fn select_to_next_word_end(
14459 &mut self,
14460 _: &SelectToNextWordEnd,
14461 window: &mut Window,
14462 cx: &mut Context<Self>,
14463 ) {
14464 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14465 self.change_selections(Default::default(), window, cx, |s| {
14466 s.move_heads_with(|map, head, _| {
14467 (movement::next_word_end(map, head), SelectionGoal::None)
14468 });
14469 })
14470 }
14471
14472 pub fn select_to_next_subword_end(
14473 &mut self,
14474 _: &SelectToNextSubwordEnd,
14475 window: &mut Window,
14476 cx: &mut Context<Self>,
14477 ) {
14478 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14479 self.change_selections(Default::default(), window, cx, |s| {
14480 s.move_heads_with(|map, head, _| {
14481 (movement::next_subword_end(map, head), SelectionGoal::None)
14482 });
14483 })
14484 }
14485
14486 pub fn delete_to_next_word_end(
14487 &mut self,
14488 action: &DeleteToNextWordEnd,
14489 window: &mut Window,
14490 cx: &mut Context<Self>,
14491 ) {
14492 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14493 self.transact(window, cx, |this, window, cx| {
14494 this.change_selections(Default::default(), window, cx, |s| {
14495 s.move_with(|map, selection| {
14496 if selection.is_empty() {
14497 let mut cursor = if action.ignore_newlines {
14498 movement::next_word_end(map, selection.head())
14499 } else {
14500 movement::next_word_end_or_newline(map, selection.head())
14501 };
14502 cursor = movement::adjust_greedy_deletion(
14503 map,
14504 selection.head(),
14505 cursor,
14506 action.ignore_brackets,
14507 );
14508 selection.set_head(cursor, SelectionGoal::None);
14509 }
14510 });
14511 });
14512 this.insert("", window, cx);
14513 });
14514 }
14515
14516 pub fn delete_to_next_subword_end(
14517 &mut self,
14518 action: &DeleteToNextSubwordEnd,
14519 window: &mut Window,
14520 cx: &mut Context<Self>,
14521 ) {
14522 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14523 self.transact(window, cx, |this, window, cx| {
14524 this.change_selections(Default::default(), window, cx, |s| {
14525 s.move_with(|map, selection| {
14526 if selection.is_empty() {
14527 let mut cursor = if action.ignore_newlines {
14528 movement::next_subword_end(map, selection.head())
14529 } else {
14530 movement::next_subword_end_or_newline(map, selection.head())
14531 };
14532 cursor = movement::adjust_greedy_deletion(
14533 map,
14534 selection.head(),
14535 cursor,
14536 action.ignore_brackets,
14537 );
14538 selection.set_head(cursor, SelectionGoal::None);
14539 }
14540 });
14541 });
14542 this.insert("", window, cx);
14543 });
14544 }
14545
14546 pub fn move_to_beginning_of_line(
14547 &mut self,
14548 action: &MoveToBeginningOfLine,
14549 window: &mut Window,
14550 cx: &mut Context<Self>,
14551 ) {
14552 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14553 self.change_selections(Default::default(), window, cx, |s| {
14554 s.move_cursors_with(|map, head, _| {
14555 (
14556 movement::indented_line_beginning(
14557 map,
14558 head,
14559 action.stop_at_soft_wraps,
14560 action.stop_at_indent,
14561 ),
14562 SelectionGoal::None,
14563 )
14564 });
14565 })
14566 }
14567
14568 pub fn select_to_beginning_of_line(
14569 &mut self,
14570 action: &SelectToBeginningOfLine,
14571 window: &mut Window,
14572 cx: &mut Context<Self>,
14573 ) {
14574 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14575 self.change_selections(Default::default(), window, cx, |s| {
14576 s.move_heads_with(|map, head, _| {
14577 (
14578 movement::indented_line_beginning(
14579 map,
14580 head,
14581 action.stop_at_soft_wraps,
14582 action.stop_at_indent,
14583 ),
14584 SelectionGoal::None,
14585 )
14586 });
14587 });
14588 }
14589
14590 pub fn delete_to_beginning_of_line(
14591 &mut self,
14592 action: &DeleteToBeginningOfLine,
14593 window: &mut Window,
14594 cx: &mut Context<Self>,
14595 ) {
14596 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14597 self.transact(window, cx, |this, window, cx| {
14598 this.change_selections(Default::default(), window, cx, |s| {
14599 s.move_with(|_, selection| {
14600 selection.reversed = true;
14601 });
14602 });
14603
14604 this.select_to_beginning_of_line(
14605 &SelectToBeginningOfLine {
14606 stop_at_soft_wraps: false,
14607 stop_at_indent: action.stop_at_indent,
14608 },
14609 window,
14610 cx,
14611 );
14612 this.backspace(&Backspace, window, cx);
14613 });
14614 }
14615
14616 pub fn move_to_end_of_line(
14617 &mut self,
14618 action: &MoveToEndOfLine,
14619 window: &mut Window,
14620 cx: &mut Context<Self>,
14621 ) {
14622 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14623 self.change_selections(Default::default(), window, cx, |s| {
14624 s.move_cursors_with(|map, head, _| {
14625 (
14626 movement::line_end(map, head, action.stop_at_soft_wraps),
14627 SelectionGoal::None,
14628 )
14629 });
14630 })
14631 }
14632
14633 pub fn select_to_end_of_line(
14634 &mut self,
14635 action: &SelectToEndOfLine,
14636 window: &mut Window,
14637 cx: &mut Context<Self>,
14638 ) {
14639 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14640 self.change_selections(Default::default(), window, cx, |s| {
14641 s.move_heads_with(|map, head, _| {
14642 (
14643 movement::line_end(map, head, action.stop_at_soft_wraps),
14644 SelectionGoal::None,
14645 )
14646 });
14647 })
14648 }
14649
14650 pub fn delete_to_end_of_line(
14651 &mut self,
14652 _: &DeleteToEndOfLine,
14653 window: &mut Window,
14654 cx: &mut Context<Self>,
14655 ) {
14656 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14657 self.transact(window, cx, |this, window, cx| {
14658 this.select_to_end_of_line(
14659 &SelectToEndOfLine {
14660 stop_at_soft_wraps: false,
14661 },
14662 window,
14663 cx,
14664 );
14665 this.delete(&Delete, window, cx);
14666 });
14667 }
14668
14669 pub fn cut_to_end_of_line(
14670 &mut self,
14671 action: &CutToEndOfLine,
14672 window: &mut Window,
14673 cx: &mut Context<Self>,
14674 ) {
14675 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14676 self.transact(window, cx, |this, window, cx| {
14677 this.select_to_end_of_line(
14678 &SelectToEndOfLine {
14679 stop_at_soft_wraps: false,
14680 },
14681 window,
14682 cx,
14683 );
14684 if !action.stop_at_newlines {
14685 this.change_selections(Default::default(), window, cx, |s| {
14686 s.move_with(|_, sel| {
14687 if sel.is_empty() {
14688 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14689 }
14690 });
14691 });
14692 }
14693 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14694 let item = this.cut_common(false, window, cx);
14695 cx.write_to_clipboard(item);
14696 });
14697 }
14698
14699 pub fn move_to_start_of_paragraph(
14700 &mut self,
14701 _: &MoveToStartOfParagraph,
14702 window: &mut Window,
14703 cx: &mut Context<Self>,
14704 ) {
14705 if matches!(self.mode, EditorMode::SingleLine) {
14706 cx.propagate();
14707 return;
14708 }
14709 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14710 self.change_selections(Default::default(), window, cx, |s| {
14711 s.move_with(|map, selection| {
14712 selection.collapse_to(
14713 movement::start_of_paragraph(map, selection.head(), 1),
14714 SelectionGoal::None,
14715 )
14716 });
14717 })
14718 }
14719
14720 pub fn move_to_end_of_paragraph(
14721 &mut self,
14722 _: &MoveToEndOfParagraph,
14723 window: &mut Window,
14724 cx: &mut Context<Self>,
14725 ) {
14726 if matches!(self.mode, EditorMode::SingleLine) {
14727 cx.propagate();
14728 return;
14729 }
14730 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14731 self.change_selections(Default::default(), window, cx, |s| {
14732 s.move_with(|map, selection| {
14733 selection.collapse_to(
14734 movement::end_of_paragraph(map, selection.head(), 1),
14735 SelectionGoal::None,
14736 )
14737 });
14738 })
14739 }
14740
14741 pub fn select_to_start_of_paragraph(
14742 &mut self,
14743 _: &SelectToStartOfParagraph,
14744 window: &mut Window,
14745 cx: &mut Context<Self>,
14746 ) {
14747 if matches!(self.mode, EditorMode::SingleLine) {
14748 cx.propagate();
14749 return;
14750 }
14751 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14752 self.change_selections(Default::default(), window, cx, |s| {
14753 s.move_heads_with(|map, head, _| {
14754 (
14755 movement::start_of_paragraph(map, head, 1),
14756 SelectionGoal::None,
14757 )
14758 });
14759 })
14760 }
14761
14762 pub fn select_to_end_of_paragraph(
14763 &mut self,
14764 _: &SelectToEndOfParagraph,
14765 window: &mut Window,
14766 cx: &mut Context<Self>,
14767 ) {
14768 if matches!(self.mode, EditorMode::SingleLine) {
14769 cx.propagate();
14770 return;
14771 }
14772 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14773 self.change_selections(Default::default(), window, cx, |s| {
14774 s.move_heads_with(|map, head, _| {
14775 (
14776 movement::end_of_paragraph(map, head, 1),
14777 SelectionGoal::None,
14778 )
14779 });
14780 })
14781 }
14782
14783 pub fn move_to_start_of_excerpt(
14784 &mut self,
14785 _: &MoveToStartOfExcerpt,
14786 window: &mut Window,
14787 cx: &mut Context<Self>,
14788 ) {
14789 if matches!(self.mode, EditorMode::SingleLine) {
14790 cx.propagate();
14791 return;
14792 }
14793 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14794 self.change_selections(Default::default(), window, cx, |s| {
14795 s.move_with(|map, selection| {
14796 selection.collapse_to(
14797 movement::start_of_excerpt(
14798 map,
14799 selection.head(),
14800 workspace::searchable::Direction::Prev,
14801 ),
14802 SelectionGoal::None,
14803 )
14804 });
14805 })
14806 }
14807
14808 pub fn move_to_start_of_next_excerpt(
14809 &mut self,
14810 _: &MoveToStartOfNextExcerpt,
14811 window: &mut Window,
14812 cx: &mut Context<Self>,
14813 ) {
14814 if matches!(self.mode, EditorMode::SingleLine) {
14815 cx.propagate();
14816 return;
14817 }
14818
14819 self.change_selections(Default::default(), window, cx, |s| {
14820 s.move_with(|map, selection| {
14821 selection.collapse_to(
14822 movement::start_of_excerpt(
14823 map,
14824 selection.head(),
14825 workspace::searchable::Direction::Next,
14826 ),
14827 SelectionGoal::None,
14828 )
14829 });
14830 })
14831 }
14832
14833 pub fn move_to_end_of_excerpt(
14834 &mut self,
14835 _: &MoveToEndOfExcerpt,
14836 window: &mut Window,
14837 cx: &mut Context<Self>,
14838 ) {
14839 if matches!(self.mode, EditorMode::SingleLine) {
14840 cx.propagate();
14841 return;
14842 }
14843 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14844 self.change_selections(Default::default(), window, cx, |s| {
14845 s.move_with(|map, selection| {
14846 selection.collapse_to(
14847 movement::end_of_excerpt(
14848 map,
14849 selection.head(),
14850 workspace::searchable::Direction::Next,
14851 ),
14852 SelectionGoal::None,
14853 )
14854 });
14855 })
14856 }
14857
14858 pub fn move_to_end_of_previous_excerpt(
14859 &mut self,
14860 _: &MoveToEndOfPreviousExcerpt,
14861 window: &mut Window,
14862 cx: &mut Context<Self>,
14863 ) {
14864 if matches!(self.mode, EditorMode::SingleLine) {
14865 cx.propagate();
14866 return;
14867 }
14868 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14869 self.change_selections(Default::default(), window, cx, |s| {
14870 s.move_with(|map, selection| {
14871 selection.collapse_to(
14872 movement::end_of_excerpt(
14873 map,
14874 selection.head(),
14875 workspace::searchable::Direction::Prev,
14876 ),
14877 SelectionGoal::None,
14878 )
14879 });
14880 })
14881 }
14882
14883 pub fn select_to_start_of_excerpt(
14884 &mut self,
14885 _: &SelectToStartOfExcerpt,
14886 window: &mut Window,
14887 cx: &mut Context<Self>,
14888 ) {
14889 if matches!(self.mode, EditorMode::SingleLine) {
14890 cx.propagate();
14891 return;
14892 }
14893 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14894 self.change_selections(Default::default(), window, cx, |s| {
14895 s.move_heads_with(|map, head, _| {
14896 (
14897 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14898 SelectionGoal::None,
14899 )
14900 });
14901 })
14902 }
14903
14904 pub fn select_to_start_of_next_excerpt(
14905 &mut self,
14906 _: &SelectToStartOfNextExcerpt,
14907 window: &mut Window,
14908 cx: &mut Context<Self>,
14909 ) {
14910 if matches!(self.mode, EditorMode::SingleLine) {
14911 cx.propagate();
14912 return;
14913 }
14914 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14915 self.change_selections(Default::default(), window, cx, |s| {
14916 s.move_heads_with(|map, head, _| {
14917 (
14918 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14919 SelectionGoal::None,
14920 )
14921 });
14922 })
14923 }
14924
14925 pub fn select_to_end_of_excerpt(
14926 &mut self,
14927 _: &SelectToEndOfExcerpt,
14928 window: &mut Window,
14929 cx: &mut Context<Self>,
14930 ) {
14931 if matches!(self.mode, EditorMode::SingleLine) {
14932 cx.propagate();
14933 return;
14934 }
14935 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14936 self.change_selections(Default::default(), window, cx, |s| {
14937 s.move_heads_with(|map, head, _| {
14938 (
14939 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14940 SelectionGoal::None,
14941 )
14942 });
14943 })
14944 }
14945
14946 pub fn select_to_end_of_previous_excerpt(
14947 &mut self,
14948 _: &SelectToEndOfPreviousExcerpt,
14949 window: &mut Window,
14950 cx: &mut Context<Self>,
14951 ) {
14952 if matches!(self.mode, EditorMode::SingleLine) {
14953 cx.propagate();
14954 return;
14955 }
14956 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14957 self.change_selections(Default::default(), window, cx, |s| {
14958 s.move_heads_with(|map, head, _| {
14959 (
14960 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14961 SelectionGoal::None,
14962 )
14963 });
14964 })
14965 }
14966
14967 pub fn move_to_beginning(
14968 &mut self,
14969 _: &MoveToBeginning,
14970 window: &mut Window,
14971 cx: &mut Context<Self>,
14972 ) {
14973 if matches!(self.mode, EditorMode::SingleLine) {
14974 cx.propagate();
14975 return;
14976 }
14977 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14978 self.change_selections(Default::default(), window, cx, |s| {
14979 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
14980 });
14981 }
14982
14983 pub fn select_to_beginning(
14984 &mut self,
14985 _: &SelectToBeginning,
14986 window: &mut Window,
14987 cx: &mut Context<Self>,
14988 ) {
14989 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14990 selection.set_head(Point::zero(), SelectionGoal::None);
14991 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14992 self.change_selections(Default::default(), window, cx, |s| {
14993 s.select(vec![selection]);
14994 });
14995 }
14996
14997 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14998 if matches!(self.mode, EditorMode::SingleLine) {
14999 cx.propagate();
15000 return;
15001 }
15002 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15003 let cursor = self.buffer.read(cx).read(cx).len();
15004 self.change_selections(Default::default(), window, cx, |s| {
15005 s.select_ranges(vec![cursor..cursor])
15006 });
15007 }
15008
15009 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
15010 self.nav_history = nav_history;
15011 }
15012
15013 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
15014 self.nav_history.as_ref()
15015 }
15016
15017 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
15018 self.push_to_nav_history(
15019 self.selections.newest_anchor().head(),
15020 None,
15021 false,
15022 true,
15023 cx,
15024 );
15025 }
15026
15027 fn navigation_data(&self, cursor_anchor: Anchor, cx: &mut Context<Self>) -> NavigationData {
15028 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15029 let buffer = self.buffer.read(cx).read(cx);
15030 let cursor_position = cursor_anchor.to_point(&buffer);
15031 let scroll_anchor = self.scroll_manager.native_anchor(&display_snapshot, cx);
15032 let scroll_top_row = scroll_anchor.top_row(&buffer);
15033 drop(buffer);
15034
15035 NavigationData {
15036 cursor_anchor,
15037 cursor_position,
15038 scroll_anchor,
15039 scroll_top_row,
15040 }
15041 }
15042
15043 fn navigation_entry(
15044 &self,
15045 cursor_anchor: Anchor,
15046 cx: &mut Context<Self>,
15047 ) -> Option<NavigationEntry> {
15048 let Some(history) = self.nav_history.clone() else {
15049 return None;
15050 };
15051 let data = self.navigation_data(cursor_anchor, cx);
15052 Some(history.navigation_entry(Some(Arc::new(data) as Arc<dyn Any + Send + Sync>)))
15053 }
15054
15055 fn push_to_nav_history(
15056 &mut self,
15057 cursor_anchor: Anchor,
15058 new_position: Option<Point>,
15059 is_deactivate: bool,
15060 always: bool,
15061 cx: &mut Context<Self>,
15062 ) {
15063 let data = self.navigation_data(cursor_anchor, cx);
15064 if let Some(nav_history) = self.nav_history.as_mut() {
15065 if let Some(new_position) = new_position {
15066 let row_delta = (new_position.row as i64 - data.cursor_position.row as i64).abs();
15067 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
15068 return;
15069 }
15070 }
15071
15072 nav_history.push(Some(data), cx);
15073 cx.emit(EditorEvent::PushedToNavHistory {
15074 anchor: cursor_anchor,
15075 is_deactivate,
15076 })
15077 }
15078 }
15079
15080 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
15081 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15082 let buffer = self.buffer.read(cx).snapshot(cx);
15083 let mut selection = self
15084 .selections
15085 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
15086 selection.set_head(buffer.len(), SelectionGoal::None);
15087 self.change_selections(Default::default(), window, cx, |s| {
15088 s.select(vec![selection]);
15089 });
15090 }
15091
15092 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
15093 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15094 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15095 s.select_ranges(vec![Anchor::min()..Anchor::max()]);
15096 });
15097 }
15098
15099 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
15100 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15101 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15102 let mut selections = self.selections.all::<Point>(&display_map);
15103 let max_point = display_map.buffer_snapshot().max_point();
15104 for selection in &mut selections {
15105 let rows = selection.spanned_rows(true, &display_map);
15106 selection.start = Point::new(rows.start.0, 0);
15107 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
15108 selection.reversed = false;
15109 }
15110 self.change_selections(Default::default(), window, cx, |s| {
15111 s.select(selections);
15112 });
15113 }
15114
15115 pub fn split_selection_into_lines(
15116 &mut self,
15117 action: &SplitSelectionIntoLines,
15118 window: &mut Window,
15119 cx: &mut Context<Self>,
15120 ) {
15121 let selections = self
15122 .selections
15123 .all::<Point>(&self.display_snapshot(cx))
15124 .into_iter()
15125 .map(|selection| selection.start..selection.end)
15126 .collect::<Vec<_>>();
15127 self.unfold_ranges(&selections, true, true, cx);
15128
15129 let mut new_selection_ranges = Vec::new();
15130 {
15131 let buffer = self.buffer.read(cx).read(cx);
15132 for selection in selections {
15133 for row in selection.start.row..selection.end.row {
15134 let line_start = Point::new(row, 0);
15135 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
15136
15137 if action.keep_selections {
15138 // Keep the selection range for each line
15139 let selection_start = if row == selection.start.row {
15140 selection.start
15141 } else {
15142 line_start
15143 };
15144 new_selection_ranges.push(selection_start..line_end);
15145 } else {
15146 // Collapse to cursor at end of line
15147 new_selection_ranges.push(line_end..line_end);
15148 }
15149 }
15150
15151 let is_multiline_selection = selection.start.row != selection.end.row;
15152 // Don't insert last one if it's a multi-line selection ending at the start of a line,
15153 // so this action feels more ergonomic when paired with other selection operations
15154 let should_skip_last = is_multiline_selection && selection.end.column == 0;
15155 if !should_skip_last {
15156 if action.keep_selections {
15157 if is_multiline_selection {
15158 let line_start = Point::new(selection.end.row, 0);
15159 new_selection_ranges.push(line_start..selection.end);
15160 } else {
15161 new_selection_ranges.push(selection.start..selection.end);
15162 }
15163 } else {
15164 new_selection_ranges.push(selection.end..selection.end);
15165 }
15166 }
15167 }
15168 }
15169 self.change_selections(Default::default(), window, cx, |s| {
15170 s.select_ranges(new_selection_ranges);
15171 });
15172 }
15173
15174 pub fn add_selection_above(
15175 &mut self,
15176 action: &AddSelectionAbove,
15177 window: &mut Window,
15178 cx: &mut Context<Self>,
15179 ) {
15180 self.add_selection(true, action.skip_soft_wrap, window, cx);
15181 }
15182
15183 pub fn add_selection_below(
15184 &mut self,
15185 action: &AddSelectionBelow,
15186 window: &mut Window,
15187 cx: &mut Context<Self>,
15188 ) {
15189 self.add_selection(false, action.skip_soft_wrap, window, cx);
15190 }
15191
15192 fn add_selection(
15193 &mut self,
15194 above: bool,
15195 skip_soft_wrap: bool,
15196 window: &mut Window,
15197 cx: &mut Context<Self>,
15198 ) {
15199 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15200
15201 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15202 let all_selections = self.selections.all::<Point>(&display_map);
15203 let text_layout_details = self.text_layout_details(window, cx);
15204
15205 let (mut columnar_selections, new_selections_to_columnarize) = {
15206 if let Some(state) = self.add_selections_state.as_ref() {
15207 let columnar_selection_ids: HashSet<_> = state
15208 .groups
15209 .iter()
15210 .flat_map(|group| group.stack.iter())
15211 .copied()
15212 .collect();
15213
15214 all_selections
15215 .into_iter()
15216 .partition(|s| columnar_selection_ids.contains(&s.id))
15217 } else {
15218 (Vec::new(), all_selections)
15219 }
15220 };
15221
15222 let mut state = self
15223 .add_selections_state
15224 .take()
15225 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
15226
15227 for selection in new_selections_to_columnarize {
15228 let range = selection.display_range(&display_map).sorted();
15229 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
15230 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
15231 let positions = start_x.min(end_x)..start_x.max(end_x);
15232 let mut stack = Vec::new();
15233 for row in range.start.row().0..=range.end.row().0 {
15234 if let Some(selection) = self.selections.build_columnar_selection(
15235 &display_map,
15236 DisplayRow(row),
15237 &positions,
15238 selection.reversed,
15239 &text_layout_details,
15240 ) {
15241 stack.push(selection.id);
15242 columnar_selections.push(selection);
15243 }
15244 }
15245 if !stack.is_empty() {
15246 if above {
15247 stack.reverse();
15248 }
15249 state.groups.push(AddSelectionsGroup { above, stack });
15250 }
15251 }
15252
15253 let mut final_selections = Vec::new();
15254 let end_row = if above {
15255 DisplayRow(0)
15256 } else {
15257 display_map.max_point().row()
15258 };
15259
15260 // When `skip_soft_wrap` is true, we use buffer columns instead of pixel
15261 // positions to place new selections, so we need to keep track of the
15262 // column range of the oldest selection in each group, because
15263 // intermediate selections may have been clamped to shorter lines.
15264 // selections may have been clamped to shorter lines.
15265 let mut goal_columns_by_selection_id = if skip_soft_wrap {
15266 let mut map = HashMap::default();
15267 for group in state.groups.iter() {
15268 if let Some(oldest_id) = group.stack.first() {
15269 if let Some(oldest_selection) =
15270 columnar_selections.iter().find(|s| s.id == *oldest_id)
15271 {
15272 let start_col = oldest_selection.start.column;
15273 let end_col = oldest_selection.end.column;
15274 let goal_columns = start_col.min(end_col)..start_col.max(end_col);
15275 for id in &group.stack {
15276 map.insert(*id, goal_columns.clone());
15277 }
15278 }
15279 }
15280 }
15281 map
15282 } else {
15283 HashMap::default()
15284 };
15285
15286 let mut last_added_item_per_group = HashMap::default();
15287 for group in state.groups.iter_mut() {
15288 if let Some(last_id) = group.stack.last() {
15289 last_added_item_per_group.insert(*last_id, group);
15290 }
15291 }
15292
15293 for selection in columnar_selections {
15294 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
15295 if above == group.above {
15296 let range = selection.display_range(&display_map).sorted();
15297 debug_assert_eq!(range.start.row(), range.end.row());
15298 let row = range.start.row();
15299 let positions =
15300 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
15301 Pixels::from(start)..Pixels::from(end)
15302 } else {
15303 let start_x =
15304 display_map.x_for_display_point(range.start, &text_layout_details);
15305 let end_x =
15306 display_map.x_for_display_point(range.end, &text_layout_details);
15307 start_x.min(end_x)..start_x.max(end_x)
15308 };
15309
15310 let maybe_new_selection = if skip_soft_wrap {
15311 let goal_columns = goal_columns_by_selection_id
15312 .remove(&selection.id)
15313 .unwrap_or_else(|| {
15314 let start_col = selection.start.column;
15315 let end_col = selection.end.column;
15316 start_col.min(end_col)..start_col.max(end_col)
15317 });
15318 self.selections.find_next_columnar_selection_by_buffer_row(
15319 &display_map,
15320 row,
15321 end_row,
15322 above,
15323 &goal_columns,
15324 selection.reversed,
15325 &text_layout_details,
15326 )
15327 } else {
15328 self.selections.find_next_columnar_selection_by_display_row(
15329 &display_map,
15330 row,
15331 end_row,
15332 above,
15333 &positions,
15334 selection.reversed,
15335 &text_layout_details,
15336 )
15337 };
15338
15339 if let Some(new_selection) = maybe_new_selection {
15340 group.stack.push(new_selection.id);
15341 if above {
15342 final_selections.push(new_selection);
15343 final_selections.push(selection);
15344 } else {
15345 final_selections.push(selection);
15346 final_selections.push(new_selection);
15347 }
15348 } else {
15349 final_selections.push(selection);
15350 }
15351 } else {
15352 group.stack.pop();
15353 }
15354 } else {
15355 final_selections.push(selection);
15356 }
15357 }
15358
15359 self.change_selections(Default::default(), window, cx, |s| {
15360 s.select(final_selections);
15361 });
15362
15363 let final_selection_ids: HashSet<_> = self
15364 .selections
15365 .all::<Point>(&display_map)
15366 .iter()
15367 .map(|s| s.id)
15368 .collect();
15369 state.groups.retain_mut(|group| {
15370 // selections might get merged above so we remove invalid items from stacks
15371 group.stack.retain(|id| final_selection_ids.contains(id));
15372
15373 // single selection in stack can be treated as initial state
15374 group.stack.len() > 1
15375 });
15376
15377 if !state.groups.is_empty() {
15378 self.add_selections_state = Some(state);
15379 }
15380 }
15381
15382 pub fn insert_snippet_at_selections(
15383 &mut self,
15384 action: &InsertSnippet,
15385 window: &mut Window,
15386 cx: &mut Context<Self>,
15387 ) {
15388 self.try_insert_snippet_at_selections(action, window, cx)
15389 .log_err();
15390 }
15391
15392 fn try_insert_snippet_at_selections(
15393 &mut self,
15394 action: &InsertSnippet,
15395 window: &mut Window,
15396 cx: &mut Context<Self>,
15397 ) -> Result<()> {
15398 let insertion_ranges = self
15399 .selections
15400 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15401 .into_iter()
15402 .map(|selection| selection.range())
15403 .collect_vec();
15404
15405 let snippet = if let Some(snippet_body) = &action.snippet {
15406 if action.language.is_none() && action.name.is_none() {
15407 Snippet::parse(snippet_body)?
15408 } else {
15409 bail!("`snippet` is mutually exclusive with `language` and `name`")
15410 }
15411 } else if let Some(name) = &action.name {
15412 let project = self.project().context("no project")?;
15413 let snippet_store = project.read(cx).snippets().read(cx);
15414 let snippet = snippet_store
15415 .snippets_for(action.language.clone(), cx)
15416 .into_iter()
15417 .find(|snippet| snippet.name == *name)
15418 .context("snippet not found")?;
15419 Snippet::parse(&snippet.body)?
15420 } else {
15421 // todo(andrew): open modal to select snippet
15422 bail!("`name` or `snippet` is required")
15423 };
15424
15425 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15426 }
15427
15428 fn select_match_ranges(
15429 &mut self,
15430 range: Range<MultiBufferOffset>,
15431 reversed: bool,
15432 replace_newest: bool,
15433 auto_scroll: Option<Autoscroll>,
15434 window: &mut Window,
15435 cx: &mut Context<Editor>,
15436 ) {
15437 self.unfold_ranges(
15438 std::slice::from_ref(&range),
15439 false,
15440 auto_scroll.is_some(),
15441 cx,
15442 );
15443 let effects = if let Some(scroll) = auto_scroll {
15444 SelectionEffects::scroll(scroll)
15445 } else {
15446 SelectionEffects::no_scroll()
15447 };
15448 self.change_selections(effects, window, cx, |s| {
15449 if replace_newest {
15450 s.delete(s.newest_anchor().id);
15451 }
15452 if reversed {
15453 s.insert_range(range.end..range.start);
15454 } else {
15455 s.insert_range(range);
15456 }
15457 });
15458 }
15459
15460 pub fn select_next_match_internal(
15461 &mut self,
15462 display_map: &DisplaySnapshot,
15463 replace_newest: bool,
15464 autoscroll: Option<Autoscroll>,
15465 window: &mut Window,
15466 cx: &mut Context<Self>,
15467 ) -> Result<()> {
15468 let buffer = display_map.buffer_snapshot();
15469 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15470 if let Some(mut select_next_state) = self.select_next_state.take() {
15471 let query = &select_next_state.query;
15472 if !select_next_state.done {
15473 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15474 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15475 let mut next_selected_range = None;
15476
15477 let bytes_after_last_selection =
15478 buffer.bytes_in_range(last_selection.end..buffer.len());
15479 let bytes_before_first_selection =
15480 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
15481 let query_matches = query
15482 .stream_find_iter(bytes_after_last_selection)
15483 .map(|result| (last_selection.end, result))
15484 .chain(
15485 query
15486 .stream_find_iter(bytes_before_first_selection)
15487 .map(|result| (MultiBufferOffset(0), result)),
15488 );
15489
15490 for (start_offset, query_match) in query_matches {
15491 let query_match = query_match.unwrap(); // can only fail due to I/O
15492 let offset_range =
15493 start_offset + query_match.start()..start_offset + query_match.end();
15494
15495 if !select_next_state.wordwise
15496 || (!buffer.is_inside_word(offset_range.start, None)
15497 && !buffer.is_inside_word(offset_range.end, None))
15498 {
15499 let idx = selections
15500 .partition_point(|selection| selection.end <= offset_range.start);
15501 let overlaps = selections
15502 .get(idx)
15503 .map_or(false, |selection| selection.start < offset_range.end);
15504
15505 if !overlaps {
15506 next_selected_range = Some(offset_range);
15507 break;
15508 }
15509 }
15510 }
15511
15512 if let Some(next_selected_range) = next_selected_range {
15513 self.select_match_ranges(
15514 next_selected_range,
15515 last_selection.reversed,
15516 replace_newest,
15517 autoscroll,
15518 window,
15519 cx,
15520 );
15521 } else {
15522 select_next_state.done = true;
15523 }
15524 }
15525
15526 self.select_next_state = Some(select_next_state);
15527 } else {
15528 let mut only_carets = true;
15529 let mut same_text_selected = true;
15530 let mut selected_text = None;
15531
15532 let mut selections_iter = selections.iter().peekable();
15533 while let Some(selection) = selections_iter.next() {
15534 if selection.start != selection.end {
15535 only_carets = false;
15536 }
15537
15538 if same_text_selected {
15539 if selected_text.is_none() {
15540 selected_text =
15541 Some(buffer.text_for_range(selection.range()).collect::<String>());
15542 }
15543
15544 if let Some(next_selection) = selections_iter.peek() {
15545 if next_selection.len() == selection.len() {
15546 let next_selected_text = buffer
15547 .text_for_range(next_selection.range())
15548 .collect::<String>();
15549 if Some(next_selected_text) != selected_text {
15550 same_text_selected = false;
15551 selected_text = None;
15552 }
15553 } else {
15554 same_text_selected = false;
15555 selected_text = None;
15556 }
15557 }
15558 }
15559 }
15560
15561 if only_carets {
15562 for selection in &mut selections {
15563 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15564 selection.start = word_range.start;
15565 selection.end = word_range.end;
15566 selection.goal = SelectionGoal::None;
15567 selection.reversed = false;
15568 self.select_match_ranges(
15569 selection.start..selection.end,
15570 selection.reversed,
15571 replace_newest,
15572 autoscroll,
15573 window,
15574 cx,
15575 );
15576 }
15577
15578 if selections.len() == 1 {
15579 let selection = selections
15580 .last()
15581 .expect("ensured that there's only one selection");
15582 let query = buffer
15583 .text_for_range(selection.start..selection.end)
15584 .collect::<String>();
15585 let is_empty = query.is_empty();
15586 let select_state = SelectNextState {
15587 query: self.build_query(&[query], cx)?,
15588 wordwise: true,
15589 done: is_empty,
15590 };
15591 self.select_next_state = Some(select_state);
15592 } else {
15593 self.select_next_state = None;
15594 }
15595 } else if let Some(selected_text) = selected_text {
15596 self.select_next_state = Some(SelectNextState {
15597 query: self.build_query(&[selected_text], cx)?,
15598 wordwise: false,
15599 done: false,
15600 });
15601 self.select_next_match_internal(
15602 display_map,
15603 replace_newest,
15604 autoscroll,
15605 window,
15606 cx,
15607 )?;
15608 }
15609 }
15610 Ok(())
15611 }
15612
15613 pub fn select_all_matches(
15614 &mut self,
15615 _action: &SelectAllMatches,
15616 window: &mut Window,
15617 cx: &mut Context<Self>,
15618 ) -> Result<()> {
15619 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15620
15621 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15622
15623 self.select_next_match_internal(&display_map, false, None, window, cx)?;
15624 let Some(select_next_state) = self.select_next_state.as_mut().filter(|state| !state.done)
15625 else {
15626 return Ok(());
15627 };
15628
15629 let mut new_selections = Vec::new();
15630
15631 let reversed = self
15632 .selections
15633 .oldest::<MultiBufferOffset>(&display_map)
15634 .reversed;
15635 let buffer = display_map.buffer_snapshot();
15636 let query_matches = select_next_state
15637 .query
15638 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
15639
15640 for query_match in query_matches.into_iter() {
15641 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
15642 let offset_range = if reversed {
15643 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
15644 } else {
15645 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
15646 };
15647
15648 if !select_next_state.wordwise
15649 || (!buffer.is_inside_word(offset_range.start, None)
15650 && !buffer.is_inside_word(offset_range.end, None))
15651 {
15652 new_selections.push(offset_range.start..offset_range.end);
15653 }
15654 }
15655
15656 select_next_state.done = true;
15657
15658 if new_selections.is_empty() {
15659 log::error!("bug: new_selections is empty in select_all_matches");
15660 return Ok(());
15661 }
15662
15663 self.unfold_ranges(&new_selections, false, false, cx);
15664 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
15665 selections.select_ranges(new_selections)
15666 });
15667
15668 Ok(())
15669 }
15670
15671 pub fn select_next(
15672 &mut self,
15673 action: &SelectNext,
15674 window: &mut Window,
15675 cx: &mut Context<Self>,
15676 ) -> Result<()> {
15677 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15678 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15679 self.select_next_match_internal(
15680 &display_map,
15681 action.replace_newest,
15682 Some(Autoscroll::newest()),
15683 window,
15684 cx,
15685 )
15686 }
15687
15688 pub fn select_previous(
15689 &mut self,
15690 action: &SelectPrevious,
15691 window: &mut Window,
15692 cx: &mut Context<Self>,
15693 ) -> Result<()> {
15694 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15695 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15696 let buffer = display_map.buffer_snapshot();
15697 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15698 if let Some(mut select_prev_state) = self.select_prev_state.take() {
15699 let query = &select_prev_state.query;
15700 if !select_prev_state.done {
15701 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15702 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15703 let mut next_selected_range = None;
15704 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
15705 let bytes_before_last_selection =
15706 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
15707 let bytes_after_first_selection =
15708 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
15709 let query_matches = query
15710 .stream_find_iter(bytes_before_last_selection)
15711 .map(|result| (last_selection.start, result))
15712 .chain(
15713 query
15714 .stream_find_iter(bytes_after_first_selection)
15715 .map(|result| (buffer.len(), result)),
15716 );
15717 for (end_offset, query_match) in query_matches {
15718 let query_match = query_match.unwrap(); // can only fail due to I/O
15719 let offset_range =
15720 end_offset - query_match.end()..end_offset - query_match.start();
15721
15722 if !select_prev_state.wordwise
15723 || (!buffer.is_inside_word(offset_range.start, None)
15724 && !buffer.is_inside_word(offset_range.end, None))
15725 {
15726 next_selected_range = Some(offset_range);
15727 break;
15728 }
15729 }
15730
15731 if let Some(next_selected_range) = next_selected_range {
15732 self.select_match_ranges(
15733 next_selected_range,
15734 last_selection.reversed,
15735 action.replace_newest,
15736 Some(Autoscroll::newest()),
15737 window,
15738 cx,
15739 );
15740 } else {
15741 select_prev_state.done = true;
15742 }
15743 }
15744
15745 self.select_prev_state = Some(select_prev_state);
15746 } else {
15747 let mut only_carets = true;
15748 let mut same_text_selected = true;
15749 let mut selected_text = None;
15750
15751 let mut selections_iter = selections.iter().peekable();
15752 while let Some(selection) = selections_iter.next() {
15753 if selection.start != selection.end {
15754 only_carets = false;
15755 }
15756
15757 if same_text_selected {
15758 if selected_text.is_none() {
15759 selected_text =
15760 Some(buffer.text_for_range(selection.range()).collect::<String>());
15761 }
15762
15763 if let Some(next_selection) = selections_iter.peek() {
15764 if next_selection.len() == selection.len() {
15765 let next_selected_text = buffer
15766 .text_for_range(next_selection.range())
15767 .collect::<String>();
15768 if Some(next_selected_text) != selected_text {
15769 same_text_selected = false;
15770 selected_text = None;
15771 }
15772 } else {
15773 same_text_selected = false;
15774 selected_text = None;
15775 }
15776 }
15777 }
15778 }
15779
15780 if only_carets {
15781 for selection in &mut selections {
15782 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15783 selection.start = word_range.start;
15784 selection.end = word_range.end;
15785 selection.goal = SelectionGoal::None;
15786 selection.reversed = false;
15787 self.select_match_ranges(
15788 selection.start..selection.end,
15789 selection.reversed,
15790 action.replace_newest,
15791 Some(Autoscroll::newest()),
15792 window,
15793 cx,
15794 );
15795 }
15796 if selections.len() == 1 {
15797 let selection = selections
15798 .last()
15799 .expect("ensured that there's only one selection");
15800 let query = buffer
15801 .text_for_range(selection.start..selection.end)
15802 .collect::<String>();
15803 let is_empty = query.is_empty();
15804 let select_state = SelectNextState {
15805 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
15806 wordwise: true,
15807 done: is_empty,
15808 };
15809 self.select_prev_state = Some(select_state);
15810 } else {
15811 self.select_prev_state = None;
15812 }
15813 } else if let Some(selected_text) = selected_text {
15814 self.select_prev_state = Some(SelectNextState {
15815 query: self
15816 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
15817 wordwise: false,
15818 done: false,
15819 });
15820 self.select_previous(action, window, cx)?;
15821 }
15822 }
15823 Ok(())
15824 }
15825
15826 /// Builds an `AhoCorasick` automaton from the provided patterns, while
15827 /// setting the case sensitivity based on the global
15828 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
15829 /// editor's settings.
15830 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
15831 where
15832 I: IntoIterator<Item = P>,
15833 P: AsRef<[u8]>,
15834 {
15835 let case_sensitive = self
15836 .select_next_is_case_sensitive
15837 .unwrap_or_else(|| EditorSettings::get_global(cx).search.case_sensitive);
15838
15839 let mut builder = AhoCorasickBuilder::new();
15840 builder.ascii_case_insensitive(!case_sensitive);
15841 builder.build(patterns)
15842 }
15843
15844 pub fn find_next_match(
15845 &mut self,
15846 _: &FindNextMatch,
15847 window: &mut Window,
15848 cx: &mut Context<Self>,
15849 ) -> Result<()> {
15850 let selections = self.selections.disjoint_anchors_arc();
15851 match selections.first() {
15852 Some(first) if selections.len() >= 2 => {
15853 self.change_selections(Default::default(), window, cx, |s| {
15854 s.select_ranges([first.range()]);
15855 });
15856 }
15857 _ => self.select_next(
15858 &SelectNext {
15859 replace_newest: true,
15860 },
15861 window,
15862 cx,
15863 )?,
15864 }
15865 Ok(())
15866 }
15867
15868 pub fn find_previous_match(
15869 &mut self,
15870 _: &FindPreviousMatch,
15871 window: &mut Window,
15872 cx: &mut Context<Self>,
15873 ) -> Result<()> {
15874 let selections = self.selections.disjoint_anchors_arc();
15875 match selections.last() {
15876 Some(last) if selections.len() >= 2 => {
15877 self.change_selections(Default::default(), window, cx, |s| {
15878 s.select_ranges([last.range()]);
15879 });
15880 }
15881 _ => self.select_previous(
15882 &SelectPrevious {
15883 replace_newest: true,
15884 },
15885 window,
15886 cx,
15887 )?,
15888 }
15889 Ok(())
15890 }
15891
15892 pub fn toggle_comments(
15893 &mut self,
15894 action: &ToggleComments,
15895 window: &mut Window,
15896 cx: &mut Context<Self>,
15897 ) {
15898 if self.read_only(cx) {
15899 return;
15900 }
15901 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15902 let text_layout_details = &self.text_layout_details(window, cx);
15903 self.transact(window, cx, |this, window, cx| {
15904 let mut selections = this
15905 .selections
15906 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15907 let mut edits = Vec::new();
15908 let mut selection_edit_ranges = Vec::new();
15909 let mut last_toggled_row = None;
15910 let snapshot = this.buffer.read(cx).read(cx);
15911 let empty_str: Arc<str> = Arc::default();
15912 let mut suffixes_inserted = Vec::new();
15913 let ignore_indent = action.ignore_indent;
15914
15915 fn comment_prefix_range(
15916 snapshot: &MultiBufferSnapshot,
15917 row: MultiBufferRow,
15918 comment_prefix: &str,
15919 comment_prefix_whitespace: &str,
15920 ignore_indent: bool,
15921 ) -> Range<Point> {
15922 let indent_size = if ignore_indent {
15923 0
15924 } else {
15925 snapshot.indent_size_for_line(row).len
15926 };
15927
15928 let start = Point::new(row.0, indent_size);
15929
15930 let mut line_bytes = snapshot
15931 .bytes_in_range(start..snapshot.max_point())
15932 .flatten()
15933 .copied();
15934
15935 // If this line currently begins with the line comment prefix, then record
15936 // the range containing the prefix.
15937 if line_bytes
15938 .by_ref()
15939 .take(comment_prefix.len())
15940 .eq(comment_prefix.bytes())
15941 {
15942 // Include any whitespace that matches the comment prefix.
15943 let matching_whitespace_len = line_bytes
15944 .zip(comment_prefix_whitespace.bytes())
15945 .take_while(|(a, b)| a == b)
15946 .count() as u32;
15947 let end = Point::new(
15948 start.row,
15949 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15950 );
15951 start..end
15952 } else {
15953 start..start
15954 }
15955 }
15956
15957 fn comment_suffix_range(
15958 snapshot: &MultiBufferSnapshot,
15959 row: MultiBufferRow,
15960 comment_suffix: &str,
15961 comment_suffix_has_leading_space: bool,
15962 ) -> Range<Point> {
15963 let end = Point::new(row.0, snapshot.line_len(row));
15964 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15965
15966 let mut line_end_bytes = snapshot
15967 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15968 .flatten()
15969 .copied();
15970
15971 let leading_space_len = if suffix_start_column > 0
15972 && line_end_bytes.next() == Some(b' ')
15973 && comment_suffix_has_leading_space
15974 {
15975 1
15976 } else {
15977 0
15978 };
15979
15980 // If this line currently begins with the line comment prefix, then record
15981 // the range containing the prefix.
15982 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15983 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15984 start..end
15985 } else {
15986 end..end
15987 }
15988 }
15989
15990 // TODO: Handle selections that cross excerpts
15991 for selection in &mut selections {
15992 let start_column = snapshot
15993 .indent_size_for_line(MultiBufferRow(selection.start.row))
15994 .len;
15995 let language = if let Some(language) =
15996 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15997 {
15998 language
15999 } else {
16000 continue;
16001 };
16002
16003 selection_edit_ranges.clear();
16004
16005 // If multiple selections contain a given row, avoid processing that
16006 // row more than once.
16007 let mut start_row = MultiBufferRow(selection.start.row);
16008 if last_toggled_row == Some(start_row) {
16009 start_row = start_row.next_row();
16010 }
16011 let end_row =
16012 if selection.end.row > selection.start.row && selection.end.column == 0 {
16013 MultiBufferRow(selection.end.row - 1)
16014 } else {
16015 MultiBufferRow(selection.end.row)
16016 };
16017 last_toggled_row = Some(end_row);
16018
16019 if start_row > end_row {
16020 continue;
16021 }
16022
16023 // If the language has line comments, toggle those.
16024 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
16025
16026 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
16027 if ignore_indent {
16028 full_comment_prefixes = full_comment_prefixes
16029 .into_iter()
16030 .map(|s| Arc::from(s.trim_end()))
16031 .collect();
16032 }
16033
16034 if !full_comment_prefixes.is_empty() {
16035 let first_prefix = full_comment_prefixes
16036 .first()
16037 .expect("prefixes is non-empty");
16038 let prefix_trimmed_lengths = full_comment_prefixes
16039 .iter()
16040 .map(|p| p.trim_end_matches(' ').len())
16041 .collect::<SmallVec<[usize; 4]>>();
16042
16043 let mut all_selection_lines_are_comments = true;
16044
16045 for row in start_row.0..=end_row.0 {
16046 let row = MultiBufferRow(row);
16047 if start_row < end_row && snapshot.is_line_blank(row) {
16048 continue;
16049 }
16050
16051 let prefix_range = full_comment_prefixes
16052 .iter()
16053 .zip(prefix_trimmed_lengths.iter().copied())
16054 .map(|(prefix, trimmed_prefix_len)| {
16055 comment_prefix_range(
16056 snapshot.deref(),
16057 row,
16058 &prefix[..trimmed_prefix_len],
16059 &prefix[trimmed_prefix_len..],
16060 ignore_indent,
16061 )
16062 })
16063 .max_by_key(|range| range.end.column - range.start.column)
16064 .expect("prefixes is non-empty");
16065
16066 if prefix_range.is_empty() {
16067 all_selection_lines_are_comments = false;
16068 }
16069
16070 selection_edit_ranges.push(prefix_range);
16071 }
16072
16073 if all_selection_lines_are_comments {
16074 edits.extend(
16075 selection_edit_ranges
16076 .iter()
16077 .cloned()
16078 .map(|range| (range, empty_str.clone())),
16079 );
16080 } else {
16081 let min_column = selection_edit_ranges
16082 .iter()
16083 .map(|range| range.start.column)
16084 .min()
16085 .unwrap_or(0);
16086 edits.extend(selection_edit_ranges.iter().map(|range| {
16087 let position = Point::new(range.start.row, min_column);
16088 (position..position, first_prefix.clone())
16089 }));
16090 }
16091 } else if let Some(BlockCommentConfig {
16092 start: full_comment_prefix,
16093 end: comment_suffix,
16094 ..
16095 }) = language.block_comment()
16096 {
16097 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
16098 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
16099 let prefix_range = comment_prefix_range(
16100 snapshot.deref(),
16101 start_row,
16102 comment_prefix,
16103 comment_prefix_whitespace,
16104 ignore_indent,
16105 );
16106 let suffix_range = comment_suffix_range(
16107 snapshot.deref(),
16108 end_row,
16109 comment_suffix.trim_start_matches(' '),
16110 comment_suffix.starts_with(' '),
16111 );
16112
16113 if prefix_range.is_empty() || suffix_range.is_empty() {
16114 edits.push((
16115 prefix_range.start..prefix_range.start,
16116 full_comment_prefix.clone(),
16117 ));
16118 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
16119 suffixes_inserted.push((end_row, comment_suffix.len()));
16120 } else {
16121 edits.push((prefix_range, empty_str.clone()));
16122 edits.push((suffix_range, empty_str.clone()));
16123 }
16124 } else {
16125 continue;
16126 }
16127 }
16128
16129 drop(snapshot);
16130 this.buffer.update(cx, |buffer, cx| {
16131 buffer.edit(edits, None, cx);
16132 });
16133
16134 // Adjust selections so that they end before any comment suffixes that
16135 // were inserted.
16136 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
16137 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16138 let snapshot = this.buffer.read(cx).read(cx);
16139 for selection in &mut selections {
16140 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
16141 match row.cmp(&MultiBufferRow(selection.end.row)) {
16142 Ordering::Less => {
16143 suffixes_inserted.next();
16144 continue;
16145 }
16146 Ordering::Greater => break,
16147 Ordering::Equal => {
16148 if selection.end.column == snapshot.line_len(row) {
16149 if selection.is_empty() {
16150 selection.start.column -= suffix_len as u32;
16151 }
16152 selection.end.column -= suffix_len as u32;
16153 }
16154 break;
16155 }
16156 }
16157 }
16158 }
16159
16160 drop(snapshot);
16161 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
16162
16163 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16164 let selections_on_single_row = selections.windows(2).all(|selections| {
16165 selections[0].start.row == selections[1].start.row
16166 && selections[0].end.row == selections[1].end.row
16167 && selections[0].start.row == selections[0].end.row
16168 });
16169 let selections_selecting = selections
16170 .iter()
16171 .any(|selection| selection.start != selection.end);
16172 let advance_downwards = action.advance_downwards
16173 && selections_on_single_row
16174 && !selections_selecting
16175 && !matches!(this.mode, EditorMode::SingleLine);
16176
16177 if advance_downwards {
16178 let snapshot = this.buffer.read(cx).snapshot(cx);
16179
16180 this.change_selections(Default::default(), window, cx, |s| {
16181 s.move_cursors_with(|display_snapshot, display_point, _| {
16182 let mut point = display_point.to_point(display_snapshot);
16183 point.row += 1;
16184 point = snapshot.clip_point(point, Bias::Left);
16185 let display_point = point.to_display_point(display_snapshot);
16186 let goal = SelectionGoal::HorizontalPosition(
16187 display_snapshot
16188 .x_for_display_point(display_point, text_layout_details)
16189 .into(),
16190 );
16191 (display_point, goal)
16192 })
16193 });
16194 }
16195 });
16196 }
16197
16198 pub fn select_enclosing_symbol(
16199 &mut self,
16200 _: &SelectEnclosingSymbol,
16201 window: &mut Window,
16202 cx: &mut Context<Self>,
16203 ) {
16204 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16205
16206 let buffer = self.buffer.read(cx).snapshot(cx);
16207 let old_selections = self
16208 .selections
16209 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16210 .into_boxed_slice();
16211
16212 fn update_selection(
16213 selection: &Selection<MultiBufferOffset>,
16214 buffer_snap: &MultiBufferSnapshot,
16215 ) -> Option<Selection<MultiBufferOffset>> {
16216 let cursor = selection.head();
16217 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
16218 for symbol in symbols.iter().rev() {
16219 let start = symbol.range.start.to_offset(buffer_snap);
16220 let end = symbol.range.end.to_offset(buffer_snap);
16221 let new_range = start..end;
16222 if start < selection.start || end > selection.end {
16223 return Some(Selection {
16224 id: selection.id,
16225 start: new_range.start,
16226 end: new_range.end,
16227 goal: SelectionGoal::None,
16228 reversed: selection.reversed,
16229 });
16230 }
16231 }
16232 None
16233 }
16234
16235 let mut selected_larger_symbol = false;
16236 let new_selections = old_selections
16237 .iter()
16238 .map(|selection| match update_selection(selection, &buffer) {
16239 Some(new_selection) => {
16240 if new_selection.range() != selection.range() {
16241 selected_larger_symbol = true;
16242 }
16243 new_selection
16244 }
16245 None => selection.clone(),
16246 })
16247 .collect::<Vec<_>>();
16248
16249 if selected_larger_symbol {
16250 self.change_selections(Default::default(), window, cx, |s| {
16251 s.select(new_selections);
16252 });
16253 }
16254 }
16255
16256 pub fn select_larger_syntax_node(
16257 &mut self,
16258 _: &SelectLargerSyntaxNode,
16259 window: &mut Window,
16260 cx: &mut Context<Self>,
16261 ) {
16262 let Some(visible_row_count) = self.visible_row_count() else {
16263 return;
16264 };
16265 let old_selections: Box<[_]> = self
16266 .selections
16267 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16268 .into();
16269 if old_selections.is_empty() {
16270 return;
16271 }
16272
16273 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16274
16275 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16276 let buffer = self.buffer.read(cx).snapshot(cx);
16277
16278 let mut selected_larger_node = false;
16279 let mut new_selections = old_selections
16280 .iter()
16281 .map(|selection| {
16282 let old_range = selection.start..selection.end;
16283
16284 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
16285 // manually select word at selection
16286 if ["string_content", "inline"].contains(&node.kind()) {
16287 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
16288 // ignore if word is already selected
16289 if !word_range.is_empty() && old_range != word_range {
16290 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
16291 // only select word if start and end point belongs to same word
16292 if word_range == last_word_range {
16293 selected_larger_node = true;
16294 return Selection {
16295 id: selection.id,
16296 start: word_range.start,
16297 end: word_range.end,
16298 goal: SelectionGoal::None,
16299 reversed: selection.reversed,
16300 };
16301 }
16302 }
16303 }
16304 }
16305
16306 let mut new_range = old_range.clone();
16307 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
16308 new_range = range;
16309 if !node.is_named() {
16310 continue;
16311 }
16312 if !display_map.intersects_fold(new_range.start)
16313 && !display_map.intersects_fold(new_range.end)
16314 {
16315 break;
16316 }
16317 }
16318
16319 selected_larger_node |= new_range != old_range;
16320 Selection {
16321 id: selection.id,
16322 start: new_range.start,
16323 end: new_range.end,
16324 goal: SelectionGoal::None,
16325 reversed: selection.reversed,
16326 }
16327 })
16328 .collect::<Vec<_>>();
16329
16330 if !selected_larger_node {
16331 return; // don't put this call in the history
16332 }
16333
16334 // scroll based on transformation done to the last selection created by the user
16335 let (last_old, last_new) = old_selections
16336 .last()
16337 .zip(new_selections.last().cloned())
16338 .expect("old_selections isn't empty");
16339
16340 // revert selection
16341 let is_selection_reversed = {
16342 let should_newest_selection_be_reversed = last_old.start != last_new.start;
16343 new_selections.last_mut().expect("checked above").reversed =
16344 should_newest_selection_be_reversed;
16345 should_newest_selection_be_reversed
16346 };
16347
16348 if selected_larger_node {
16349 self.select_syntax_node_history.disable_clearing = true;
16350 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16351 s.select(new_selections.clone());
16352 });
16353 self.select_syntax_node_history.disable_clearing = false;
16354 }
16355
16356 let start_row = last_new.start.to_display_point(&display_map).row().0;
16357 let end_row = last_new.end.to_display_point(&display_map).row().0;
16358 let selection_height = end_row - start_row + 1;
16359 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
16360
16361 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
16362 let scroll_behavior = if fits_on_the_screen {
16363 self.request_autoscroll(Autoscroll::fit(), cx);
16364 SelectSyntaxNodeScrollBehavior::FitSelection
16365 } else if is_selection_reversed {
16366 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16367 SelectSyntaxNodeScrollBehavior::CursorTop
16368 } else {
16369 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16370 SelectSyntaxNodeScrollBehavior::CursorBottom
16371 };
16372
16373 self.select_syntax_node_history.push((
16374 old_selections,
16375 scroll_behavior,
16376 is_selection_reversed,
16377 ));
16378 }
16379
16380 pub fn select_smaller_syntax_node(
16381 &mut self,
16382 _: &SelectSmallerSyntaxNode,
16383 window: &mut Window,
16384 cx: &mut Context<Self>,
16385 ) {
16386 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16387
16388 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
16389 self.select_syntax_node_history.pop()
16390 {
16391 if let Some(selection) = selections.last_mut() {
16392 selection.reversed = is_selection_reversed;
16393 }
16394
16395 self.select_syntax_node_history.disable_clearing = true;
16396 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16397 s.select(selections.to_vec());
16398 });
16399 self.select_syntax_node_history.disable_clearing = false;
16400
16401 match scroll_behavior {
16402 SelectSyntaxNodeScrollBehavior::CursorTop => {
16403 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16404 }
16405 SelectSyntaxNodeScrollBehavior::FitSelection => {
16406 self.request_autoscroll(Autoscroll::fit(), cx);
16407 }
16408 SelectSyntaxNodeScrollBehavior::CursorBottom => {
16409 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16410 }
16411 }
16412 }
16413 }
16414
16415 pub fn unwrap_syntax_node(
16416 &mut self,
16417 _: &UnwrapSyntaxNode,
16418 window: &mut Window,
16419 cx: &mut Context<Self>,
16420 ) {
16421 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16422
16423 let buffer = self.buffer.read(cx).snapshot(cx);
16424 let selections = self
16425 .selections
16426 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16427 .into_iter()
16428 // subtracting the offset requires sorting
16429 .sorted_by_key(|i| i.start);
16430
16431 let full_edits = selections
16432 .into_iter()
16433 .filter_map(|selection| {
16434 let child = if selection.is_empty()
16435 && let Some((_, ancestor_range)) =
16436 buffer.syntax_ancestor(selection.start..selection.end)
16437 {
16438 ancestor_range
16439 } else {
16440 selection.range()
16441 };
16442
16443 let mut parent = child.clone();
16444 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
16445 parent = ancestor_range;
16446 if parent.start < child.start || parent.end > child.end {
16447 break;
16448 }
16449 }
16450
16451 if parent == child {
16452 return None;
16453 }
16454 let text = buffer.text_for_range(child).collect::<String>();
16455 Some((selection.id, parent, text))
16456 })
16457 .collect::<Vec<_>>();
16458 if full_edits.is_empty() {
16459 return;
16460 }
16461
16462 self.transact(window, cx, |this, window, cx| {
16463 this.buffer.update(cx, |buffer, cx| {
16464 buffer.edit(
16465 full_edits
16466 .iter()
16467 .map(|(_, p, t)| (p.clone(), t.clone()))
16468 .collect::<Vec<_>>(),
16469 None,
16470 cx,
16471 );
16472 });
16473 this.change_selections(Default::default(), window, cx, |s| {
16474 let mut offset = 0;
16475 let mut selections = vec![];
16476 for (id, parent, text) in full_edits {
16477 let start = parent.start - offset;
16478 offset += (parent.end - parent.start) - text.len();
16479 selections.push(Selection {
16480 id,
16481 start,
16482 end: start + text.len(),
16483 reversed: false,
16484 goal: Default::default(),
16485 });
16486 }
16487 s.select(selections);
16488 });
16489 });
16490 }
16491
16492 pub fn select_next_syntax_node(
16493 &mut self,
16494 _: &SelectNextSyntaxNode,
16495 window: &mut Window,
16496 cx: &mut Context<Self>,
16497 ) {
16498 let old_selections: Box<[_]> = self
16499 .selections
16500 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16501 .into();
16502 if old_selections.is_empty() {
16503 return;
16504 }
16505
16506 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16507
16508 let buffer = self.buffer.read(cx).snapshot(cx);
16509 let mut selected_sibling = false;
16510
16511 let new_selections = old_selections
16512 .iter()
16513 .map(|selection| {
16514 let old_range = selection.start..selection.end;
16515
16516 let old_range =
16517 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16518 let excerpt = buffer.excerpt_containing(old_range.clone());
16519
16520 if let Some(mut excerpt) = excerpt
16521 && let Some(node) = excerpt
16522 .buffer()
16523 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
16524 {
16525 let new_range = excerpt.map_range_from_buffer(
16526 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16527 );
16528 selected_sibling = true;
16529 Selection {
16530 id: selection.id,
16531 start: new_range.start,
16532 end: new_range.end,
16533 goal: SelectionGoal::None,
16534 reversed: selection.reversed,
16535 }
16536 } else {
16537 selection.clone()
16538 }
16539 })
16540 .collect::<Vec<_>>();
16541
16542 if selected_sibling {
16543 self.change_selections(
16544 SelectionEffects::scroll(Autoscroll::fit()),
16545 window,
16546 cx,
16547 |s| {
16548 s.select(new_selections);
16549 },
16550 );
16551 }
16552 }
16553
16554 pub fn select_prev_syntax_node(
16555 &mut self,
16556 _: &SelectPreviousSyntaxNode,
16557 window: &mut Window,
16558 cx: &mut Context<Self>,
16559 ) {
16560 let old_selections: Box<[_]> = self
16561 .selections
16562 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16563 .into();
16564 if old_selections.is_empty() {
16565 return;
16566 }
16567
16568 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16569
16570 let buffer = self.buffer.read(cx).snapshot(cx);
16571 let mut selected_sibling = false;
16572
16573 let new_selections = old_selections
16574 .iter()
16575 .map(|selection| {
16576 let old_range = selection.start..selection.end;
16577 let old_range =
16578 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16579 let excerpt = buffer.excerpt_containing(old_range.clone());
16580
16581 if let Some(mut excerpt) = excerpt
16582 && let Some(node) = excerpt
16583 .buffer()
16584 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
16585 {
16586 let new_range = excerpt.map_range_from_buffer(
16587 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16588 );
16589 selected_sibling = true;
16590 Selection {
16591 id: selection.id,
16592 start: new_range.start,
16593 end: new_range.end,
16594 goal: SelectionGoal::None,
16595 reversed: selection.reversed,
16596 }
16597 } else {
16598 selection.clone()
16599 }
16600 })
16601 .collect::<Vec<_>>();
16602
16603 if selected_sibling {
16604 self.change_selections(
16605 SelectionEffects::scroll(Autoscroll::fit()),
16606 window,
16607 cx,
16608 |s| {
16609 s.select(new_selections);
16610 },
16611 );
16612 }
16613 }
16614
16615 pub fn move_to_start_of_larger_syntax_node(
16616 &mut self,
16617 _: &MoveToStartOfLargerSyntaxNode,
16618 window: &mut Window,
16619 cx: &mut Context<Self>,
16620 ) {
16621 self.move_cursors_to_syntax_nodes(window, cx, false);
16622 }
16623
16624 pub fn move_to_end_of_larger_syntax_node(
16625 &mut self,
16626 _: &MoveToEndOfLargerSyntaxNode,
16627 window: &mut Window,
16628 cx: &mut Context<Self>,
16629 ) {
16630 self.move_cursors_to_syntax_nodes(window, cx, true);
16631 }
16632
16633 fn find_syntax_node_boundary(
16634 &self,
16635 selection_pos: MultiBufferOffset,
16636 move_to_end: bool,
16637 display_map: &DisplaySnapshot,
16638 buffer: &MultiBufferSnapshot,
16639 ) -> MultiBufferOffset {
16640 let old_range = selection_pos..selection_pos;
16641 let mut new_pos = selection_pos;
16642 let mut search_range = old_range;
16643 while let Some((node, range)) = buffer.syntax_ancestor(search_range.clone()) {
16644 search_range = range.clone();
16645 if !node.is_named()
16646 || display_map.intersects_fold(range.start)
16647 || display_map.intersects_fold(range.end)
16648 // If cursor is already at the end of the syntax node, continue searching
16649 || (move_to_end && range.end == selection_pos)
16650 // If cursor is already at the start of the syntax node, continue searching
16651 || (!move_to_end && range.start == selection_pos)
16652 {
16653 continue;
16654 }
16655
16656 // If we found a string_content node, find the largest parent that is still string_content
16657 // Enables us to skip to the end of strings without taking multiple steps inside the string
16658 let (_, final_range) = if node.kind() == "string_content" {
16659 let mut current_node = node;
16660 let mut current_range = range;
16661 while let Some((parent, parent_range)) =
16662 buffer.syntax_ancestor(current_range.clone())
16663 {
16664 if parent.kind() == "string_content" {
16665 current_node = parent;
16666 current_range = parent_range;
16667 } else {
16668 break;
16669 }
16670 }
16671
16672 (current_node, current_range)
16673 } else {
16674 (node, range)
16675 };
16676
16677 new_pos = if move_to_end {
16678 final_range.end
16679 } else {
16680 final_range.start
16681 };
16682
16683 break;
16684 }
16685
16686 new_pos
16687 }
16688
16689 fn move_cursors_to_syntax_nodes(
16690 &mut self,
16691 window: &mut Window,
16692 cx: &mut Context<Self>,
16693 move_to_end: bool,
16694 ) -> bool {
16695 let old_selections: Box<[_]> = self
16696 .selections
16697 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16698 .into();
16699 if old_selections.is_empty() {
16700 return false;
16701 }
16702
16703 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16704
16705 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16706 let buffer = self.buffer.read(cx).snapshot(cx);
16707
16708 let mut any_cursor_moved = false;
16709 let new_selections = old_selections
16710 .iter()
16711 .map(|selection| {
16712 if !selection.is_empty() {
16713 return selection.clone();
16714 }
16715
16716 let selection_pos = selection.head();
16717 let new_pos = self.find_syntax_node_boundary(
16718 selection_pos,
16719 move_to_end,
16720 &display_map,
16721 &buffer,
16722 );
16723
16724 any_cursor_moved |= new_pos != selection_pos;
16725
16726 Selection {
16727 id: selection.id,
16728 start: new_pos,
16729 end: new_pos,
16730 goal: SelectionGoal::None,
16731 reversed: false,
16732 }
16733 })
16734 .collect::<Vec<_>>();
16735
16736 self.change_selections(Default::default(), window, cx, |s| {
16737 s.select(new_selections);
16738 });
16739 self.request_autoscroll(Autoscroll::newest(), cx);
16740
16741 any_cursor_moved
16742 }
16743
16744 pub fn select_to_start_of_larger_syntax_node(
16745 &mut self,
16746 _: &SelectToStartOfLargerSyntaxNode,
16747 window: &mut Window,
16748 cx: &mut Context<Self>,
16749 ) {
16750 self.select_to_syntax_nodes(window, cx, false);
16751 }
16752
16753 pub fn select_to_end_of_larger_syntax_node(
16754 &mut self,
16755 _: &SelectToEndOfLargerSyntaxNode,
16756 window: &mut Window,
16757 cx: &mut Context<Self>,
16758 ) {
16759 self.select_to_syntax_nodes(window, cx, true);
16760 }
16761
16762 fn select_to_syntax_nodes(
16763 &mut self,
16764 window: &mut Window,
16765 cx: &mut Context<Self>,
16766 move_to_end: bool,
16767 ) {
16768 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16769
16770 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16771 let buffer = self.buffer.read(cx).snapshot(cx);
16772 let old_selections = self.selections.all::<MultiBufferOffset>(&display_map);
16773
16774 let new_selections = old_selections
16775 .iter()
16776 .map(|selection| {
16777 let new_pos = self.find_syntax_node_boundary(
16778 selection.head(),
16779 move_to_end,
16780 &display_map,
16781 &buffer,
16782 );
16783
16784 let mut new_selection = selection.clone();
16785 new_selection.set_head(new_pos, SelectionGoal::None);
16786 new_selection
16787 })
16788 .collect::<Vec<_>>();
16789
16790 self.change_selections(Default::default(), window, cx, |s| {
16791 s.select(new_selections);
16792 });
16793 }
16794
16795 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
16796 if !EditorSettings::get_global(cx).gutter.runnables {
16797 self.clear_tasks();
16798 return Task::ready(());
16799 }
16800 let project = self.project().map(Entity::downgrade);
16801 let task_sources = self.lsp_task_sources(cx);
16802 let multi_buffer = self.buffer.downgrade();
16803 cx.spawn_in(window, async move |editor, cx| {
16804 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
16805 let Some(project) = project.and_then(|p| p.upgrade()) else {
16806 return;
16807 };
16808 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
16809 this.display_map.update(cx, |map, cx| map.snapshot(cx))
16810 }) else {
16811 return;
16812 };
16813
16814 let hide_runnables = project.update(cx, |project, _| project.is_via_collab());
16815 if hide_runnables {
16816 return;
16817 }
16818 let new_rows =
16819 cx.background_spawn({
16820 let snapshot = display_snapshot.clone();
16821 async move {
16822 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
16823 }
16824 })
16825 .await;
16826 let Ok(lsp_tasks) =
16827 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
16828 else {
16829 return;
16830 };
16831 let lsp_tasks = lsp_tasks.await;
16832
16833 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
16834 lsp_tasks
16835 .into_iter()
16836 .flat_map(|(kind, tasks)| {
16837 tasks.into_iter().filter_map(move |(location, task)| {
16838 Some((kind.clone(), location?, task))
16839 })
16840 })
16841 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
16842 let buffer = location.target.buffer;
16843 let buffer_snapshot = buffer.read(cx).snapshot();
16844 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
16845 |(excerpt_id, snapshot, _)| {
16846 if snapshot.remote_id() == buffer_snapshot.remote_id() {
16847 display_snapshot
16848 .buffer_snapshot()
16849 .anchor_in_excerpt(excerpt_id, location.target.range.start)
16850 } else {
16851 None
16852 }
16853 },
16854 );
16855 if let Some(offset) = offset {
16856 let task_buffer_range =
16857 location.target.range.to_point(&buffer_snapshot);
16858 let context_buffer_range =
16859 task_buffer_range.to_offset(&buffer_snapshot);
16860 let context_range = BufferOffset(context_buffer_range.start)
16861 ..BufferOffset(context_buffer_range.end);
16862
16863 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
16864 .or_insert_with(|| RunnableTasks {
16865 templates: Vec::new(),
16866 offset,
16867 column: task_buffer_range.start.column,
16868 extra_variables: HashMap::default(),
16869 context_range,
16870 })
16871 .templates
16872 .push((kind, task.original_task().clone()));
16873 }
16874
16875 acc
16876 })
16877 }) else {
16878 return;
16879 };
16880
16881 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
16882 buffer.language_settings(cx).tasks.prefer_lsp
16883 }) else {
16884 return;
16885 };
16886
16887 let rows = Self::runnable_rows(
16888 project,
16889 display_snapshot,
16890 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
16891 new_rows,
16892 cx.clone(),
16893 )
16894 .await;
16895 editor
16896 .update(cx, |editor, _| {
16897 editor.clear_tasks();
16898 for (key, mut value) in rows {
16899 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
16900 value.templates.extend(lsp_tasks.templates);
16901 }
16902
16903 editor.insert_tasks(key, value);
16904 }
16905 for (key, value) in lsp_tasks_by_rows {
16906 editor.insert_tasks(key, value);
16907 }
16908 })
16909 .ok();
16910 })
16911 }
16912 fn fetch_runnable_ranges(
16913 snapshot: &DisplaySnapshot,
16914 range: Range<Anchor>,
16915 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
16916 snapshot.buffer_snapshot().runnable_ranges(range).collect()
16917 }
16918
16919 fn runnable_rows(
16920 project: Entity<Project>,
16921 snapshot: DisplaySnapshot,
16922 prefer_lsp: bool,
16923 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
16924 cx: AsyncWindowContext,
16925 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
16926 cx.spawn(async move |cx| {
16927 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
16928 for (run_range, mut runnable) in runnable_ranges {
16929 let Some(tasks) = cx
16930 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
16931 .ok()
16932 else {
16933 continue;
16934 };
16935 let mut tasks = tasks.await;
16936
16937 if prefer_lsp {
16938 tasks.retain(|(task_kind, _)| {
16939 !matches!(task_kind, TaskSourceKind::Language { .. })
16940 });
16941 }
16942 if tasks.is_empty() {
16943 continue;
16944 }
16945
16946 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
16947 let Some(row) = snapshot
16948 .buffer_snapshot()
16949 .buffer_line_for_row(MultiBufferRow(point.row))
16950 .map(|(_, range)| range.start.row)
16951 else {
16952 continue;
16953 };
16954
16955 let context_range =
16956 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
16957 runnable_rows.push((
16958 (runnable.buffer_id, row),
16959 RunnableTasks {
16960 templates: tasks,
16961 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
16962 context_range,
16963 column: point.column,
16964 extra_variables: runnable.extra_captures,
16965 },
16966 ));
16967 }
16968 runnable_rows
16969 })
16970 }
16971
16972 fn templates_with_tags(
16973 project: &Entity<Project>,
16974 runnable: &mut Runnable,
16975 cx: &mut App,
16976 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
16977 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
16978 let (worktree_id, file) = project
16979 .buffer_for_id(runnable.buffer, cx)
16980 .and_then(|buffer| buffer.read(cx).file())
16981 .map(|file| (file.worktree_id(cx), file.clone()))
16982 .unzip();
16983
16984 (
16985 project.task_store().read(cx).task_inventory().cloned(),
16986 worktree_id,
16987 file,
16988 )
16989 });
16990
16991 let tags = mem::take(&mut runnable.tags);
16992 let language = runnable.language.clone();
16993 cx.spawn(async move |cx| {
16994 let mut templates_with_tags = Vec::new();
16995 if let Some(inventory) = inventory {
16996 for RunnableTag(tag) in tags {
16997 let new_tasks = inventory.update(cx, |inventory, cx| {
16998 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
16999 });
17000 templates_with_tags.extend(new_tasks.await.into_iter().filter(
17001 move |(_, template)| {
17002 template.tags.iter().any(|source_tag| source_tag == &tag)
17003 },
17004 ));
17005 }
17006 }
17007 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
17008
17009 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
17010 // Strongest source wins; if we have worktree tag binding, prefer that to
17011 // global and language bindings;
17012 // if we have a global binding, prefer that to language binding.
17013 let first_mismatch = templates_with_tags
17014 .iter()
17015 .position(|(tag_source, _)| tag_source != leading_tag_source);
17016 if let Some(index) = first_mismatch {
17017 templates_with_tags.truncate(index);
17018 }
17019 }
17020
17021 templates_with_tags
17022 })
17023 }
17024
17025 pub fn move_to_enclosing_bracket(
17026 &mut self,
17027 _: &MoveToEnclosingBracket,
17028 window: &mut Window,
17029 cx: &mut Context<Self>,
17030 ) {
17031 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17032 self.change_selections(Default::default(), window, cx, |s| {
17033 s.move_offsets_with(|snapshot, selection| {
17034 let Some(enclosing_bracket_ranges) =
17035 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
17036 else {
17037 return;
17038 };
17039
17040 let mut best_length = usize::MAX;
17041 let mut best_inside = false;
17042 let mut best_in_bracket_range = false;
17043 let mut best_destination = None;
17044 for (open, close) in enclosing_bracket_ranges {
17045 let close = close.to_inclusive();
17046 let length = *close.end() - open.start;
17047 let inside = selection.start >= open.end && selection.end <= *close.start();
17048 let in_bracket_range = open.to_inclusive().contains(&selection.head())
17049 || close.contains(&selection.head());
17050
17051 // If best is next to a bracket and current isn't, skip
17052 if !in_bracket_range && best_in_bracket_range {
17053 continue;
17054 }
17055
17056 // Prefer smaller lengths unless best is inside and current isn't
17057 if length > best_length && (best_inside || !inside) {
17058 continue;
17059 }
17060
17061 best_length = length;
17062 best_inside = inside;
17063 best_in_bracket_range = in_bracket_range;
17064 best_destination = Some(
17065 if close.contains(&selection.start) && close.contains(&selection.end) {
17066 if inside { open.end } else { open.start }
17067 } else if inside {
17068 *close.start()
17069 } else {
17070 *close.end()
17071 },
17072 );
17073 }
17074
17075 if let Some(destination) = best_destination {
17076 selection.collapse_to(destination, SelectionGoal::None);
17077 }
17078 })
17079 });
17080 }
17081
17082 pub fn undo_selection(
17083 &mut self,
17084 _: &UndoSelection,
17085 window: &mut Window,
17086 cx: &mut Context<Self>,
17087 ) {
17088 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17089 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
17090 self.selection_history.mode = SelectionHistoryMode::Undoing;
17091 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17092 this.end_selection(window, cx);
17093 this.change_selections(
17094 SelectionEffects::scroll(Autoscroll::newest()),
17095 window,
17096 cx,
17097 |s| s.select_anchors(entry.selections.to_vec()),
17098 );
17099 });
17100 self.selection_history.mode = SelectionHistoryMode::Normal;
17101
17102 self.select_next_state = entry.select_next_state;
17103 self.select_prev_state = entry.select_prev_state;
17104 self.add_selections_state = entry.add_selections_state;
17105 }
17106 }
17107
17108 pub fn redo_selection(
17109 &mut self,
17110 _: &RedoSelection,
17111 window: &mut Window,
17112 cx: &mut Context<Self>,
17113 ) {
17114 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17115 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
17116 self.selection_history.mode = SelectionHistoryMode::Redoing;
17117 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17118 this.end_selection(window, cx);
17119 this.change_selections(
17120 SelectionEffects::scroll(Autoscroll::newest()),
17121 window,
17122 cx,
17123 |s| s.select_anchors(entry.selections.to_vec()),
17124 );
17125 });
17126 self.selection_history.mode = SelectionHistoryMode::Normal;
17127
17128 self.select_next_state = entry.select_next_state;
17129 self.select_prev_state = entry.select_prev_state;
17130 self.add_selections_state = entry.add_selections_state;
17131 }
17132 }
17133
17134 pub fn expand_excerpts(
17135 &mut self,
17136 action: &ExpandExcerpts,
17137 _: &mut Window,
17138 cx: &mut Context<Self>,
17139 ) {
17140 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
17141 }
17142
17143 pub fn expand_excerpts_down(
17144 &mut self,
17145 action: &ExpandExcerptsDown,
17146 _: &mut Window,
17147 cx: &mut Context<Self>,
17148 ) {
17149 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
17150 }
17151
17152 pub fn expand_excerpts_up(
17153 &mut self,
17154 action: &ExpandExcerptsUp,
17155 _: &mut Window,
17156 cx: &mut Context<Self>,
17157 ) {
17158 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
17159 }
17160
17161 pub fn expand_excerpts_for_direction(
17162 &mut self,
17163 lines: u32,
17164 direction: ExpandExcerptDirection,
17165 cx: &mut Context<Self>,
17166 ) {
17167 let selections = self.selections.disjoint_anchors_arc();
17168
17169 let lines = if lines == 0 {
17170 EditorSettings::get_global(cx).expand_excerpt_lines
17171 } else {
17172 lines
17173 };
17174
17175 let snapshot = self.buffer.read(cx).snapshot(cx);
17176 let mut excerpt_ids = selections
17177 .iter()
17178 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
17179 .collect::<Vec<_>>();
17180 excerpt_ids.sort();
17181 excerpt_ids.dedup();
17182
17183 if self.delegate_expand_excerpts {
17184 cx.emit(EditorEvent::ExpandExcerptsRequested {
17185 excerpt_ids,
17186 lines,
17187 direction,
17188 });
17189 return;
17190 }
17191
17192 self.buffer.update(cx, |buffer, cx| {
17193 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
17194 })
17195 }
17196
17197 pub fn expand_excerpt(
17198 &mut self,
17199 excerpt: ExcerptId,
17200 direction: ExpandExcerptDirection,
17201 window: &mut Window,
17202 cx: &mut Context<Self>,
17203 ) {
17204 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
17205
17206 if self.delegate_expand_excerpts {
17207 cx.emit(EditorEvent::ExpandExcerptsRequested {
17208 excerpt_ids: vec![excerpt],
17209 lines: lines_to_expand,
17210 direction,
17211 });
17212 return;
17213 }
17214
17215 let current_scroll_position = self.scroll_position(cx);
17216 let mut scroll = None;
17217
17218 if direction == ExpandExcerptDirection::Down {
17219 let multi_buffer = self.buffer.read(cx);
17220 let snapshot = multi_buffer.snapshot(cx);
17221 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
17222 && let Some(buffer) = multi_buffer.buffer(buffer_id)
17223 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
17224 {
17225 let buffer_snapshot = buffer.read(cx).snapshot();
17226 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
17227 let last_row = buffer_snapshot.max_point().row;
17228 let lines_below = last_row.saturating_sub(excerpt_end_row);
17229 if lines_below >= lines_to_expand {
17230 scroll = Some(
17231 current_scroll_position
17232 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
17233 );
17234 }
17235 }
17236 }
17237 if direction == ExpandExcerptDirection::Up
17238 && self
17239 .buffer
17240 .read(cx)
17241 .snapshot(cx)
17242 .excerpt_before(excerpt)
17243 .is_none()
17244 {
17245 scroll = Some(current_scroll_position);
17246 }
17247
17248 self.buffer.update(cx, |buffer, cx| {
17249 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
17250 });
17251
17252 if let Some(new_scroll_position) = scroll {
17253 self.set_scroll_position(new_scroll_position, window, cx);
17254 }
17255 }
17256
17257 pub fn go_to_singleton_buffer_point(
17258 &mut self,
17259 point: Point,
17260 window: &mut Window,
17261 cx: &mut Context<Self>,
17262 ) {
17263 self.go_to_singleton_buffer_range(point..point, window, cx);
17264 }
17265
17266 pub fn go_to_singleton_buffer_range(
17267 &mut self,
17268 range: Range<Point>,
17269 window: &mut Window,
17270 cx: &mut Context<Self>,
17271 ) {
17272 let multibuffer = self.buffer().read(cx);
17273 let Some(buffer) = multibuffer.as_singleton() else {
17274 return;
17275 };
17276 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
17277 return;
17278 };
17279 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
17280 return;
17281 };
17282 self.change_selections(
17283 SelectionEffects::default().nav_history(true),
17284 window,
17285 cx,
17286 |s| s.select_anchor_ranges([start..end]),
17287 );
17288 }
17289
17290 pub fn go_to_diagnostic(
17291 &mut self,
17292 action: &GoToDiagnostic,
17293 window: &mut Window,
17294 cx: &mut Context<Self>,
17295 ) {
17296 if !self.diagnostics_enabled() {
17297 return;
17298 }
17299 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17300 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
17301 }
17302
17303 pub fn go_to_prev_diagnostic(
17304 &mut self,
17305 action: &GoToPreviousDiagnostic,
17306 window: &mut Window,
17307 cx: &mut Context<Self>,
17308 ) {
17309 if !self.diagnostics_enabled() {
17310 return;
17311 }
17312 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17313 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
17314 }
17315
17316 pub fn go_to_diagnostic_impl(
17317 &mut self,
17318 direction: Direction,
17319 severity: GoToDiagnosticSeverityFilter,
17320 window: &mut Window,
17321 cx: &mut Context<Self>,
17322 ) {
17323 let buffer = self.buffer.read(cx).snapshot(cx);
17324 let selection = self
17325 .selections
17326 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17327
17328 let mut active_group_id = None;
17329 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
17330 && active_group.active_range.start.to_offset(&buffer) == selection.start
17331 {
17332 active_group_id = Some(active_group.group_id);
17333 }
17334
17335 fn filtered<'a>(
17336 severity: GoToDiagnosticSeverityFilter,
17337 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
17338 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
17339 diagnostics
17340 .filter(move |entry| severity.matches(entry.diagnostic.severity))
17341 .filter(|entry| entry.range.start != entry.range.end)
17342 .filter(|entry| !entry.diagnostic.is_unnecessary)
17343 }
17344
17345 let before = filtered(
17346 severity,
17347 buffer
17348 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
17349 .filter(|entry| entry.range.start <= selection.start),
17350 );
17351 let after = filtered(
17352 severity,
17353 buffer
17354 .diagnostics_in_range(selection.start..buffer.len())
17355 .filter(|entry| entry.range.start >= selection.start),
17356 );
17357
17358 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
17359 if direction == Direction::Prev {
17360 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
17361 {
17362 for diagnostic in prev_diagnostics.into_iter().rev() {
17363 if diagnostic.range.start != selection.start
17364 || active_group_id
17365 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
17366 {
17367 found = Some(diagnostic);
17368 break 'outer;
17369 }
17370 }
17371 }
17372 } else {
17373 for diagnostic in after.chain(before) {
17374 if diagnostic.range.start != selection.start
17375 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
17376 {
17377 found = Some(diagnostic);
17378 break;
17379 }
17380 }
17381 }
17382 let Some(next_diagnostic) = found else {
17383 return;
17384 };
17385
17386 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
17387 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
17388 return;
17389 };
17390 let snapshot = self.snapshot(window, cx);
17391 if snapshot.intersects_fold(next_diagnostic.range.start) {
17392 self.unfold_ranges(
17393 std::slice::from_ref(&next_diagnostic.range),
17394 true,
17395 false,
17396 cx,
17397 );
17398 }
17399 self.change_selections(Default::default(), window, cx, |s| {
17400 s.select_ranges(vec![
17401 next_diagnostic.range.start..next_diagnostic.range.start,
17402 ])
17403 });
17404 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
17405 self.refresh_edit_prediction(false, true, window, cx);
17406 }
17407
17408 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
17409 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17410 let snapshot = self.snapshot(window, cx);
17411 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
17412 self.go_to_hunk_before_or_after_position(
17413 &snapshot,
17414 selection.head(),
17415 Direction::Next,
17416 window,
17417 cx,
17418 );
17419 }
17420
17421 pub fn go_to_hunk_before_or_after_position(
17422 &mut self,
17423 snapshot: &EditorSnapshot,
17424 position: Point,
17425 direction: Direction,
17426 window: &mut Window,
17427 cx: &mut Context<Editor>,
17428 ) {
17429 let row = if direction == Direction::Next {
17430 self.hunk_after_position(snapshot, position)
17431 .map(|hunk| hunk.row_range.start)
17432 } else {
17433 self.hunk_before_position(snapshot, position)
17434 };
17435
17436 if let Some(row) = row {
17437 let destination = Point::new(row.0, 0);
17438 let autoscroll = Autoscroll::center();
17439
17440 self.unfold_ranges(&[destination..destination], false, false, cx);
17441 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17442 s.select_ranges([destination..destination]);
17443 });
17444 }
17445 }
17446
17447 fn hunk_after_position(
17448 &mut self,
17449 snapshot: &EditorSnapshot,
17450 position: Point,
17451 ) -> Option<MultiBufferDiffHunk> {
17452 snapshot
17453 .buffer_snapshot()
17454 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
17455 .find(|hunk| hunk.row_range.start.0 > position.row)
17456 .or_else(|| {
17457 snapshot
17458 .buffer_snapshot()
17459 .diff_hunks_in_range(Point::zero()..position)
17460 .find(|hunk| hunk.row_range.end.0 < position.row)
17461 })
17462 }
17463
17464 fn go_to_prev_hunk(
17465 &mut self,
17466 _: &GoToPreviousHunk,
17467 window: &mut Window,
17468 cx: &mut Context<Self>,
17469 ) {
17470 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17471 let snapshot = self.snapshot(window, cx);
17472 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
17473 self.go_to_hunk_before_or_after_position(
17474 &snapshot,
17475 selection.head(),
17476 Direction::Prev,
17477 window,
17478 cx,
17479 );
17480 }
17481
17482 fn hunk_before_position(
17483 &mut self,
17484 snapshot: &EditorSnapshot,
17485 position: Point,
17486 ) -> Option<MultiBufferRow> {
17487 snapshot
17488 .buffer_snapshot()
17489 .diff_hunk_before(position)
17490 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
17491 }
17492
17493 fn go_to_next_change(
17494 &mut self,
17495 _: &GoToNextChange,
17496 window: &mut Window,
17497 cx: &mut Context<Self>,
17498 ) {
17499 if let Some(selections) = self
17500 .change_list
17501 .next_change(1, Direction::Next)
17502 .map(|s| s.to_vec())
17503 {
17504 self.change_selections(Default::default(), window, cx, |s| {
17505 let map = s.display_snapshot();
17506 s.select_display_ranges(selections.iter().map(|a| {
17507 let point = a.to_display_point(&map);
17508 point..point
17509 }))
17510 })
17511 }
17512 }
17513
17514 fn go_to_previous_change(
17515 &mut self,
17516 _: &GoToPreviousChange,
17517 window: &mut Window,
17518 cx: &mut Context<Self>,
17519 ) {
17520 if let Some(selections) = self
17521 .change_list
17522 .next_change(1, Direction::Prev)
17523 .map(|s| s.to_vec())
17524 {
17525 self.change_selections(Default::default(), window, cx, |s| {
17526 let map = s.display_snapshot();
17527 s.select_display_ranges(selections.iter().map(|a| {
17528 let point = a.to_display_point(&map);
17529 point..point
17530 }))
17531 })
17532 }
17533 }
17534
17535 pub fn go_to_next_document_highlight(
17536 &mut self,
17537 _: &GoToNextDocumentHighlight,
17538 window: &mut Window,
17539 cx: &mut Context<Self>,
17540 ) {
17541 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
17542 }
17543
17544 pub fn go_to_prev_document_highlight(
17545 &mut self,
17546 _: &GoToPreviousDocumentHighlight,
17547 window: &mut Window,
17548 cx: &mut Context<Self>,
17549 ) {
17550 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
17551 }
17552
17553 pub fn go_to_document_highlight_before_or_after_position(
17554 &mut self,
17555 direction: Direction,
17556 window: &mut Window,
17557 cx: &mut Context<Editor>,
17558 ) {
17559 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17560 let snapshot = self.snapshot(window, cx);
17561 let buffer = &snapshot.buffer_snapshot();
17562 let position = self
17563 .selections
17564 .newest::<Point>(&snapshot.display_snapshot)
17565 .head();
17566 let anchor_position = buffer.anchor_after(position);
17567
17568 // Get all document highlights (both read and write)
17569 let mut all_highlights = Vec::new();
17570
17571 if let Some((_, read_highlights)) = self
17572 .background_highlights
17573 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
17574 {
17575 all_highlights.extend(read_highlights.iter());
17576 }
17577
17578 if let Some((_, write_highlights)) = self
17579 .background_highlights
17580 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
17581 {
17582 all_highlights.extend(write_highlights.iter());
17583 }
17584
17585 if all_highlights.is_empty() {
17586 return;
17587 }
17588
17589 // Sort highlights by position
17590 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17591
17592 let target_highlight = match direction {
17593 Direction::Next => {
17594 // Find the first highlight after the current position
17595 all_highlights
17596 .iter()
17597 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17598 }
17599 Direction::Prev => {
17600 // Find the last highlight before the current position
17601 all_highlights
17602 .iter()
17603 .rev()
17604 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17605 }
17606 };
17607
17608 if let Some(highlight) = target_highlight {
17609 let destination = highlight.start.to_point(buffer);
17610 let autoscroll = Autoscroll::center();
17611
17612 self.unfold_ranges(&[destination..destination], false, false, cx);
17613 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17614 s.select_ranges([destination..destination]);
17615 });
17616 }
17617 }
17618
17619 fn go_to_line<T: 'static>(
17620 &mut self,
17621 position: Anchor,
17622 highlight_color: Option<Hsla>,
17623 window: &mut Window,
17624 cx: &mut Context<Self>,
17625 ) {
17626 let snapshot = self.snapshot(window, cx).display_snapshot;
17627 let position = position.to_point(&snapshot.buffer_snapshot());
17628 let start = snapshot
17629 .buffer_snapshot()
17630 .clip_point(Point::new(position.row, 0), Bias::Left);
17631 let end = start + Point::new(1, 0);
17632 let start = snapshot.buffer_snapshot().anchor_before(start);
17633 let end = snapshot.buffer_snapshot().anchor_before(end);
17634
17635 self.highlight_rows::<T>(
17636 start..end,
17637 highlight_color
17638 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
17639 Default::default(),
17640 cx,
17641 );
17642
17643 if self.buffer.read(cx).is_singleton() {
17644 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
17645 }
17646 }
17647
17648 pub fn go_to_definition(
17649 &mut self,
17650 _: &GoToDefinition,
17651 window: &mut Window,
17652 cx: &mut Context<Self>,
17653 ) -> Task<Result<Navigated>> {
17654 let definition =
17655 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
17656 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
17657 cx.spawn_in(window, async move |editor, cx| {
17658 if definition.await? == Navigated::Yes {
17659 return Ok(Navigated::Yes);
17660 }
17661 match fallback_strategy {
17662 GoToDefinitionFallback::None => Ok(Navigated::No),
17663 GoToDefinitionFallback::FindAllReferences => {
17664 match editor.update_in(cx, |editor, window, cx| {
17665 editor.find_all_references(&FindAllReferences::default(), window, cx)
17666 })? {
17667 Some(references) => references.await,
17668 None => Ok(Navigated::No),
17669 }
17670 }
17671 }
17672 })
17673 }
17674
17675 pub fn go_to_declaration(
17676 &mut self,
17677 _: &GoToDeclaration,
17678 window: &mut Window,
17679 cx: &mut Context<Self>,
17680 ) -> Task<Result<Navigated>> {
17681 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
17682 }
17683
17684 pub fn go_to_declaration_split(
17685 &mut self,
17686 _: &GoToDeclaration,
17687 window: &mut Window,
17688 cx: &mut Context<Self>,
17689 ) -> Task<Result<Navigated>> {
17690 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
17691 }
17692
17693 pub fn go_to_implementation(
17694 &mut self,
17695 _: &GoToImplementation,
17696 window: &mut Window,
17697 cx: &mut Context<Self>,
17698 ) -> Task<Result<Navigated>> {
17699 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
17700 }
17701
17702 pub fn go_to_implementation_split(
17703 &mut self,
17704 _: &GoToImplementationSplit,
17705 window: &mut Window,
17706 cx: &mut Context<Self>,
17707 ) -> Task<Result<Navigated>> {
17708 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
17709 }
17710
17711 pub fn go_to_type_definition(
17712 &mut self,
17713 _: &GoToTypeDefinition,
17714 window: &mut Window,
17715 cx: &mut Context<Self>,
17716 ) -> Task<Result<Navigated>> {
17717 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
17718 }
17719
17720 pub fn go_to_definition_split(
17721 &mut self,
17722 _: &GoToDefinitionSplit,
17723 window: &mut Window,
17724 cx: &mut Context<Self>,
17725 ) -> Task<Result<Navigated>> {
17726 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
17727 }
17728
17729 pub fn go_to_type_definition_split(
17730 &mut self,
17731 _: &GoToTypeDefinitionSplit,
17732 window: &mut Window,
17733 cx: &mut Context<Self>,
17734 ) -> Task<Result<Navigated>> {
17735 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
17736 }
17737
17738 fn go_to_definition_of_kind(
17739 &mut self,
17740 kind: GotoDefinitionKind,
17741 split: bool,
17742 window: &mut Window,
17743 cx: &mut Context<Self>,
17744 ) -> Task<Result<Navigated>> {
17745 let Some(provider) = self.semantics_provider.clone() else {
17746 return Task::ready(Ok(Navigated::No));
17747 };
17748 let head = self
17749 .selections
17750 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
17751 .head();
17752 let buffer = self.buffer.read(cx);
17753 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
17754 return Task::ready(Ok(Navigated::No));
17755 };
17756 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
17757 return Task::ready(Ok(Navigated::No));
17758 };
17759
17760 let nav_entry = self.navigation_entry(self.selections.newest_anchor().head(), cx);
17761
17762 cx.spawn_in(window, async move |editor, cx| {
17763 let Some(definitions) = definitions.await? else {
17764 return Ok(Navigated::No);
17765 };
17766 let navigated = editor
17767 .update_in(cx, |editor, window, cx| {
17768 editor.navigate_to_hover_links(
17769 Some(kind),
17770 definitions
17771 .into_iter()
17772 .filter(|location| {
17773 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
17774 })
17775 .map(HoverLink::Text)
17776 .collect::<Vec<_>>(),
17777 nav_entry,
17778 split,
17779 window,
17780 cx,
17781 )
17782 })?
17783 .await?;
17784 anyhow::Ok(navigated)
17785 })
17786 }
17787
17788 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
17789 let selection = self.selections.newest_anchor();
17790 let head = selection.head();
17791 let tail = selection.tail();
17792
17793 let Some((buffer, start_position)) =
17794 self.buffer.read(cx).text_anchor_for_position(head, cx)
17795 else {
17796 return;
17797 };
17798
17799 let end_position = if head != tail {
17800 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
17801 return;
17802 };
17803 Some(pos)
17804 } else {
17805 None
17806 };
17807
17808 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
17809 let url = if let Some(end_pos) = end_position {
17810 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
17811 } else {
17812 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
17813 };
17814
17815 if let Some(url) = url {
17816 cx.update(|window, cx| {
17817 if parse_zed_link(&url, cx).is_some() {
17818 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17819 } else {
17820 cx.open_url(&url);
17821 }
17822 })?;
17823 }
17824
17825 anyhow::Ok(())
17826 });
17827
17828 url_finder.detach();
17829 }
17830
17831 pub fn open_selected_filename(
17832 &mut self,
17833 _: &OpenSelectedFilename,
17834 window: &mut Window,
17835 cx: &mut Context<Self>,
17836 ) {
17837 let Some(workspace) = self.workspace() else {
17838 return;
17839 };
17840
17841 let position = self.selections.newest_anchor().head();
17842
17843 let Some((buffer, buffer_position)) =
17844 self.buffer.read(cx).text_anchor_for_position(position, cx)
17845 else {
17846 return;
17847 };
17848
17849 let project = self.project.clone();
17850
17851 cx.spawn_in(window, async move |_, cx| {
17852 let result = find_file(&buffer, project, buffer_position, cx).await;
17853
17854 if let Some((_, path)) = result {
17855 workspace
17856 .update_in(cx, |workspace, window, cx| {
17857 workspace.open_resolved_path(path, window, cx)
17858 })?
17859 .await?;
17860 }
17861 anyhow::Ok(())
17862 })
17863 .detach();
17864 }
17865
17866 pub(crate) fn navigate_to_hover_links(
17867 &mut self,
17868 kind: Option<GotoDefinitionKind>,
17869 definitions: Vec<HoverLink>,
17870 origin: Option<NavigationEntry>,
17871 split: bool,
17872 window: &mut Window,
17873 cx: &mut Context<Editor>,
17874 ) -> Task<Result<Navigated>> {
17875 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
17876 let mut first_url_or_file = None;
17877 let definitions: Vec<_> = definitions
17878 .into_iter()
17879 .filter_map(|def| match def {
17880 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
17881 HoverLink::InlayHint(lsp_location, server_id) => {
17882 let computation =
17883 self.compute_target_location(lsp_location, server_id, window, cx);
17884 Some(cx.background_spawn(computation))
17885 }
17886 HoverLink::Url(url) => {
17887 first_url_or_file = Some(Either::Left(url));
17888 None
17889 }
17890 HoverLink::File(path) => {
17891 first_url_or_file = Some(Either::Right(path));
17892 None
17893 }
17894 })
17895 .collect();
17896
17897 let workspace = self.workspace();
17898
17899 cx.spawn_in(window, async move |editor, cx| {
17900 let locations: Vec<Location> = future::join_all(definitions)
17901 .await
17902 .into_iter()
17903 .filter_map(|location| location.transpose())
17904 .collect::<Result<_>>()
17905 .context("location tasks")?;
17906 let mut locations = cx.update(|_, cx| {
17907 locations
17908 .into_iter()
17909 .map(|location| {
17910 let buffer = location.buffer.read(cx);
17911 (location.buffer, location.range.to_point(buffer))
17912 })
17913 .into_group_map()
17914 })?;
17915 let mut num_locations = 0;
17916 for ranges in locations.values_mut() {
17917 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17918 ranges.dedup();
17919 num_locations += ranges.len();
17920 }
17921
17922 if num_locations > 1 {
17923 let tab_kind = match kind {
17924 Some(GotoDefinitionKind::Implementation) => "Implementations",
17925 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
17926 Some(GotoDefinitionKind::Declaration) => "Declarations",
17927 Some(GotoDefinitionKind::Type) => "Types",
17928 };
17929 let title = editor
17930 .update_in(cx, |_, _, cx| {
17931 let target = locations
17932 .iter()
17933 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17934 .map(|(buffer, location)| {
17935 buffer
17936 .read(cx)
17937 .text_for_range(location.clone())
17938 .collect::<String>()
17939 })
17940 .filter(|text| !text.contains('\n'))
17941 .unique()
17942 .take(3)
17943 .join(", ");
17944 if target.is_empty() {
17945 tab_kind.to_owned()
17946 } else {
17947 format!("{tab_kind} for {target}")
17948 }
17949 })
17950 .context("buffer title")?;
17951
17952 let Some(workspace) = workspace else {
17953 return Ok(Navigated::No);
17954 };
17955
17956 let opened = workspace
17957 .update_in(cx, |workspace, window, cx| {
17958 let allow_preview = PreviewTabsSettings::get_global(cx)
17959 .enable_preview_multibuffer_from_code_navigation;
17960 if let Some((target_editor, target_pane)) =
17961 Self::open_locations_in_multibuffer(
17962 workspace,
17963 locations,
17964 title,
17965 split,
17966 allow_preview,
17967 MultibufferSelectionMode::First,
17968 window,
17969 cx,
17970 )
17971 {
17972 // We create our own nav history instead of using
17973 // `target_editor.nav_history` because `nav_history`
17974 // seems to be populated asynchronously when an item
17975 // is added to a pane
17976 let mut nav_history = target_pane
17977 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
17978 target_editor.update(cx, |editor, cx| {
17979 let nav_data = editor
17980 .navigation_data(editor.selections.newest_anchor().head(), cx);
17981 let target =
17982 Some(nav_history.navigation_entry(Some(
17983 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
17984 )));
17985 nav_history.push_tag(origin, target);
17986 })
17987 }
17988 })
17989 .is_ok();
17990
17991 anyhow::Ok(Navigated::from_bool(opened))
17992 } else if num_locations == 0 {
17993 // If there is one url or file, open it directly
17994 match first_url_or_file {
17995 Some(Either::Left(url)) => {
17996 cx.update(|window, cx| {
17997 if parse_zed_link(&url, cx).is_some() {
17998 window
17999 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
18000 } else {
18001 cx.open_url(&url);
18002 }
18003 })?;
18004 Ok(Navigated::Yes)
18005 }
18006 Some(Either::Right(path)) => {
18007 // TODO(andrew): respect preview tab settings
18008 // `enable_keep_preview_on_code_navigation` and
18009 // `enable_preview_file_from_code_navigation`
18010 let Some(workspace) = workspace else {
18011 return Ok(Navigated::No);
18012 };
18013 workspace
18014 .update_in(cx, |workspace, window, cx| {
18015 workspace.open_resolved_path(path, window, cx)
18016 })?
18017 .await?;
18018 Ok(Navigated::Yes)
18019 }
18020 None => Ok(Navigated::No),
18021 }
18022 } else {
18023 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18024 let target_range = target_ranges.first().unwrap().clone();
18025
18026 editor.update_in(cx, |editor, window, cx| {
18027 let range = editor.range_for_match(&target_range);
18028 let range = collapse_multiline_range(range);
18029
18030 if !split
18031 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
18032 {
18033 editor.go_to_singleton_buffer_range(range, window, cx);
18034
18035 let target =
18036 editor.navigation_entry(editor.selections.newest_anchor().head(), cx);
18037 if let Some(mut nav_history) = editor.nav_history.clone() {
18038 nav_history.push_tag(origin, target);
18039 }
18040 } else {
18041 let Some(workspace) = workspace else {
18042 return Navigated::No;
18043 };
18044 let pane = workspace.read(cx).active_pane().clone();
18045 window.defer(cx, move |window, cx| {
18046 let (target_editor, target_pane): (Entity<Self>, Entity<Pane>) =
18047 workspace.update(cx, |workspace, cx| {
18048 let pane = if split {
18049 workspace.adjacent_pane(window, cx)
18050 } else {
18051 workspace.active_pane().clone()
18052 };
18053
18054 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18055 let keep_old_preview = preview_tabs_settings
18056 .enable_keep_preview_on_code_navigation;
18057 let allow_new_preview = preview_tabs_settings
18058 .enable_preview_file_from_code_navigation;
18059
18060 let editor = workspace.open_project_item(
18061 pane.clone(),
18062 target_buffer.clone(),
18063 true,
18064 true,
18065 keep_old_preview,
18066 allow_new_preview,
18067 window,
18068 cx,
18069 );
18070 (editor, pane)
18071 });
18072 // We create our own nav history instead of using
18073 // `target_editor.nav_history` because `nav_history`
18074 // seems to be populated asynchronously when an item
18075 // is added to a pane
18076 let mut nav_history = target_pane
18077 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18078 target_editor.update(cx, |target_editor, cx| {
18079 // When selecting a definition in a different buffer, disable the nav history
18080 // to avoid creating a history entry at the previous cursor location.
18081 pane.update(cx, |pane, _| pane.disable_history());
18082 target_editor.go_to_singleton_buffer_range(range, window, cx);
18083
18084 let nav_data = target_editor.navigation_data(
18085 target_editor.selections.newest_anchor().head(),
18086 cx,
18087 );
18088 let target =
18089 Some(nav_history.navigation_entry(Some(
18090 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18091 )));
18092 nav_history.push_tag(origin, target);
18093 pane.update(cx, |pane, _| pane.enable_history());
18094 });
18095 });
18096 }
18097 Navigated::Yes
18098 })
18099 }
18100 })
18101 }
18102
18103 fn compute_target_location(
18104 &self,
18105 lsp_location: lsp::Location,
18106 server_id: LanguageServerId,
18107 window: &mut Window,
18108 cx: &mut Context<Self>,
18109 ) -> Task<anyhow::Result<Option<Location>>> {
18110 let Some(project) = self.project.clone() else {
18111 return Task::ready(Ok(None));
18112 };
18113
18114 cx.spawn_in(window, async move |editor, cx| {
18115 let location_task = editor.update(cx, |_, cx| {
18116 project.update(cx, |project, cx| {
18117 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
18118 })
18119 })?;
18120 let location = Some({
18121 let target_buffer_handle = location_task.await.context("open local buffer")?;
18122 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
18123 let target_start = target_buffer
18124 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
18125 let target_end = target_buffer
18126 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
18127 target_buffer.anchor_after(target_start)
18128 ..target_buffer.anchor_before(target_end)
18129 });
18130 Location {
18131 buffer: target_buffer_handle,
18132 range,
18133 }
18134 });
18135 Ok(location)
18136 })
18137 }
18138
18139 fn go_to_next_reference(
18140 &mut self,
18141 _: &GoToNextReference,
18142 window: &mut Window,
18143 cx: &mut Context<Self>,
18144 ) {
18145 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
18146 if let Some(task) = task {
18147 task.detach();
18148 };
18149 }
18150
18151 fn go_to_prev_reference(
18152 &mut self,
18153 _: &GoToPreviousReference,
18154 window: &mut Window,
18155 cx: &mut Context<Self>,
18156 ) {
18157 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
18158 if let Some(task) = task {
18159 task.detach();
18160 };
18161 }
18162
18163 pub fn go_to_reference_before_or_after_position(
18164 &mut self,
18165 direction: Direction,
18166 count: usize,
18167 window: &mut Window,
18168 cx: &mut Context<Self>,
18169 ) -> Option<Task<Result<()>>> {
18170 let selection = self.selections.newest_anchor();
18171 let head = selection.head();
18172
18173 let multi_buffer = self.buffer.read(cx);
18174
18175 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
18176 let workspace = self.workspace()?;
18177 let project = workspace.read(cx).project().clone();
18178 let references =
18179 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
18180 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
18181 let Some(locations) = references.await? else {
18182 return Ok(());
18183 };
18184
18185 if locations.is_empty() {
18186 // totally normal - the cursor may be on something which is not
18187 // a symbol (e.g. a keyword)
18188 log::info!("no references found under cursor");
18189 return Ok(());
18190 }
18191
18192 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
18193
18194 let (locations, current_location_index) =
18195 multi_buffer.update(cx, |multi_buffer, cx| {
18196 let mut locations = locations
18197 .into_iter()
18198 .filter_map(|loc| {
18199 let start = multi_buffer.buffer_anchor_to_anchor(
18200 &loc.buffer,
18201 loc.range.start,
18202 cx,
18203 )?;
18204 let end = multi_buffer.buffer_anchor_to_anchor(
18205 &loc.buffer,
18206 loc.range.end,
18207 cx,
18208 )?;
18209 Some(start..end)
18210 })
18211 .collect::<Vec<_>>();
18212
18213 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18214 // There is an O(n) implementation, but given this list will be
18215 // small (usually <100 items), the extra O(log(n)) factor isn't
18216 // worth the (surprisingly large amount of) extra complexity.
18217 locations
18218 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
18219
18220 let head_offset = head.to_offset(&multi_buffer_snapshot);
18221
18222 let current_location_index = locations.iter().position(|loc| {
18223 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
18224 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
18225 });
18226
18227 (locations, current_location_index)
18228 });
18229
18230 let Some(current_location_index) = current_location_index else {
18231 // This indicates something has gone wrong, because we already
18232 // handle the "no references" case above
18233 log::error!(
18234 "failed to find current reference under cursor. Total references: {}",
18235 locations.len()
18236 );
18237 return Ok(());
18238 };
18239
18240 let destination_location_index = match direction {
18241 Direction::Next => (current_location_index + count) % locations.len(),
18242 Direction::Prev => {
18243 (current_location_index + locations.len() - count % locations.len())
18244 % locations.len()
18245 }
18246 };
18247
18248 // TODO(cameron): is this needed?
18249 // the thinking is to avoid "jumping to the current location" (avoid
18250 // polluting "jumplist" in vim terms)
18251 if current_location_index == destination_location_index {
18252 return Ok(());
18253 }
18254
18255 let Range { start, end } = locations[destination_location_index];
18256
18257 editor.update_in(cx, |editor, window, cx| {
18258 let effects = SelectionEffects::default();
18259
18260 editor.unfold_ranges(&[start..end], false, false, cx);
18261 editor.change_selections(effects, window, cx, |s| {
18262 s.select_ranges([start..start]);
18263 });
18264 })?;
18265
18266 Ok(())
18267 }))
18268 }
18269
18270 pub fn find_all_references(
18271 &mut self,
18272 action: &FindAllReferences,
18273 window: &mut Window,
18274 cx: &mut Context<Self>,
18275 ) -> Option<Task<Result<Navigated>>> {
18276 let always_open_multibuffer = action.always_open_multibuffer;
18277 let selection = self.selections.newest_anchor();
18278 let multi_buffer = self.buffer.read(cx);
18279 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18280 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
18281 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
18282 let head = selection_offset.head();
18283
18284 let head_anchor = multi_buffer_snapshot.anchor_at(
18285 head,
18286 if head < selection_offset.tail() {
18287 Bias::Right
18288 } else {
18289 Bias::Left
18290 },
18291 );
18292
18293 match self
18294 .find_all_references_task_sources
18295 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18296 {
18297 Ok(_) => {
18298 log::info!(
18299 "Ignoring repeated FindAllReferences invocation with the position of already running task"
18300 );
18301 return None;
18302 }
18303 Err(i) => {
18304 self.find_all_references_task_sources.insert(i, head_anchor);
18305 }
18306 }
18307
18308 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
18309 let workspace = self.workspace()?;
18310 let project = workspace.read(cx).project().clone();
18311 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
18312 Some(cx.spawn_in(window, async move |editor, cx| {
18313 let _cleanup = cx.on_drop(&editor, move |editor, _| {
18314 if let Ok(i) = editor
18315 .find_all_references_task_sources
18316 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18317 {
18318 editor.find_all_references_task_sources.remove(i);
18319 }
18320 });
18321
18322 let Some(locations) = references.await? else {
18323 return anyhow::Ok(Navigated::No);
18324 };
18325 let mut locations = cx.update(|_, cx| {
18326 locations
18327 .into_iter()
18328 .map(|location| {
18329 let buffer = location.buffer.read(cx);
18330 (location.buffer, location.range.to_point(buffer))
18331 })
18332 // if special-casing the single-match case, remove ranges
18333 // that intersect current selection
18334 .filter(|(location_buffer, location)| {
18335 if always_open_multibuffer || &buffer != location_buffer {
18336 return true;
18337 }
18338
18339 !location.contains_inclusive(&selection_point.range())
18340 })
18341 .into_group_map()
18342 })?;
18343 if locations.is_empty() {
18344 return anyhow::Ok(Navigated::No);
18345 }
18346 for ranges in locations.values_mut() {
18347 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18348 ranges.dedup();
18349 }
18350 let mut num_locations = 0;
18351 for ranges in locations.values_mut() {
18352 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18353 ranges.dedup();
18354 num_locations += ranges.len();
18355 }
18356
18357 if num_locations == 1 && !always_open_multibuffer {
18358 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18359 let target_range = target_ranges.first().unwrap().clone();
18360
18361 return editor.update_in(cx, |editor, window, cx| {
18362 let range = target_range.to_point(target_buffer.read(cx));
18363 let range = editor.range_for_match(&range);
18364 let range = range.start..range.start;
18365
18366 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
18367 editor.go_to_singleton_buffer_range(range, window, cx);
18368 } else {
18369 let pane = workspace.read(cx).active_pane().clone();
18370 window.defer(cx, move |window, cx| {
18371 let target_editor: Entity<Self> =
18372 workspace.update(cx, |workspace, cx| {
18373 let pane = workspace.active_pane().clone();
18374
18375 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18376 let keep_old_preview = preview_tabs_settings
18377 .enable_keep_preview_on_code_navigation;
18378 let allow_new_preview = preview_tabs_settings
18379 .enable_preview_file_from_code_navigation;
18380
18381 workspace.open_project_item(
18382 pane,
18383 target_buffer.clone(),
18384 true,
18385 true,
18386 keep_old_preview,
18387 allow_new_preview,
18388 window,
18389 cx,
18390 )
18391 });
18392 target_editor.update(cx, |target_editor, cx| {
18393 // When selecting a definition in a different buffer, disable the nav history
18394 // to avoid creating a history entry at the previous cursor location.
18395 pane.update(cx, |pane, _| pane.disable_history());
18396 target_editor.go_to_singleton_buffer_range(range, window, cx);
18397 pane.update(cx, |pane, _| pane.enable_history());
18398 });
18399 });
18400 }
18401 Navigated::No
18402 });
18403 }
18404
18405 workspace.update_in(cx, |workspace, window, cx| {
18406 let target = locations
18407 .iter()
18408 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18409 .map(|(buffer, location)| {
18410 buffer
18411 .read(cx)
18412 .text_for_range(location.clone())
18413 .collect::<String>()
18414 })
18415 .filter(|text| !text.contains('\n'))
18416 .unique()
18417 .take(3)
18418 .join(", ");
18419 let title = if target.is_empty() {
18420 "References".to_owned()
18421 } else {
18422 format!("References to {target}")
18423 };
18424 let allow_preview = PreviewTabsSettings::get_global(cx)
18425 .enable_preview_multibuffer_from_code_navigation;
18426 Self::open_locations_in_multibuffer(
18427 workspace,
18428 locations,
18429 title,
18430 false,
18431 allow_preview,
18432 MultibufferSelectionMode::First,
18433 window,
18434 cx,
18435 );
18436 Navigated::Yes
18437 })
18438 }))
18439 }
18440
18441 /// Opens a multibuffer with the given project locations in it.
18442 pub fn open_locations_in_multibuffer(
18443 workspace: &mut Workspace,
18444 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
18445 title: String,
18446 split: bool,
18447 allow_preview: bool,
18448 multibuffer_selection_mode: MultibufferSelectionMode,
18449 window: &mut Window,
18450 cx: &mut Context<Workspace>,
18451 ) -> Option<(Entity<Editor>, Entity<Pane>)> {
18452 if locations.is_empty() {
18453 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
18454 return None;
18455 }
18456
18457 let capability = workspace.project().read(cx).capability();
18458 let mut ranges = <Vec<Range<Anchor>>>::new();
18459
18460 // a key to find existing multibuffer editors with the same set of locations
18461 // to prevent us from opening more and more multibuffer tabs for searches and the like
18462 let mut key = (title.clone(), vec![]);
18463 let excerpt_buffer = cx.new(|cx| {
18464 let key = &mut key.1;
18465 let mut multibuffer = MultiBuffer::new(capability);
18466 for (buffer, mut ranges_for_buffer) in locations {
18467 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
18468 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
18469 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
18470 PathKey::for_buffer(&buffer, cx),
18471 buffer.clone(),
18472 ranges_for_buffer,
18473 multibuffer_context_lines(cx),
18474 cx,
18475 );
18476 ranges.extend(new_ranges)
18477 }
18478
18479 multibuffer.with_title(title)
18480 });
18481 let existing = workspace.active_pane().update(cx, |pane, cx| {
18482 pane.items()
18483 .filter_map(|item| item.downcast::<Editor>())
18484 .find(|editor| {
18485 editor
18486 .read(cx)
18487 .lookup_key
18488 .as_ref()
18489 .and_then(|it| {
18490 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
18491 })
18492 .is_some_and(|it| *it == key)
18493 })
18494 });
18495 let was_existing = existing.is_some();
18496 let editor = existing.unwrap_or_else(|| {
18497 cx.new(|cx| {
18498 let mut editor = Editor::for_multibuffer(
18499 excerpt_buffer,
18500 Some(workspace.project().clone()),
18501 window,
18502 cx,
18503 );
18504 editor.lookup_key = Some(Box::new(key));
18505 editor
18506 })
18507 });
18508 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
18509 MultibufferSelectionMode::First => {
18510 if let Some(first_range) = ranges.first() {
18511 editor.change_selections(
18512 SelectionEffects::no_scroll(),
18513 window,
18514 cx,
18515 |selections| {
18516 selections.clear_disjoint();
18517 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
18518 },
18519 );
18520 }
18521 editor.highlight_background::<Self>(
18522 &ranges,
18523 |_, theme| theme.colors().editor_highlighted_line_background,
18524 cx,
18525 );
18526 }
18527 MultibufferSelectionMode::All => {
18528 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
18529 selections.clear_disjoint();
18530 selections.select_anchor_ranges(ranges);
18531 });
18532 }
18533 });
18534
18535 let item = Box::new(editor.clone());
18536
18537 let pane = if split {
18538 workspace.adjacent_pane(window, cx)
18539 } else {
18540 workspace.active_pane().clone()
18541 };
18542 let activate_pane = split;
18543
18544 let mut destination_index = None;
18545 pane.update(cx, |pane, cx| {
18546 if allow_preview && !was_existing {
18547 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
18548 }
18549 if was_existing && !allow_preview {
18550 pane.unpreview_item_if_preview(item.item_id());
18551 }
18552 pane.add_item(item, activate_pane, true, destination_index, window, cx);
18553 });
18554
18555 Some((editor, pane))
18556 }
18557
18558 pub fn rename(
18559 &mut self,
18560 _: &Rename,
18561 window: &mut Window,
18562 cx: &mut Context<Self>,
18563 ) -> Option<Task<Result<()>>> {
18564 use language::ToOffset as _;
18565
18566 let provider = self.semantics_provider.clone()?;
18567 let selection = self.selections.newest_anchor().clone();
18568 let (cursor_buffer, cursor_buffer_position) = self
18569 .buffer
18570 .read(cx)
18571 .text_anchor_for_position(selection.head(), cx)?;
18572 let (tail_buffer, cursor_buffer_position_end) = self
18573 .buffer
18574 .read(cx)
18575 .text_anchor_for_position(selection.tail(), cx)?;
18576 if tail_buffer != cursor_buffer {
18577 return None;
18578 }
18579
18580 let snapshot = cursor_buffer.read(cx).snapshot();
18581 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
18582 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
18583 let prepare_rename = provider
18584 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
18585 .unwrap_or_else(|| Task::ready(Ok(None)));
18586 drop(snapshot);
18587
18588 Some(cx.spawn_in(window, async move |this, cx| {
18589 let rename_range = if let Some(range) = prepare_rename.await? {
18590 Some(range)
18591 } else {
18592 this.update(cx, |this, cx| {
18593 let buffer = this.buffer.read(cx).snapshot(cx);
18594 let mut buffer_highlights = this
18595 .document_highlights_for_position(selection.head(), &buffer)
18596 .filter(|highlight| {
18597 highlight.start.excerpt_id == selection.head().excerpt_id
18598 && highlight.end.excerpt_id == selection.head().excerpt_id
18599 });
18600 buffer_highlights
18601 .next()
18602 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
18603 })?
18604 };
18605 if let Some(rename_range) = rename_range {
18606 this.update_in(cx, |this, window, cx| {
18607 let snapshot = cursor_buffer.read(cx).snapshot();
18608 let rename_buffer_range = rename_range.to_offset(&snapshot);
18609 let cursor_offset_in_rename_range =
18610 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
18611 let cursor_offset_in_rename_range_end =
18612 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
18613
18614 this.take_rename(false, window, cx);
18615 let buffer = this.buffer.read(cx).read(cx);
18616 let cursor_offset = selection.head().to_offset(&buffer);
18617 let rename_start =
18618 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
18619 let rename_end = rename_start + rename_buffer_range.len();
18620 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
18621 let mut old_highlight_id = None;
18622 let old_name: Arc<str> = buffer
18623 .chunks(rename_start..rename_end, true)
18624 .map(|chunk| {
18625 if old_highlight_id.is_none() {
18626 old_highlight_id = chunk.syntax_highlight_id;
18627 }
18628 chunk.text
18629 })
18630 .collect::<String>()
18631 .into();
18632
18633 drop(buffer);
18634
18635 // Position the selection in the rename editor so that it matches the current selection.
18636 this.show_local_selections = false;
18637 let rename_editor = cx.new(|cx| {
18638 let mut editor = Editor::single_line(window, cx);
18639 editor.buffer.update(cx, |buffer, cx| {
18640 buffer.edit(
18641 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
18642 None,
18643 cx,
18644 )
18645 });
18646 let cursor_offset_in_rename_range =
18647 MultiBufferOffset(cursor_offset_in_rename_range);
18648 let cursor_offset_in_rename_range_end =
18649 MultiBufferOffset(cursor_offset_in_rename_range_end);
18650 let rename_selection_range = match cursor_offset_in_rename_range
18651 .cmp(&cursor_offset_in_rename_range_end)
18652 {
18653 Ordering::Equal => {
18654 editor.select_all(&SelectAll, window, cx);
18655 return editor;
18656 }
18657 Ordering::Less => {
18658 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
18659 }
18660 Ordering::Greater => {
18661 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
18662 }
18663 };
18664 if rename_selection_range.end.0 > old_name.len() {
18665 editor.select_all(&SelectAll, window, cx);
18666 } else {
18667 editor.change_selections(Default::default(), window, cx, |s| {
18668 s.select_ranges([rename_selection_range]);
18669 });
18670 }
18671 editor
18672 });
18673 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
18674 if e == &EditorEvent::Focused {
18675 cx.emit(EditorEvent::FocusedIn)
18676 }
18677 })
18678 .detach();
18679
18680 let write_highlights =
18681 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
18682 let read_highlights =
18683 this.clear_background_highlights::<DocumentHighlightRead>(cx);
18684 let ranges = write_highlights
18685 .iter()
18686 .flat_map(|(_, ranges)| ranges.iter())
18687 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
18688 .cloned()
18689 .collect();
18690
18691 this.highlight_text::<Rename>(
18692 ranges,
18693 HighlightStyle {
18694 fade_out: Some(0.6),
18695 ..Default::default()
18696 },
18697 cx,
18698 );
18699 let rename_focus_handle = rename_editor.focus_handle(cx);
18700 window.focus(&rename_focus_handle, cx);
18701 let block_id = this.insert_blocks(
18702 [BlockProperties {
18703 style: BlockStyle::Flex,
18704 placement: BlockPlacement::Below(range.start),
18705 height: Some(1),
18706 render: Arc::new({
18707 let rename_editor = rename_editor.clone();
18708 move |cx: &mut BlockContext| {
18709 let mut text_style = cx.editor_style.text.clone();
18710 if let Some(highlight_style) = old_highlight_id
18711 .and_then(|h| h.style(&cx.editor_style.syntax))
18712 {
18713 text_style = text_style.highlight(highlight_style);
18714 }
18715 div()
18716 .block_mouse_except_scroll()
18717 .pl(cx.anchor_x)
18718 .child(EditorElement::new(
18719 &rename_editor,
18720 EditorStyle {
18721 background: cx.theme().system().transparent,
18722 local_player: cx.editor_style.local_player,
18723 text: text_style,
18724 scrollbar_width: cx.editor_style.scrollbar_width,
18725 syntax: cx.editor_style.syntax.clone(),
18726 status: cx.editor_style.status.clone(),
18727 inlay_hints_style: HighlightStyle {
18728 font_weight: Some(FontWeight::BOLD),
18729 ..make_inlay_hints_style(cx.app)
18730 },
18731 edit_prediction_styles: make_suggestion_styles(
18732 cx.app,
18733 ),
18734 ..EditorStyle::default()
18735 },
18736 ))
18737 .into_any_element()
18738 }
18739 }),
18740 priority: 0,
18741 }],
18742 Some(Autoscroll::fit()),
18743 cx,
18744 )[0];
18745 this.pending_rename = Some(RenameState {
18746 range,
18747 old_name,
18748 editor: rename_editor,
18749 block_id,
18750 });
18751 })?;
18752 }
18753
18754 Ok(())
18755 }))
18756 }
18757
18758 pub fn confirm_rename(
18759 &mut self,
18760 _: &ConfirmRename,
18761 window: &mut Window,
18762 cx: &mut Context<Self>,
18763 ) -> Option<Task<Result<()>>> {
18764 let rename = self.take_rename(false, window, cx)?;
18765 let workspace = self.workspace()?.downgrade();
18766 let (buffer, start) = self
18767 .buffer
18768 .read(cx)
18769 .text_anchor_for_position(rename.range.start, cx)?;
18770 let (end_buffer, _) = self
18771 .buffer
18772 .read(cx)
18773 .text_anchor_for_position(rename.range.end, cx)?;
18774 if buffer != end_buffer {
18775 return None;
18776 }
18777
18778 let old_name = rename.old_name;
18779 let new_name = rename.editor.read(cx).text(cx);
18780
18781 let rename = self.semantics_provider.as_ref()?.perform_rename(
18782 &buffer,
18783 start,
18784 new_name.clone(),
18785 cx,
18786 )?;
18787
18788 Some(cx.spawn_in(window, async move |editor, cx| {
18789 let project_transaction = rename.await?;
18790 Self::open_project_transaction(
18791 &editor,
18792 workspace,
18793 project_transaction,
18794 format!("Rename: {} → {}", old_name, new_name),
18795 cx,
18796 )
18797 .await?;
18798
18799 editor.update(cx, |editor, cx| {
18800 editor.refresh_document_highlights(cx);
18801 })?;
18802 Ok(())
18803 }))
18804 }
18805
18806 fn take_rename(
18807 &mut self,
18808 moving_cursor: bool,
18809 window: &mut Window,
18810 cx: &mut Context<Self>,
18811 ) -> Option<RenameState> {
18812 let rename = self.pending_rename.take()?;
18813 if rename.editor.focus_handle(cx).is_focused(window) {
18814 window.focus(&self.focus_handle, cx);
18815 }
18816
18817 self.remove_blocks(
18818 [rename.block_id].into_iter().collect(),
18819 Some(Autoscroll::fit()),
18820 cx,
18821 );
18822 self.clear_highlights::<Rename>(cx);
18823 self.show_local_selections = true;
18824
18825 if moving_cursor {
18826 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
18827 editor
18828 .selections
18829 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
18830 .head()
18831 });
18832
18833 // Update the selection to match the position of the selection inside
18834 // the rename editor.
18835 let snapshot = self.buffer.read(cx).read(cx);
18836 let rename_range = rename.range.to_offset(&snapshot);
18837 let cursor_in_editor = snapshot
18838 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
18839 .min(rename_range.end);
18840 drop(snapshot);
18841
18842 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18843 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
18844 });
18845 } else {
18846 self.refresh_document_highlights(cx);
18847 }
18848
18849 Some(rename)
18850 }
18851
18852 pub fn pending_rename(&self) -> Option<&RenameState> {
18853 self.pending_rename.as_ref()
18854 }
18855
18856 fn format(
18857 &mut self,
18858 _: &Format,
18859 window: &mut Window,
18860 cx: &mut Context<Self>,
18861 ) -> Option<Task<Result<()>>> {
18862 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18863
18864 let project = match &self.project {
18865 Some(project) => project.clone(),
18866 None => return None,
18867 };
18868
18869 Some(self.perform_format(
18870 project,
18871 FormatTrigger::Manual,
18872 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
18873 window,
18874 cx,
18875 ))
18876 }
18877
18878 fn format_selections(
18879 &mut self,
18880 _: &FormatSelections,
18881 window: &mut Window,
18882 cx: &mut Context<Self>,
18883 ) -> Option<Task<Result<()>>> {
18884 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18885
18886 let project = match &self.project {
18887 Some(project) => project.clone(),
18888 None => return None,
18889 };
18890
18891 let ranges = self
18892 .selections
18893 .all_adjusted(&self.display_snapshot(cx))
18894 .into_iter()
18895 .map(|selection| selection.range())
18896 .collect_vec();
18897
18898 Some(self.perform_format(
18899 project,
18900 FormatTrigger::Manual,
18901 FormatTarget::Ranges(ranges),
18902 window,
18903 cx,
18904 ))
18905 }
18906
18907 fn perform_format(
18908 &mut self,
18909 project: Entity<Project>,
18910 trigger: FormatTrigger,
18911 target: FormatTarget,
18912 window: &mut Window,
18913 cx: &mut Context<Self>,
18914 ) -> Task<Result<()>> {
18915 let buffer = self.buffer.clone();
18916 let (buffers, target) = match target {
18917 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
18918 FormatTarget::Ranges(selection_ranges) => {
18919 let multi_buffer = buffer.read(cx);
18920 let snapshot = multi_buffer.read(cx);
18921 let mut buffers = HashSet::default();
18922 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
18923 BTreeMap::new();
18924 for selection_range in selection_ranges {
18925 for (buffer, buffer_range, _) in
18926 snapshot.range_to_buffer_ranges(selection_range.start..=selection_range.end)
18927 {
18928 let buffer_id = buffer.remote_id();
18929 let start = buffer.anchor_before(buffer_range.start);
18930 let end = buffer.anchor_after(buffer_range.end);
18931 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
18932 buffer_id_to_ranges
18933 .entry(buffer_id)
18934 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
18935 .or_insert_with(|| vec![start..end]);
18936 }
18937 }
18938 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
18939 }
18940 };
18941
18942 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
18943 let selections_prev = transaction_id_prev
18944 .and_then(|transaction_id_prev| {
18945 // default to selections as they were after the last edit, if we have them,
18946 // instead of how they are now.
18947 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
18948 // will take you back to where you made the last edit, instead of staying where you scrolled
18949 self.selection_history
18950 .transaction(transaction_id_prev)
18951 .map(|t| t.0.clone())
18952 })
18953 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
18954
18955 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
18956 let format = project.update(cx, |project, cx| {
18957 project.format(buffers, target, true, trigger, cx)
18958 });
18959
18960 cx.spawn_in(window, async move |editor, cx| {
18961 let transaction = futures::select_biased! {
18962 transaction = format.log_err().fuse() => transaction,
18963 () = timeout => {
18964 log::warn!("timed out waiting for formatting");
18965 None
18966 }
18967 };
18968
18969 buffer.update(cx, |buffer, cx| {
18970 if let Some(transaction) = transaction
18971 && !buffer.is_singleton()
18972 {
18973 buffer.push_transaction(&transaction.0, cx);
18974 }
18975 cx.notify();
18976 });
18977
18978 if let Some(transaction_id_now) =
18979 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))
18980 {
18981 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
18982 if has_new_transaction {
18983 editor
18984 .update(cx, |editor, _| {
18985 editor
18986 .selection_history
18987 .insert_transaction(transaction_id_now, selections_prev);
18988 })
18989 .ok();
18990 }
18991 }
18992
18993 Ok(())
18994 })
18995 }
18996
18997 fn organize_imports(
18998 &mut self,
18999 _: &OrganizeImports,
19000 window: &mut Window,
19001 cx: &mut Context<Self>,
19002 ) -> Option<Task<Result<()>>> {
19003 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19004 let project = match &self.project {
19005 Some(project) => project.clone(),
19006 None => return None,
19007 };
19008 Some(self.perform_code_action_kind(
19009 project,
19010 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
19011 window,
19012 cx,
19013 ))
19014 }
19015
19016 fn perform_code_action_kind(
19017 &mut self,
19018 project: Entity<Project>,
19019 kind: CodeActionKind,
19020 window: &mut Window,
19021 cx: &mut Context<Self>,
19022 ) -> Task<Result<()>> {
19023 let buffer = self.buffer.clone();
19024 let buffers = buffer.read(cx).all_buffers();
19025 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
19026 let apply_action = project.update(cx, |project, cx| {
19027 project.apply_code_action_kind(buffers, kind, true, cx)
19028 });
19029 cx.spawn_in(window, async move |_, cx| {
19030 let transaction = futures::select_biased! {
19031 () = timeout => {
19032 log::warn!("timed out waiting for executing code action");
19033 None
19034 }
19035 transaction = apply_action.log_err().fuse() => transaction,
19036 };
19037 buffer.update(cx, |buffer, cx| {
19038 // check if we need this
19039 if let Some(transaction) = transaction
19040 && !buffer.is_singleton()
19041 {
19042 buffer.push_transaction(&transaction.0, cx);
19043 }
19044 cx.notify();
19045 });
19046 Ok(())
19047 })
19048 }
19049
19050 pub fn restart_language_server(
19051 &mut self,
19052 _: &RestartLanguageServer,
19053 _: &mut Window,
19054 cx: &mut Context<Self>,
19055 ) {
19056 if let Some(project) = self.project.clone() {
19057 self.buffer.update(cx, |multi_buffer, cx| {
19058 project.update(cx, |project, cx| {
19059 project.restart_language_servers_for_buffers(
19060 multi_buffer.all_buffers().into_iter().collect(),
19061 HashSet::default(),
19062 cx,
19063 );
19064 });
19065 })
19066 }
19067 }
19068
19069 pub fn stop_language_server(
19070 &mut self,
19071 _: &StopLanguageServer,
19072 _: &mut Window,
19073 cx: &mut Context<Self>,
19074 ) {
19075 if let Some(project) = self.project.clone() {
19076 self.buffer.update(cx, |multi_buffer, cx| {
19077 project.update(cx, |project, cx| {
19078 project.stop_language_servers_for_buffers(
19079 multi_buffer.all_buffers().into_iter().collect(),
19080 HashSet::default(),
19081 cx,
19082 );
19083 });
19084 });
19085 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19086 }
19087 }
19088
19089 fn cancel_language_server_work(
19090 workspace: &mut Workspace,
19091 _: &actions::CancelLanguageServerWork,
19092 _: &mut Window,
19093 cx: &mut Context<Workspace>,
19094 ) {
19095 let project = workspace.project();
19096 let buffers = workspace
19097 .active_item(cx)
19098 .and_then(|item| item.act_as::<Editor>(cx))
19099 .map_or(HashSet::default(), |editor| {
19100 editor.read(cx).buffer.read(cx).all_buffers()
19101 });
19102 project.update(cx, |project, cx| {
19103 project.cancel_language_server_work_for_buffers(buffers, cx);
19104 });
19105 }
19106
19107 fn show_character_palette(
19108 &mut self,
19109 _: &ShowCharacterPalette,
19110 window: &mut Window,
19111 _: &mut Context<Self>,
19112 ) {
19113 window.show_character_palette();
19114 }
19115
19116 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
19117 if !self.diagnostics_enabled() {
19118 return;
19119 }
19120
19121 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
19122 let buffer = self.buffer.read(cx).snapshot(cx);
19123 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
19124 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
19125 let is_valid = buffer
19126 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
19127 .any(|entry| {
19128 entry.diagnostic.is_primary
19129 && !entry.range.is_empty()
19130 && entry.range.start == primary_range_start
19131 && entry.diagnostic.message == active_diagnostics.active_message
19132 });
19133
19134 if !is_valid {
19135 self.dismiss_diagnostics(cx);
19136 }
19137 }
19138 }
19139
19140 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
19141 match &self.active_diagnostics {
19142 ActiveDiagnostic::Group(group) => Some(group),
19143 _ => None,
19144 }
19145 }
19146
19147 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
19148 if !self.diagnostics_enabled() {
19149 return;
19150 }
19151 self.dismiss_diagnostics(cx);
19152 self.active_diagnostics = ActiveDiagnostic::All;
19153 }
19154
19155 fn activate_diagnostics(
19156 &mut self,
19157 buffer_id: BufferId,
19158 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
19159 window: &mut Window,
19160 cx: &mut Context<Self>,
19161 ) {
19162 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19163 return;
19164 }
19165 self.dismiss_diagnostics(cx);
19166 let snapshot = self.snapshot(window, cx);
19167 let buffer = self.buffer.read(cx).snapshot(cx);
19168 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
19169 return;
19170 };
19171
19172 let diagnostic_group = buffer
19173 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
19174 .collect::<Vec<_>>();
19175
19176 let language_registry = self
19177 .project()
19178 .map(|project| project.read(cx).languages().clone());
19179
19180 let blocks = renderer.render_group(
19181 diagnostic_group,
19182 buffer_id,
19183 snapshot,
19184 cx.weak_entity(),
19185 language_registry,
19186 cx,
19187 );
19188
19189 let blocks = self.display_map.update(cx, |display_map, cx| {
19190 display_map.insert_blocks(blocks, cx).into_iter().collect()
19191 });
19192 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
19193 active_range: buffer.anchor_before(diagnostic.range.start)
19194 ..buffer.anchor_after(diagnostic.range.end),
19195 active_message: diagnostic.diagnostic.message.clone(),
19196 group_id: diagnostic.diagnostic.group_id,
19197 blocks,
19198 });
19199 cx.notify();
19200 }
19201
19202 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
19203 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19204 return;
19205 };
19206
19207 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
19208 if let ActiveDiagnostic::Group(group) = prev {
19209 self.display_map.update(cx, |display_map, cx| {
19210 display_map.remove_blocks(group.blocks, cx);
19211 });
19212 cx.notify();
19213 }
19214 }
19215
19216 /// Disable inline diagnostics rendering for this editor.
19217 pub fn disable_inline_diagnostics(&mut self) {
19218 self.inline_diagnostics_enabled = false;
19219 self.inline_diagnostics_update = Task::ready(());
19220 self.inline_diagnostics.clear();
19221 }
19222
19223 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
19224 self.diagnostics_enabled = false;
19225 self.dismiss_diagnostics(cx);
19226 self.inline_diagnostics_update = Task::ready(());
19227 self.inline_diagnostics.clear();
19228 }
19229
19230 pub fn disable_word_completions(&mut self) {
19231 self.word_completions_enabled = false;
19232 }
19233
19234 pub fn diagnostics_enabled(&self) -> bool {
19235 self.diagnostics_enabled && self.mode.is_full()
19236 }
19237
19238 pub fn inline_diagnostics_enabled(&self) -> bool {
19239 self.inline_diagnostics_enabled && self.diagnostics_enabled()
19240 }
19241
19242 pub fn show_inline_diagnostics(&self) -> bool {
19243 self.show_inline_diagnostics
19244 }
19245
19246 pub fn toggle_inline_diagnostics(
19247 &mut self,
19248 _: &ToggleInlineDiagnostics,
19249 window: &mut Window,
19250 cx: &mut Context<Editor>,
19251 ) {
19252 self.show_inline_diagnostics = !self.show_inline_diagnostics;
19253 self.refresh_inline_diagnostics(false, window, cx);
19254 }
19255
19256 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
19257 self.diagnostics_max_severity = severity;
19258 self.display_map.update(cx, |display_map, _| {
19259 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
19260 });
19261 }
19262
19263 pub fn toggle_diagnostics(
19264 &mut self,
19265 _: &ToggleDiagnostics,
19266 window: &mut Window,
19267 cx: &mut Context<Editor>,
19268 ) {
19269 if !self.diagnostics_enabled() {
19270 return;
19271 }
19272
19273 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19274 EditorSettings::get_global(cx)
19275 .diagnostics_max_severity
19276 .filter(|severity| severity != &DiagnosticSeverity::Off)
19277 .unwrap_or(DiagnosticSeverity::Hint)
19278 } else {
19279 DiagnosticSeverity::Off
19280 };
19281 self.set_max_diagnostics_severity(new_severity, cx);
19282 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19283 self.active_diagnostics = ActiveDiagnostic::None;
19284 self.inline_diagnostics_update = Task::ready(());
19285 self.inline_diagnostics.clear();
19286 } else {
19287 self.refresh_inline_diagnostics(false, window, cx);
19288 }
19289
19290 cx.notify();
19291 }
19292
19293 pub fn toggle_minimap(
19294 &mut self,
19295 _: &ToggleMinimap,
19296 window: &mut Window,
19297 cx: &mut Context<Editor>,
19298 ) {
19299 if self.supports_minimap(cx) {
19300 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
19301 }
19302 }
19303
19304 fn refresh_inline_diagnostics(
19305 &mut self,
19306 debounce: bool,
19307 window: &mut Window,
19308 cx: &mut Context<Self>,
19309 ) {
19310 let max_severity = ProjectSettings::get_global(cx)
19311 .diagnostics
19312 .inline
19313 .max_severity
19314 .unwrap_or(self.diagnostics_max_severity);
19315
19316 if !self.inline_diagnostics_enabled()
19317 || !self.diagnostics_enabled()
19318 || !self.show_inline_diagnostics
19319 || max_severity == DiagnosticSeverity::Off
19320 {
19321 self.inline_diagnostics_update = Task::ready(());
19322 self.inline_diagnostics.clear();
19323 return;
19324 }
19325
19326 let debounce_ms = ProjectSettings::get_global(cx)
19327 .diagnostics
19328 .inline
19329 .update_debounce_ms;
19330 let debounce = if debounce && debounce_ms > 0 {
19331 Some(Duration::from_millis(debounce_ms))
19332 } else {
19333 None
19334 };
19335 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
19336 if let Some(debounce) = debounce {
19337 cx.background_executor().timer(debounce).await;
19338 }
19339 let Some(snapshot) = editor.upgrade().map(|editor| {
19340 editor.update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
19341 }) else {
19342 return;
19343 };
19344
19345 let new_inline_diagnostics = cx
19346 .background_spawn(async move {
19347 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
19348 for diagnostic_entry in
19349 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
19350 {
19351 let message = diagnostic_entry
19352 .diagnostic
19353 .message
19354 .split_once('\n')
19355 .map(|(line, _)| line)
19356 .map(SharedString::new)
19357 .unwrap_or_else(|| {
19358 SharedString::new(&*diagnostic_entry.diagnostic.message)
19359 });
19360 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
19361 let (Ok(i) | Err(i)) = inline_diagnostics
19362 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
19363 inline_diagnostics.insert(
19364 i,
19365 (
19366 start_anchor,
19367 InlineDiagnostic {
19368 message,
19369 group_id: diagnostic_entry.diagnostic.group_id,
19370 start: diagnostic_entry.range.start.to_point(&snapshot),
19371 is_primary: diagnostic_entry.diagnostic.is_primary,
19372 severity: diagnostic_entry.diagnostic.severity,
19373 },
19374 ),
19375 );
19376 }
19377 inline_diagnostics
19378 })
19379 .await;
19380
19381 editor
19382 .update(cx, |editor, cx| {
19383 editor.inline_diagnostics = new_inline_diagnostics;
19384 cx.notify();
19385 })
19386 .ok();
19387 });
19388 }
19389
19390 fn pull_diagnostics(
19391 &mut self,
19392 buffer_id: BufferId,
19393 _window: &Window,
19394 cx: &mut Context<Self>,
19395 ) -> Option<()> {
19396 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
19397 return None;
19398 }
19399 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
19400 .diagnostics
19401 .lsp_pull_diagnostics;
19402 if !pull_diagnostics_settings.enabled {
19403 return None;
19404 }
19405 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
19406 let project = self.project()?.downgrade();
19407 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
19408
19409 self.pull_diagnostics_task = cx.spawn(async move |_, cx| {
19410 cx.background_executor().timer(debounce).await;
19411 if let Ok(task) = project.update(cx, |project, cx| {
19412 project.lsp_store().update(cx, |lsp_store, cx| {
19413 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
19414 })
19415 }) {
19416 task.await.log_err();
19417 }
19418 project
19419 .update(cx, |project, cx| {
19420 project.lsp_store().update(cx, |lsp_store, cx| {
19421 lsp_store.pull_document_diagnostics_for_buffer_edit(buffer_id, cx);
19422 })
19423 })
19424 .log_err();
19425 });
19426
19427 Some(())
19428 }
19429
19430 pub fn set_selections_from_remote(
19431 &mut self,
19432 selections: Vec<Selection<Anchor>>,
19433 pending_selection: Option<Selection<Anchor>>,
19434 window: &mut Window,
19435 cx: &mut Context<Self>,
19436 ) {
19437 let old_cursor_position = self.selections.newest_anchor().head();
19438 self.selections
19439 .change_with(&self.display_snapshot(cx), |s| {
19440 s.select_anchors(selections);
19441 if let Some(pending_selection) = pending_selection {
19442 s.set_pending(pending_selection, SelectMode::Character);
19443 } else {
19444 s.clear_pending();
19445 }
19446 });
19447 self.selections_did_change(
19448 false,
19449 &old_cursor_position,
19450 SelectionEffects::default(),
19451 window,
19452 cx,
19453 );
19454 }
19455
19456 pub fn transact(
19457 &mut self,
19458 window: &mut Window,
19459 cx: &mut Context<Self>,
19460 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
19461 ) -> Option<TransactionId> {
19462 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
19463 this.start_transaction_at(Instant::now(), window, cx);
19464 update(this, window, cx);
19465 this.end_transaction_at(Instant::now(), cx)
19466 })
19467 }
19468
19469 pub fn start_transaction_at(
19470 &mut self,
19471 now: Instant,
19472 window: &mut Window,
19473 cx: &mut Context<Self>,
19474 ) -> Option<TransactionId> {
19475 self.end_selection(window, cx);
19476 if let Some(tx_id) = self
19477 .buffer
19478 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
19479 {
19480 self.selection_history
19481 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
19482 cx.emit(EditorEvent::TransactionBegun {
19483 transaction_id: tx_id,
19484 });
19485 Some(tx_id)
19486 } else {
19487 None
19488 }
19489 }
19490
19491 pub fn end_transaction_at(
19492 &mut self,
19493 now: Instant,
19494 cx: &mut Context<Self>,
19495 ) -> Option<TransactionId> {
19496 if let Some(transaction_id) = self
19497 .buffer
19498 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
19499 {
19500 if let Some((_, end_selections)) =
19501 self.selection_history.transaction_mut(transaction_id)
19502 {
19503 *end_selections = Some(self.selections.disjoint_anchors_arc());
19504 } else {
19505 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
19506 }
19507
19508 cx.emit(EditorEvent::Edited { transaction_id });
19509 Some(transaction_id)
19510 } else {
19511 None
19512 }
19513 }
19514
19515 pub fn modify_transaction_selection_history(
19516 &mut self,
19517 transaction_id: TransactionId,
19518 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
19519 ) -> bool {
19520 self.selection_history
19521 .transaction_mut(transaction_id)
19522 .map(modify)
19523 .is_some()
19524 }
19525
19526 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
19527 if self.selection_mark_mode {
19528 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19529 s.move_with(|_, sel| {
19530 sel.collapse_to(sel.head(), SelectionGoal::None);
19531 });
19532 })
19533 }
19534 self.selection_mark_mode = true;
19535 cx.notify();
19536 }
19537
19538 pub fn swap_selection_ends(
19539 &mut self,
19540 _: &actions::SwapSelectionEnds,
19541 window: &mut Window,
19542 cx: &mut Context<Self>,
19543 ) {
19544 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19545 s.move_with(|_, sel| {
19546 if sel.start != sel.end {
19547 sel.reversed = !sel.reversed
19548 }
19549 });
19550 });
19551 self.request_autoscroll(Autoscroll::newest(), cx);
19552 cx.notify();
19553 }
19554
19555 pub fn toggle_focus(
19556 workspace: &mut Workspace,
19557 _: &actions::ToggleFocus,
19558 window: &mut Window,
19559 cx: &mut Context<Workspace>,
19560 ) {
19561 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
19562 return;
19563 };
19564 workspace.activate_item(&item, true, true, window, cx);
19565 }
19566
19567 pub fn toggle_fold(
19568 &mut self,
19569 _: &actions::ToggleFold,
19570 window: &mut Window,
19571 cx: &mut Context<Self>,
19572 ) {
19573 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19574 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19575 let selection = self.selections.newest::<Point>(&display_map);
19576
19577 let range = if selection.is_empty() {
19578 let point = selection.head().to_display_point(&display_map);
19579 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19580 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19581 .to_point(&display_map);
19582 start..end
19583 } else {
19584 selection.range()
19585 };
19586 if display_map.folds_in_range(range).next().is_some() {
19587 self.unfold_lines(&Default::default(), window, cx)
19588 } else {
19589 self.fold(&Default::default(), window, cx)
19590 }
19591 } else {
19592 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19593 let buffer_ids: HashSet<_> = self
19594 .selections
19595 .disjoint_anchor_ranges()
19596 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19597 .collect();
19598
19599 let should_unfold = buffer_ids
19600 .iter()
19601 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19602
19603 for buffer_id in buffer_ids {
19604 if should_unfold {
19605 self.unfold_buffer(buffer_id, cx);
19606 } else {
19607 self.fold_buffer(buffer_id, cx);
19608 }
19609 }
19610 }
19611 }
19612
19613 pub fn toggle_fold_recursive(
19614 &mut self,
19615 _: &actions::ToggleFoldRecursive,
19616 window: &mut Window,
19617 cx: &mut Context<Self>,
19618 ) {
19619 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19620
19621 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19622 let range = if selection.is_empty() {
19623 let point = selection.head().to_display_point(&display_map);
19624 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19625 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19626 .to_point(&display_map);
19627 start..end
19628 } else {
19629 selection.range()
19630 };
19631 if display_map.folds_in_range(range).next().is_some() {
19632 self.unfold_recursive(&Default::default(), window, cx)
19633 } else {
19634 self.fold_recursive(&Default::default(), window, cx)
19635 }
19636 }
19637
19638 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
19639 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19640 let mut to_fold = Vec::new();
19641 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19642 let selections = self.selections.all_adjusted(&display_map);
19643
19644 for selection in selections {
19645 let range = selection.range().sorted();
19646 let buffer_start_row = range.start.row;
19647
19648 if range.start.row != range.end.row {
19649 let mut found = false;
19650 let mut row = range.start.row;
19651 while row <= range.end.row {
19652 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19653 {
19654 found = true;
19655 row = crease.range().end.row + 1;
19656 to_fold.push(crease);
19657 } else {
19658 row += 1
19659 }
19660 }
19661 if found {
19662 continue;
19663 }
19664 }
19665
19666 for row in (0..=range.start.row).rev() {
19667 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19668 && crease.range().end.row >= buffer_start_row
19669 {
19670 to_fold.push(crease);
19671 if row <= range.start.row {
19672 break;
19673 }
19674 }
19675 }
19676 }
19677
19678 self.fold_creases(to_fold, true, window, cx);
19679 } else {
19680 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19681 let buffer_ids = self
19682 .selections
19683 .disjoint_anchor_ranges()
19684 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19685 .collect::<HashSet<_>>();
19686 for buffer_id in buffer_ids {
19687 self.fold_buffer(buffer_id, cx);
19688 }
19689 }
19690 }
19691
19692 pub fn toggle_fold_all(
19693 &mut self,
19694 _: &actions::ToggleFoldAll,
19695 window: &mut Window,
19696 cx: &mut Context<Self>,
19697 ) {
19698 let has_folds = if self.buffer.read(cx).is_singleton() {
19699 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19700 let has_folds = display_map
19701 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
19702 .next()
19703 .is_some();
19704 has_folds
19705 } else {
19706 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
19707 let has_folds = buffer_ids
19708 .iter()
19709 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19710 has_folds
19711 };
19712
19713 if has_folds {
19714 self.unfold_all(&actions::UnfoldAll, window, cx);
19715 } else {
19716 self.fold_all(&actions::FoldAll, window, cx);
19717 }
19718 }
19719
19720 fn fold_at_level(
19721 &mut self,
19722 fold_at: &FoldAtLevel,
19723 window: &mut Window,
19724 cx: &mut Context<Self>,
19725 ) {
19726 if !self.buffer.read(cx).is_singleton() {
19727 return;
19728 }
19729
19730 let fold_at_level = fold_at.0;
19731 let snapshot = self.buffer.read(cx).snapshot(cx);
19732 let mut to_fold = Vec::new();
19733 let mut stack = vec![(0, snapshot.max_row().0, 1)];
19734
19735 let row_ranges_to_keep: Vec<Range<u32>> = self
19736 .selections
19737 .all::<Point>(&self.display_snapshot(cx))
19738 .into_iter()
19739 .map(|sel| sel.start.row..sel.end.row)
19740 .collect();
19741
19742 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
19743 while start_row < end_row {
19744 match self
19745 .snapshot(window, cx)
19746 .crease_for_buffer_row(MultiBufferRow(start_row))
19747 {
19748 Some(crease) => {
19749 let nested_start_row = crease.range().start.row + 1;
19750 let nested_end_row = crease.range().end.row;
19751
19752 if current_level < fold_at_level {
19753 stack.push((nested_start_row, nested_end_row, current_level + 1));
19754 } else if current_level == fold_at_level {
19755 // Fold iff there is no selection completely contained within the fold region
19756 if !row_ranges_to_keep.iter().any(|selection| {
19757 selection.end >= nested_start_row
19758 && selection.start <= nested_end_row
19759 }) {
19760 to_fold.push(crease);
19761 }
19762 }
19763
19764 start_row = nested_end_row + 1;
19765 }
19766 None => start_row += 1,
19767 }
19768 }
19769 }
19770
19771 self.fold_creases(to_fold, true, window, cx);
19772 }
19773
19774 pub fn fold_at_level_1(
19775 &mut self,
19776 _: &actions::FoldAtLevel1,
19777 window: &mut Window,
19778 cx: &mut Context<Self>,
19779 ) {
19780 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
19781 }
19782
19783 pub fn fold_at_level_2(
19784 &mut self,
19785 _: &actions::FoldAtLevel2,
19786 window: &mut Window,
19787 cx: &mut Context<Self>,
19788 ) {
19789 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
19790 }
19791
19792 pub fn fold_at_level_3(
19793 &mut self,
19794 _: &actions::FoldAtLevel3,
19795 window: &mut Window,
19796 cx: &mut Context<Self>,
19797 ) {
19798 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
19799 }
19800
19801 pub fn fold_at_level_4(
19802 &mut self,
19803 _: &actions::FoldAtLevel4,
19804 window: &mut Window,
19805 cx: &mut Context<Self>,
19806 ) {
19807 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
19808 }
19809
19810 pub fn fold_at_level_5(
19811 &mut self,
19812 _: &actions::FoldAtLevel5,
19813 window: &mut Window,
19814 cx: &mut Context<Self>,
19815 ) {
19816 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
19817 }
19818
19819 pub fn fold_at_level_6(
19820 &mut self,
19821 _: &actions::FoldAtLevel6,
19822 window: &mut Window,
19823 cx: &mut Context<Self>,
19824 ) {
19825 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
19826 }
19827
19828 pub fn fold_at_level_7(
19829 &mut self,
19830 _: &actions::FoldAtLevel7,
19831 window: &mut Window,
19832 cx: &mut Context<Self>,
19833 ) {
19834 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
19835 }
19836
19837 pub fn fold_at_level_8(
19838 &mut self,
19839 _: &actions::FoldAtLevel8,
19840 window: &mut Window,
19841 cx: &mut Context<Self>,
19842 ) {
19843 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
19844 }
19845
19846 pub fn fold_at_level_9(
19847 &mut self,
19848 _: &actions::FoldAtLevel9,
19849 window: &mut Window,
19850 cx: &mut Context<Self>,
19851 ) {
19852 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
19853 }
19854
19855 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
19856 if self.buffer.read(cx).is_singleton() {
19857 let mut fold_ranges = Vec::new();
19858 let snapshot = self.buffer.read(cx).snapshot(cx);
19859
19860 for row in 0..snapshot.max_row().0 {
19861 if let Some(foldable_range) = self
19862 .snapshot(window, cx)
19863 .crease_for_buffer_row(MultiBufferRow(row))
19864 {
19865 fold_ranges.push(foldable_range);
19866 }
19867 }
19868
19869 self.fold_creases(fold_ranges, true, window, cx);
19870 } else {
19871 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19872 editor
19873 .update_in(cx, |editor, _, cx| {
19874 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19875 editor.fold_buffer(buffer_id, cx);
19876 }
19877 })
19878 .ok();
19879 });
19880 }
19881 cx.emit(SearchEvent::ResultsCollapsedChanged(
19882 CollapseDirection::Collapsed,
19883 ));
19884 }
19885
19886 pub fn fold_function_bodies(
19887 &mut self,
19888 _: &actions::FoldFunctionBodies,
19889 window: &mut Window,
19890 cx: &mut Context<Self>,
19891 ) {
19892 let snapshot = self.buffer.read(cx).snapshot(cx);
19893
19894 let ranges = snapshot
19895 .text_object_ranges(
19896 MultiBufferOffset(0)..snapshot.len(),
19897 TreeSitterOptions::default(),
19898 )
19899 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
19900 .collect::<Vec<_>>();
19901
19902 let creases = ranges
19903 .into_iter()
19904 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
19905 .collect();
19906
19907 self.fold_creases(creases, true, window, cx);
19908 }
19909
19910 pub fn fold_recursive(
19911 &mut self,
19912 _: &actions::FoldRecursive,
19913 window: &mut Window,
19914 cx: &mut Context<Self>,
19915 ) {
19916 let mut to_fold = Vec::new();
19917 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19918 let selections = self.selections.all_adjusted(&display_map);
19919
19920 for selection in selections {
19921 let range = selection.range().sorted();
19922 let buffer_start_row = range.start.row;
19923
19924 if range.start.row != range.end.row {
19925 let mut found = false;
19926 for row in range.start.row..=range.end.row {
19927 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19928 found = true;
19929 to_fold.push(crease);
19930 }
19931 }
19932 if found {
19933 continue;
19934 }
19935 }
19936
19937 for row in (0..=range.start.row).rev() {
19938 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19939 if crease.range().end.row >= buffer_start_row {
19940 to_fold.push(crease);
19941 } else {
19942 break;
19943 }
19944 }
19945 }
19946 }
19947
19948 self.fold_creases(to_fold, true, window, cx);
19949 }
19950
19951 pub fn fold_at(
19952 &mut self,
19953 buffer_row: MultiBufferRow,
19954 window: &mut Window,
19955 cx: &mut Context<Self>,
19956 ) {
19957 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19958
19959 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
19960 let autoscroll = self
19961 .selections
19962 .all::<Point>(&display_map)
19963 .iter()
19964 .any(|selection| crease.range().overlaps(&selection.range()));
19965
19966 self.fold_creases(vec![crease], autoscroll, window, cx);
19967 }
19968 }
19969
19970 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
19971 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19972 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19973 let buffer = display_map.buffer_snapshot();
19974 let selections = self.selections.all::<Point>(&display_map);
19975 let ranges = selections
19976 .iter()
19977 .map(|s| {
19978 let range = s.display_range(&display_map).sorted();
19979 let mut start = range.start.to_point(&display_map);
19980 let mut end = range.end.to_point(&display_map);
19981 start.column = 0;
19982 end.column = buffer.line_len(MultiBufferRow(end.row));
19983 start..end
19984 })
19985 .collect::<Vec<_>>();
19986
19987 self.unfold_ranges(&ranges, true, true, cx);
19988 } else {
19989 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19990 let buffer_ids = self
19991 .selections
19992 .disjoint_anchor_ranges()
19993 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19994 .collect::<HashSet<_>>();
19995 for buffer_id in buffer_ids {
19996 self.unfold_buffer(buffer_id, cx);
19997 }
19998 }
19999 }
20000
20001 pub fn unfold_recursive(
20002 &mut self,
20003 _: &UnfoldRecursive,
20004 _window: &mut Window,
20005 cx: &mut Context<Self>,
20006 ) {
20007 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20008 let selections = self.selections.all::<Point>(&display_map);
20009 let ranges = selections
20010 .iter()
20011 .map(|s| {
20012 let mut range = s.display_range(&display_map).sorted();
20013 *range.start.column_mut() = 0;
20014 *range.end.column_mut() = display_map.line_len(range.end.row());
20015 let start = range.start.to_point(&display_map);
20016 let end = range.end.to_point(&display_map);
20017 start..end
20018 })
20019 .collect::<Vec<_>>();
20020
20021 self.unfold_ranges(&ranges, true, true, cx);
20022 }
20023
20024 pub fn unfold_at(
20025 &mut self,
20026 buffer_row: MultiBufferRow,
20027 _window: &mut Window,
20028 cx: &mut Context<Self>,
20029 ) {
20030 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20031
20032 let intersection_range = Point::new(buffer_row.0, 0)
20033 ..Point::new(
20034 buffer_row.0,
20035 display_map.buffer_snapshot().line_len(buffer_row),
20036 );
20037
20038 let autoscroll = self
20039 .selections
20040 .all::<Point>(&display_map)
20041 .iter()
20042 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
20043
20044 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
20045 }
20046
20047 pub fn unfold_all(
20048 &mut self,
20049 _: &actions::UnfoldAll,
20050 _window: &mut Window,
20051 cx: &mut Context<Self>,
20052 ) {
20053 if self.buffer.read(cx).is_singleton() {
20054 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20055 self.unfold_ranges(
20056 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
20057 true,
20058 true,
20059 cx,
20060 );
20061 } else {
20062 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
20063 editor
20064 .update(cx, |editor, cx| {
20065 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
20066 editor.unfold_buffer(buffer_id, cx);
20067 }
20068 })
20069 .ok();
20070 });
20071 }
20072 cx.emit(SearchEvent::ResultsCollapsedChanged(
20073 CollapseDirection::Expanded,
20074 ));
20075 }
20076
20077 pub fn fold_selected_ranges(
20078 &mut self,
20079 _: &FoldSelectedRanges,
20080 window: &mut Window,
20081 cx: &mut Context<Self>,
20082 ) {
20083 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20084 let selections = self.selections.all_adjusted(&display_map);
20085 let ranges = selections
20086 .into_iter()
20087 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
20088 .collect::<Vec<_>>();
20089 self.fold_creases(ranges, true, window, cx);
20090 }
20091
20092 pub fn fold_ranges<T: ToOffset + Clone>(
20093 &mut self,
20094 ranges: Vec<Range<T>>,
20095 auto_scroll: bool,
20096 window: &mut Window,
20097 cx: &mut Context<Self>,
20098 ) {
20099 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20100 let ranges = ranges
20101 .into_iter()
20102 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
20103 .collect::<Vec<_>>();
20104 self.fold_creases(ranges, auto_scroll, window, cx);
20105 }
20106
20107 pub fn fold_creases<T: ToOffset + Clone>(
20108 &mut self,
20109 creases: Vec<Crease<T>>,
20110 auto_scroll: bool,
20111 _window: &mut Window,
20112 cx: &mut Context<Self>,
20113 ) {
20114 if creases.is_empty() {
20115 return;
20116 }
20117
20118 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
20119
20120 if auto_scroll {
20121 self.request_autoscroll(Autoscroll::fit(), cx);
20122 }
20123
20124 cx.notify();
20125
20126 self.scrollbar_marker_state.dirty = true;
20127 self.folds_did_change(cx);
20128 }
20129
20130 /// Removes any folds whose ranges intersect any of the given ranges.
20131 pub fn unfold_ranges<T: ToOffset + Clone>(
20132 &mut self,
20133 ranges: &[Range<T>],
20134 inclusive: bool,
20135 auto_scroll: bool,
20136 cx: &mut Context<Self>,
20137 ) {
20138 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20139 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx);
20140 });
20141 self.folds_did_change(cx);
20142 }
20143
20144 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20145 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
20146 return;
20147 }
20148
20149 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
20150 self.display_map.update(cx, |display_map, cx| {
20151 display_map.fold_buffers([buffer_id], cx)
20152 });
20153
20154 let snapshot = self.display_snapshot(cx);
20155 self.selections.change_with(&snapshot, |selections| {
20156 selections.remove_selections_from_buffer(buffer_id);
20157 });
20158
20159 cx.emit(EditorEvent::BufferFoldToggled {
20160 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
20161 folded: true,
20162 });
20163 cx.notify();
20164 }
20165
20166 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20167 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
20168 return;
20169 }
20170 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
20171 self.display_map.update(cx, |display_map, cx| {
20172 display_map.unfold_buffers([buffer_id], cx);
20173 });
20174 cx.emit(EditorEvent::BufferFoldToggled {
20175 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
20176 folded: false,
20177 });
20178 cx.notify();
20179 }
20180
20181 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
20182 self.display_map.read(cx).is_buffer_folded(buffer)
20183 }
20184
20185 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
20186 self.display_map.read(cx).folded_buffers()
20187 }
20188
20189 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20190 self.display_map.update(cx, |display_map, cx| {
20191 display_map.disable_header_for_buffer(buffer_id, cx);
20192 });
20193 cx.notify();
20194 }
20195
20196 /// Removes any folds with the given ranges.
20197 pub fn remove_folds_with_type<T: ToOffset + Clone>(
20198 &mut self,
20199 ranges: &[Range<T>],
20200 type_id: TypeId,
20201 auto_scroll: bool,
20202 cx: &mut Context<Self>,
20203 ) {
20204 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20205 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
20206 });
20207 self.folds_did_change(cx);
20208 }
20209
20210 fn remove_folds_with<T: ToOffset + Clone>(
20211 &mut self,
20212 ranges: &[Range<T>],
20213 auto_scroll: bool,
20214 cx: &mut Context<Self>,
20215 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
20216 ) {
20217 if ranges.is_empty() {
20218 return;
20219 }
20220
20221 let mut buffers_affected = HashSet::default();
20222 let multi_buffer = self.buffer().read(cx);
20223 for range in ranges {
20224 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
20225 buffers_affected.insert(buffer.read(cx).remote_id());
20226 };
20227 }
20228
20229 self.display_map.update(cx, update);
20230
20231 if auto_scroll {
20232 self.request_autoscroll(Autoscroll::fit(), cx);
20233 }
20234
20235 cx.notify();
20236 self.scrollbar_marker_state.dirty = true;
20237 self.active_indent_guides_state.dirty = true;
20238 }
20239
20240 pub fn update_renderer_widths(
20241 &mut self,
20242 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
20243 cx: &mut Context<Self>,
20244 ) -> bool {
20245 self.display_map
20246 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
20247 }
20248
20249 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
20250 self.display_map.read(cx).fold_placeholder.clone()
20251 }
20252
20253 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
20254 self.buffer.update(cx, |buffer, cx| {
20255 buffer.set_all_diff_hunks_expanded(cx);
20256 });
20257 }
20258
20259 pub fn expand_all_diff_hunks(
20260 &mut self,
20261 _: &ExpandAllDiffHunks,
20262 _window: &mut Window,
20263 cx: &mut Context<Self>,
20264 ) {
20265 self.buffer.update(cx, |buffer, cx| {
20266 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20267 });
20268 }
20269
20270 pub fn collapse_all_diff_hunks(
20271 &mut self,
20272 _: &CollapseAllDiffHunks,
20273 _window: &mut Window,
20274 cx: &mut Context<Self>,
20275 ) {
20276 self.buffer.update(cx, |buffer, cx| {
20277 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20278 });
20279 }
20280
20281 pub fn toggle_selected_diff_hunks(
20282 &mut self,
20283 _: &ToggleSelectedDiffHunks,
20284 _window: &mut Window,
20285 cx: &mut Context<Self>,
20286 ) {
20287 let ranges: Vec<_> = self
20288 .selections
20289 .disjoint_anchors()
20290 .iter()
20291 .map(|s| s.range())
20292 .collect();
20293 self.toggle_diff_hunks_in_ranges(ranges, cx);
20294 }
20295
20296 pub fn diff_hunks_in_ranges<'a>(
20297 &'a self,
20298 ranges: &'a [Range<Anchor>],
20299 buffer: &'a MultiBufferSnapshot,
20300 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
20301 ranges.iter().flat_map(move |range| {
20302 let end_excerpt_id = range.end.excerpt_id;
20303 let range = range.to_point(buffer);
20304 let mut peek_end = range.end;
20305 if range.end.row < buffer.max_row().0 {
20306 peek_end = Point::new(range.end.row + 1, 0);
20307 }
20308 buffer
20309 .diff_hunks_in_range(range.start..peek_end)
20310 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
20311 })
20312 }
20313
20314 pub fn has_stageable_diff_hunks_in_ranges(
20315 &self,
20316 ranges: &[Range<Anchor>],
20317 snapshot: &MultiBufferSnapshot,
20318 ) -> bool {
20319 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
20320 hunks.any(|hunk| hunk.status().has_secondary_hunk())
20321 }
20322
20323 pub fn toggle_staged_selected_diff_hunks(
20324 &mut self,
20325 _: &::git::ToggleStaged,
20326 _: &mut Window,
20327 cx: &mut Context<Self>,
20328 ) {
20329 let snapshot = self.buffer.read(cx).snapshot(cx);
20330 let ranges: Vec<_> = self
20331 .selections
20332 .disjoint_anchors()
20333 .iter()
20334 .map(|s| s.range())
20335 .collect();
20336 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
20337 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20338 }
20339
20340 pub fn set_render_diff_hunk_controls(
20341 &mut self,
20342 render_diff_hunk_controls: RenderDiffHunkControlsFn,
20343 cx: &mut Context<Self>,
20344 ) {
20345 self.render_diff_hunk_controls = render_diff_hunk_controls;
20346 cx.notify();
20347 }
20348
20349 pub fn stage_and_next(
20350 &mut self,
20351 _: &::git::StageAndNext,
20352 window: &mut Window,
20353 cx: &mut Context<Self>,
20354 ) {
20355 self.do_stage_or_unstage_and_next(true, window, cx);
20356 }
20357
20358 pub fn unstage_and_next(
20359 &mut self,
20360 _: &::git::UnstageAndNext,
20361 window: &mut Window,
20362 cx: &mut Context<Self>,
20363 ) {
20364 self.do_stage_or_unstage_and_next(false, window, cx);
20365 }
20366
20367 pub fn stage_or_unstage_diff_hunks(
20368 &mut self,
20369 stage: bool,
20370 ranges: Vec<Range<Anchor>>,
20371 cx: &mut Context<Self>,
20372 ) {
20373 if self.delegate_stage_and_restore {
20374 let snapshot = self.buffer.read(cx).snapshot(cx);
20375 let hunks: Vec<_> = self.diff_hunks_in_ranges(&ranges, &snapshot).collect();
20376 if !hunks.is_empty() {
20377 cx.emit(EditorEvent::StageOrUnstageRequested { stage, hunks });
20378 }
20379 return;
20380 }
20381 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
20382 cx.spawn(async move |this, cx| {
20383 task.await?;
20384 this.update(cx, |this, cx| {
20385 let snapshot = this.buffer.read(cx).snapshot(cx);
20386 let chunk_by = this
20387 .diff_hunks_in_ranges(&ranges, &snapshot)
20388 .chunk_by(|hunk| hunk.buffer_id);
20389 for (buffer_id, hunks) in &chunk_by {
20390 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
20391 }
20392 })
20393 })
20394 .detach_and_log_err(cx);
20395 }
20396
20397 fn save_buffers_for_ranges_if_needed(
20398 &mut self,
20399 ranges: &[Range<Anchor>],
20400 cx: &mut Context<Editor>,
20401 ) -> Task<Result<()>> {
20402 let multibuffer = self.buffer.read(cx);
20403 let snapshot = multibuffer.read(cx);
20404 let buffer_ids: HashSet<_> = ranges
20405 .iter()
20406 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
20407 .collect();
20408 drop(snapshot);
20409
20410 let mut buffers = HashSet::default();
20411 for buffer_id in buffer_ids {
20412 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
20413 let buffer = buffer_entity.read(cx);
20414 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
20415 {
20416 buffers.insert(buffer_entity);
20417 }
20418 }
20419 }
20420
20421 if let Some(project) = &self.project {
20422 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
20423 } else {
20424 Task::ready(Ok(()))
20425 }
20426 }
20427
20428 fn do_stage_or_unstage_and_next(
20429 &mut self,
20430 stage: bool,
20431 window: &mut Window,
20432 cx: &mut Context<Self>,
20433 ) {
20434 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
20435
20436 if ranges.iter().any(|range| range.start != range.end) {
20437 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20438 return;
20439 }
20440
20441 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20442 let snapshot = self.snapshot(window, cx);
20443 let position = self
20444 .selections
20445 .newest::<Point>(&snapshot.display_snapshot)
20446 .head();
20447 let mut row = snapshot
20448 .buffer_snapshot()
20449 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
20450 .find(|hunk| hunk.row_range.start.0 > position.row)
20451 .map(|hunk| hunk.row_range.start);
20452
20453 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
20454 // Outside of the project diff editor, wrap around to the beginning.
20455 if !all_diff_hunks_expanded {
20456 row = row.or_else(|| {
20457 snapshot
20458 .buffer_snapshot()
20459 .diff_hunks_in_range(Point::zero()..position)
20460 .find(|hunk| hunk.row_range.end.0 < position.row)
20461 .map(|hunk| hunk.row_range.start)
20462 });
20463 }
20464
20465 if let Some(row) = row {
20466 let destination = Point::new(row.0, 0);
20467 let autoscroll = Autoscroll::center();
20468
20469 self.unfold_ranges(&[destination..destination], false, false, cx);
20470 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
20471 s.select_ranges([destination..destination]);
20472 });
20473 }
20474 }
20475
20476 pub(crate) fn do_stage_or_unstage(
20477 &self,
20478 stage: bool,
20479 buffer_id: BufferId,
20480 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
20481 cx: &mut App,
20482 ) -> Option<()> {
20483 let project = self.project()?;
20484 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
20485 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
20486 let buffer_snapshot = buffer.read(cx).snapshot();
20487 let file_exists = buffer_snapshot
20488 .file()
20489 .is_some_and(|file| file.disk_state().exists());
20490 diff.update(cx, |diff, cx| {
20491 diff.stage_or_unstage_hunks(
20492 stage,
20493 &hunks
20494 .map(|hunk| buffer_diff::DiffHunk {
20495 buffer_range: hunk.buffer_range,
20496 // We don't need to pass in word diffs here because they're only used for rendering and
20497 // this function changes internal state
20498 base_word_diffs: Vec::default(),
20499 buffer_word_diffs: Vec::default(),
20500 diff_base_byte_range: hunk.diff_base_byte_range.start.0
20501 ..hunk.diff_base_byte_range.end.0,
20502 secondary_status: hunk.status.secondary,
20503 range: Point::zero()..Point::zero(), // unused
20504 })
20505 .collect::<Vec<_>>(),
20506 &buffer_snapshot,
20507 file_exists,
20508 cx,
20509 )
20510 });
20511 None
20512 }
20513
20514 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
20515 let ranges: Vec<_> = self
20516 .selections
20517 .disjoint_anchors()
20518 .iter()
20519 .map(|s| s.range())
20520 .collect();
20521 self.buffer
20522 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
20523 }
20524
20525 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
20526 self.buffer.update(cx, |buffer, cx| {
20527 let ranges = vec![Anchor::min()..Anchor::max()];
20528 if !buffer.all_diff_hunks_expanded()
20529 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
20530 {
20531 buffer.collapse_diff_hunks(ranges, cx);
20532 true
20533 } else {
20534 false
20535 }
20536 })
20537 }
20538
20539 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
20540 if self.buffer.read(cx).all_diff_hunks_expanded() {
20541 return true;
20542 }
20543 let ranges = vec![Anchor::min()..Anchor::max()];
20544 self.buffer
20545 .read(cx)
20546 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
20547 }
20548
20549 fn toggle_diff_hunks_in_ranges(
20550 &mut self,
20551 ranges: Vec<Range<Anchor>>,
20552 cx: &mut Context<Editor>,
20553 ) {
20554 self.buffer.update(cx, |buffer, cx| {
20555 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
20556 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
20557 })
20558 }
20559
20560 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
20561 self.buffer.update(cx, |buffer, cx| {
20562 let snapshot = buffer.snapshot(cx);
20563 let excerpt_id = range.end.excerpt_id;
20564 let point_range = range.to_point(&snapshot);
20565 let expand = !buffer.single_hunk_is_expanded(range, cx);
20566 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
20567 })
20568 }
20569
20570 pub(crate) fn apply_all_diff_hunks(
20571 &mut self,
20572 _: &ApplyAllDiffHunks,
20573 window: &mut Window,
20574 cx: &mut Context<Self>,
20575 ) {
20576 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20577
20578 let buffers = self.buffer.read(cx).all_buffers();
20579 for branch_buffer in buffers {
20580 branch_buffer.update(cx, |branch_buffer, cx| {
20581 branch_buffer.merge_into_base(Vec::new(), cx);
20582 });
20583 }
20584
20585 if let Some(project) = self.project.clone() {
20586 self.save(
20587 SaveOptions {
20588 format: true,
20589 autosave: false,
20590 },
20591 project,
20592 window,
20593 cx,
20594 )
20595 .detach_and_log_err(cx);
20596 }
20597 }
20598
20599 pub(crate) fn apply_selected_diff_hunks(
20600 &mut self,
20601 _: &ApplyDiffHunk,
20602 window: &mut Window,
20603 cx: &mut Context<Self>,
20604 ) {
20605 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20606 let snapshot = self.snapshot(window, cx);
20607 let hunks = snapshot.hunks_for_ranges(
20608 self.selections
20609 .all(&snapshot.display_snapshot)
20610 .into_iter()
20611 .map(|selection| selection.range()),
20612 );
20613 let mut ranges_by_buffer = HashMap::default();
20614 self.transact(window, cx, |editor, _window, cx| {
20615 for hunk in hunks {
20616 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
20617 ranges_by_buffer
20618 .entry(buffer.clone())
20619 .or_insert_with(Vec::new)
20620 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
20621 }
20622 }
20623
20624 for (buffer, ranges) in ranges_by_buffer {
20625 buffer.update(cx, |buffer, cx| {
20626 buffer.merge_into_base(ranges, cx);
20627 });
20628 }
20629 });
20630
20631 if let Some(project) = self.project.clone() {
20632 self.save(
20633 SaveOptions {
20634 format: true,
20635 autosave: false,
20636 },
20637 project,
20638 window,
20639 cx,
20640 )
20641 .detach_and_log_err(cx);
20642 }
20643 }
20644
20645 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
20646 if hovered != self.gutter_hovered {
20647 self.gutter_hovered = hovered;
20648 cx.notify();
20649 }
20650 }
20651
20652 pub fn insert_blocks(
20653 &mut self,
20654 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
20655 autoscroll: Option<Autoscroll>,
20656 cx: &mut Context<Self>,
20657 ) -> Vec<CustomBlockId> {
20658 let blocks = self
20659 .display_map
20660 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
20661 if let Some(autoscroll) = autoscroll {
20662 self.request_autoscroll(autoscroll, cx);
20663 }
20664 cx.notify();
20665 blocks
20666 }
20667
20668 pub fn resize_blocks(
20669 &mut self,
20670 heights: HashMap<CustomBlockId, u32>,
20671 autoscroll: Option<Autoscroll>,
20672 cx: &mut Context<Self>,
20673 ) {
20674 self.display_map
20675 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
20676 if let Some(autoscroll) = autoscroll {
20677 self.request_autoscroll(autoscroll, cx);
20678 }
20679 cx.notify();
20680 }
20681
20682 pub fn replace_blocks(
20683 &mut self,
20684 renderers: HashMap<CustomBlockId, RenderBlock>,
20685 autoscroll: Option<Autoscroll>,
20686 cx: &mut Context<Self>,
20687 ) {
20688 self.display_map
20689 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
20690 if let Some(autoscroll) = autoscroll {
20691 self.request_autoscroll(autoscroll, cx);
20692 }
20693 cx.notify();
20694 }
20695
20696 pub fn remove_blocks(
20697 &mut self,
20698 block_ids: HashSet<CustomBlockId>,
20699 autoscroll: Option<Autoscroll>,
20700 cx: &mut Context<Self>,
20701 ) {
20702 self.display_map.update(cx, |display_map, cx| {
20703 display_map.remove_blocks(block_ids, cx)
20704 });
20705 if let Some(autoscroll) = autoscroll {
20706 self.request_autoscroll(autoscroll, cx);
20707 }
20708 cx.notify();
20709 }
20710
20711 pub fn row_for_block(
20712 &self,
20713 block_id: CustomBlockId,
20714 cx: &mut Context<Self>,
20715 ) -> Option<DisplayRow> {
20716 self.display_map
20717 .update(cx, |map, cx| map.row_for_block(block_id, cx))
20718 }
20719
20720 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
20721 self.focused_block = Some(focused_block);
20722 }
20723
20724 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
20725 self.focused_block.take()
20726 }
20727
20728 pub fn insert_creases(
20729 &mut self,
20730 creases: impl IntoIterator<Item = Crease<Anchor>>,
20731 cx: &mut Context<Self>,
20732 ) -> Vec<CreaseId> {
20733 self.display_map
20734 .update(cx, |map, cx| map.insert_creases(creases, cx))
20735 }
20736
20737 pub fn remove_creases(
20738 &mut self,
20739 ids: impl IntoIterator<Item = CreaseId>,
20740 cx: &mut Context<Self>,
20741 ) -> Vec<(CreaseId, Range<Anchor>)> {
20742 self.display_map
20743 .update(cx, |map, cx| map.remove_creases(ids, cx))
20744 }
20745
20746 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
20747 self.display_map
20748 .update(cx, |map, cx| map.snapshot(cx))
20749 .longest_row()
20750 }
20751
20752 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
20753 self.display_map
20754 .update(cx, |map, cx| map.snapshot(cx))
20755 .max_point()
20756 }
20757
20758 pub fn text(&self, cx: &App) -> String {
20759 self.buffer.read(cx).read(cx).text()
20760 }
20761
20762 pub fn is_empty(&self, cx: &App) -> bool {
20763 self.buffer.read(cx).read(cx).is_empty()
20764 }
20765
20766 pub fn text_option(&self, cx: &App) -> Option<String> {
20767 let text = self.text(cx);
20768 let text = text.trim();
20769
20770 if text.is_empty() {
20771 return None;
20772 }
20773
20774 Some(text.to_string())
20775 }
20776
20777 pub fn set_text(
20778 &mut self,
20779 text: impl Into<Arc<str>>,
20780 window: &mut Window,
20781 cx: &mut Context<Self>,
20782 ) {
20783 self.transact(window, cx, |this, _, cx| {
20784 this.buffer
20785 .read(cx)
20786 .as_singleton()
20787 .expect("you can only call set_text on editors for singleton buffers")
20788 .update(cx, |buffer, cx| buffer.set_text(text, cx));
20789 });
20790 }
20791
20792 pub fn display_text(&self, cx: &mut App) -> String {
20793 self.display_map
20794 .update(cx, |map, cx| map.snapshot(cx))
20795 .text()
20796 }
20797
20798 fn create_minimap(
20799 &self,
20800 minimap_settings: MinimapSettings,
20801 window: &mut Window,
20802 cx: &mut Context<Self>,
20803 ) -> Option<Entity<Self>> {
20804 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
20805 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
20806 }
20807
20808 fn initialize_new_minimap(
20809 &self,
20810 minimap_settings: MinimapSettings,
20811 window: &mut Window,
20812 cx: &mut Context<Self>,
20813 ) -> Entity<Self> {
20814 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
20815 const MINIMAP_FONT_FAMILY: SharedString = SharedString::new_static(".ZedMono");
20816
20817 let mut minimap = Editor::new_internal(
20818 EditorMode::Minimap {
20819 parent: cx.weak_entity(),
20820 },
20821 self.buffer.clone(),
20822 None,
20823 Some(self.display_map.clone()),
20824 window,
20825 cx,
20826 );
20827 let my_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20828 let minimap_snapshot = minimap.display_map.update(cx, |map, cx| map.snapshot(cx));
20829 minimap.scroll_manager.clone_state(
20830 &self.scroll_manager,
20831 &my_snapshot,
20832 &minimap_snapshot,
20833 cx,
20834 );
20835 minimap.set_text_style_refinement(TextStyleRefinement {
20836 font_size: Some(MINIMAP_FONT_SIZE),
20837 font_weight: Some(MINIMAP_FONT_WEIGHT),
20838 font_family: Some(MINIMAP_FONT_FAMILY),
20839 ..Default::default()
20840 });
20841 minimap.update_minimap_configuration(minimap_settings, cx);
20842 cx.new(|_| minimap)
20843 }
20844
20845 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
20846 let current_line_highlight = minimap_settings
20847 .current_line_highlight
20848 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
20849 self.set_current_line_highlight(Some(current_line_highlight));
20850 }
20851
20852 pub fn minimap(&self) -> Option<&Entity<Self>> {
20853 self.minimap
20854 .as_ref()
20855 .filter(|_| self.minimap_visibility.visible())
20856 }
20857
20858 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
20859 let mut wrap_guides = smallvec![];
20860
20861 if self.show_wrap_guides == Some(false) {
20862 return wrap_guides;
20863 }
20864
20865 let settings = self.buffer.read(cx).language_settings(cx);
20866 if settings.show_wrap_guides {
20867 match self.soft_wrap_mode(cx) {
20868 SoftWrap::Column(soft_wrap) => {
20869 wrap_guides.push((soft_wrap as usize, true));
20870 }
20871 SoftWrap::Bounded(soft_wrap) => {
20872 wrap_guides.push((soft_wrap as usize, true));
20873 }
20874 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
20875 }
20876 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
20877 }
20878
20879 wrap_guides
20880 }
20881
20882 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
20883 let settings = self.buffer.read(cx).language_settings(cx);
20884 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
20885 match mode {
20886 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
20887 SoftWrap::None
20888 }
20889 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
20890 language_settings::SoftWrap::PreferredLineLength => {
20891 SoftWrap::Column(settings.preferred_line_length)
20892 }
20893 language_settings::SoftWrap::Bounded => {
20894 SoftWrap::Bounded(settings.preferred_line_length)
20895 }
20896 }
20897 }
20898
20899 pub fn set_soft_wrap_mode(
20900 &mut self,
20901 mode: language_settings::SoftWrap,
20902 cx: &mut Context<Self>,
20903 ) {
20904 self.soft_wrap_mode_override = Some(mode);
20905 cx.notify();
20906 }
20907
20908 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
20909 self.hard_wrap = hard_wrap;
20910 cx.notify();
20911 }
20912
20913 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
20914 self.text_style_refinement = Some(style);
20915 }
20916
20917 /// called by the Element so we know what style we were most recently rendered with.
20918 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
20919 // We intentionally do not inform the display map about the minimap style
20920 // so that wrapping is not recalculated and stays consistent for the editor
20921 // and its linked minimap.
20922 if !self.mode.is_minimap() {
20923 let font = style.text.font();
20924 let font_size = style.text.font_size.to_pixels(window.rem_size());
20925 let display_map = self
20926 .placeholder_display_map
20927 .as_ref()
20928 .filter(|_| self.is_empty(cx))
20929 .unwrap_or(&self.display_map);
20930
20931 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
20932 }
20933 self.style = Some(style);
20934 }
20935
20936 pub fn style(&mut self, cx: &App) -> &EditorStyle {
20937 if self.style.is_none() {
20938 self.style = Some(self.create_style(cx));
20939 }
20940 self.style.as_ref().unwrap()
20941 }
20942
20943 // Called by the element. This method is not designed to be called outside of the editor
20944 // element's layout code because it does not notify when rewrapping is computed synchronously.
20945 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
20946 if self.is_empty(cx) {
20947 self.placeholder_display_map
20948 .as_ref()
20949 .map_or(false, |display_map| {
20950 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
20951 })
20952 } else {
20953 self.display_map
20954 .update(cx, |map, cx| map.set_wrap_width(width, cx))
20955 }
20956 }
20957
20958 pub fn set_soft_wrap(&mut self) {
20959 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
20960 }
20961
20962 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
20963 if self.soft_wrap_mode_override.is_some() {
20964 self.soft_wrap_mode_override.take();
20965 } else {
20966 let soft_wrap = match self.soft_wrap_mode(cx) {
20967 SoftWrap::GitDiff => return,
20968 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
20969 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
20970 language_settings::SoftWrap::None
20971 }
20972 };
20973 self.soft_wrap_mode_override = Some(soft_wrap);
20974 }
20975 cx.notify();
20976 }
20977
20978 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
20979 let Some(workspace) = self.workspace() else {
20980 return;
20981 };
20982 let fs = workspace.read(cx).app_state().fs.clone();
20983 let current_show = TabBarSettings::get_global(cx).show;
20984 update_settings_file(fs, cx, move |setting, _| {
20985 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
20986 });
20987 }
20988
20989 pub fn toggle_indent_guides(
20990 &mut self,
20991 _: &ToggleIndentGuides,
20992 _: &mut Window,
20993 cx: &mut Context<Self>,
20994 ) {
20995 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
20996 self.buffer
20997 .read(cx)
20998 .language_settings(cx)
20999 .indent_guides
21000 .enabled
21001 });
21002 self.show_indent_guides = Some(!currently_enabled);
21003 cx.notify();
21004 }
21005
21006 fn should_show_indent_guides(&self) -> Option<bool> {
21007 self.show_indent_guides
21008 }
21009
21010 pub fn disable_indent_guides_for_buffer(
21011 &mut self,
21012 buffer_id: BufferId,
21013 cx: &mut Context<Self>,
21014 ) {
21015 self.buffers_with_disabled_indent_guides.insert(buffer_id);
21016 cx.notify();
21017 }
21018
21019 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
21020 self.buffers_with_disabled_indent_guides
21021 .contains(&buffer_id)
21022 }
21023
21024 pub fn toggle_line_numbers(
21025 &mut self,
21026 _: &ToggleLineNumbers,
21027 _: &mut Window,
21028 cx: &mut Context<Self>,
21029 ) {
21030 let mut editor_settings = EditorSettings::get_global(cx).clone();
21031 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
21032 EditorSettings::override_global(editor_settings, cx);
21033 }
21034
21035 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
21036 if let Some(show_line_numbers) = self.show_line_numbers {
21037 return show_line_numbers;
21038 }
21039 EditorSettings::get_global(cx).gutter.line_numbers
21040 }
21041
21042 pub fn relative_line_numbers(&self, cx: &App) -> RelativeLineNumbers {
21043 match (
21044 self.use_relative_line_numbers,
21045 EditorSettings::get_global(cx).relative_line_numbers,
21046 ) {
21047 (None, setting) => setting,
21048 (Some(false), _) => RelativeLineNumbers::Disabled,
21049 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
21050 (Some(true), _) => RelativeLineNumbers::Enabled,
21051 }
21052 }
21053
21054 pub fn toggle_relative_line_numbers(
21055 &mut self,
21056 _: &ToggleRelativeLineNumbers,
21057 _: &mut Window,
21058 cx: &mut Context<Self>,
21059 ) {
21060 let is_relative = self.relative_line_numbers(cx);
21061 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
21062 }
21063
21064 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
21065 self.use_relative_line_numbers = is_relative;
21066 cx.notify();
21067 }
21068
21069 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
21070 self.show_gutter = show_gutter;
21071 cx.notify();
21072 }
21073
21074 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
21075 self.show_scrollbars = ScrollbarAxes {
21076 horizontal: show,
21077 vertical: show,
21078 };
21079 cx.notify();
21080 }
21081
21082 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21083 self.show_scrollbars.vertical = show;
21084 cx.notify();
21085 }
21086
21087 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21088 self.show_scrollbars.horizontal = show;
21089 cx.notify();
21090 }
21091
21092 pub fn set_minimap_visibility(
21093 &mut self,
21094 minimap_visibility: MinimapVisibility,
21095 window: &mut Window,
21096 cx: &mut Context<Self>,
21097 ) {
21098 if self.minimap_visibility != minimap_visibility {
21099 if minimap_visibility.visible() && self.minimap.is_none() {
21100 let minimap_settings = EditorSettings::get_global(cx).minimap;
21101 self.minimap =
21102 self.create_minimap(minimap_settings.with_show_override(), window, cx);
21103 }
21104 self.minimap_visibility = minimap_visibility;
21105 cx.notify();
21106 }
21107 }
21108
21109 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21110 self.set_show_scrollbars(false, cx);
21111 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
21112 }
21113
21114 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21115 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
21116 }
21117
21118 /// Normally the text in full mode and auto height editors is padded on the
21119 /// left side by roughly half a character width for improved hit testing.
21120 ///
21121 /// Use this method to disable this for cases where this is not wanted (e.g.
21122 /// if you want to align the editor text with some other text above or below)
21123 /// or if you want to add this padding to single-line editors.
21124 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
21125 self.offset_content = offset_content;
21126 cx.notify();
21127 }
21128
21129 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
21130 self.show_line_numbers = Some(show_line_numbers);
21131 cx.notify();
21132 }
21133
21134 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
21135 self.disable_expand_excerpt_buttons = true;
21136 cx.notify();
21137 }
21138
21139 pub fn set_number_deleted_lines(&mut self, number: bool, cx: &mut Context<Self>) {
21140 self.number_deleted_lines = number;
21141 cx.notify();
21142 }
21143
21144 pub fn set_delegate_expand_excerpts(&mut self, delegate: bool) {
21145 self.delegate_expand_excerpts = delegate;
21146 }
21147
21148 pub fn set_delegate_stage_and_restore(&mut self, delegate: bool) {
21149 self.delegate_stage_and_restore = delegate;
21150 }
21151
21152 pub fn set_on_local_selections_changed(
21153 &mut self,
21154 callback: Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
21155 ) {
21156 self.on_local_selections_changed = callback;
21157 }
21158
21159 pub fn set_suppress_selection_callback(&mut self, suppress: bool) {
21160 self.suppress_selection_callback = suppress;
21161 }
21162
21163 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
21164 self.show_git_diff_gutter = Some(show_git_diff_gutter);
21165 cx.notify();
21166 }
21167
21168 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
21169 self.show_code_actions = Some(show_code_actions);
21170 cx.notify();
21171 }
21172
21173 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
21174 self.show_runnables = Some(show_runnables);
21175 cx.notify();
21176 }
21177
21178 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
21179 self.show_breakpoints = Some(show_breakpoints);
21180 cx.notify();
21181 }
21182
21183 pub fn set_show_diff_review_button(&mut self, show: bool, cx: &mut Context<Self>) {
21184 self.show_diff_review_button = show;
21185 cx.notify();
21186 }
21187
21188 pub fn show_diff_review_button(&self) -> bool {
21189 self.show_diff_review_button
21190 }
21191
21192 pub fn render_diff_review_button(
21193 &self,
21194 display_row: DisplayRow,
21195 width: Pixels,
21196 cx: &mut Context<Self>,
21197 ) -> impl IntoElement {
21198 let text_color = cx.theme().colors().text;
21199 let icon_color = cx.theme().colors().icon_accent;
21200
21201 h_flex()
21202 .id("diff_review_button")
21203 .cursor_pointer()
21204 .w(width - px(1.))
21205 .h(relative(0.9))
21206 .justify_center()
21207 .rounded_sm()
21208 .border_1()
21209 .border_color(text_color.opacity(0.1))
21210 .bg(text_color.opacity(0.15))
21211 .hover(|s| {
21212 s.bg(icon_color.opacity(0.4))
21213 .border_color(icon_color.opacity(0.5))
21214 })
21215 .child(Icon::new(IconName::Plus).size(IconSize::Small))
21216 .tooltip(Tooltip::text("Add Review (drag to select multiple lines)"))
21217 .on_mouse_down(
21218 gpui::MouseButton::Left,
21219 cx.listener(move |editor, _event: &gpui::MouseDownEvent, window, cx| {
21220 editor.start_diff_review_drag(display_row, window, cx);
21221 }),
21222 )
21223 }
21224
21225 pub fn start_diff_review_drag(
21226 &mut self,
21227 display_row: DisplayRow,
21228 window: &mut Window,
21229 cx: &mut Context<Self>,
21230 ) {
21231 let snapshot = self.snapshot(window, cx);
21232 let point = snapshot
21233 .display_snapshot
21234 .display_point_to_point(DisplayPoint::new(display_row, 0), Bias::Left);
21235 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21236 self.diff_review_drag_state = Some(DiffReviewDragState {
21237 start_anchor: anchor,
21238 current_anchor: anchor,
21239 });
21240 cx.notify();
21241 }
21242
21243 pub fn update_diff_review_drag(
21244 &mut self,
21245 display_row: DisplayRow,
21246 window: &mut Window,
21247 cx: &mut Context<Self>,
21248 ) {
21249 if self.diff_review_drag_state.is_none() {
21250 return;
21251 }
21252 let snapshot = self.snapshot(window, cx);
21253 let point = snapshot
21254 .display_snapshot
21255 .display_point_to_point(display_row.as_display_point(), Bias::Left);
21256 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21257 if let Some(drag_state) = &mut self.diff_review_drag_state {
21258 drag_state.current_anchor = anchor;
21259 cx.notify();
21260 }
21261 }
21262
21263 pub fn end_diff_review_drag(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21264 if let Some(drag_state) = self.diff_review_drag_state.take() {
21265 let snapshot = self.snapshot(window, cx);
21266 let range = drag_state.row_range(&snapshot.display_snapshot);
21267 self.show_diff_review_overlay(*range.start()..*range.end(), window, cx);
21268 }
21269 cx.notify();
21270 }
21271
21272 pub fn cancel_diff_review_drag(&mut self, cx: &mut Context<Self>) {
21273 self.diff_review_drag_state = None;
21274 cx.notify();
21275 }
21276
21277 /// Calculates the appropriate block height for the diff review overlay.
21278 /// Height is in lines: 2 for input row, 1 for header when comments exist,
21279 /// and 2 lines per comment when expanded.
21280 fn calculate_overlay_height(
21281 &self,
21282 hunk_key: &DiffHunkKey,
21283 comments_expanded: bool,
21284 snapshot: &MultiBufferSnapshot,
21285 ) -> u32 {
21286 let comment_count = self.hunk_comment_count(hunk_key, snapshot);
21287 let base_height: u32 = 2; // Input row with avatar and buttons
21288
21289 if comment_count == 0 {
21290 base_height
21291 } else if comments_expanded {
21292 // Header (1 line) + 2 lines per comment
21293 base_height + 1 + (comment_count as u32 * 2)
21294 } else {
21295 // Just header when collapsed
21296 base_height + 1
21297 }
21298 }
21299
21300 pub fn show_diff_review_overlay(
21301 &mut self,
21302 display_range: Range<DisplayRow>,
21303 window: &mut Window,
21304 cx: &mut Context<Self>,
21305 ) {
21306 let Range { start, end } = display_range.sorted();
21307
21308 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21309 let editor_snapshot = self.snapshot(window, cx);
21310
21311 // Convert display rows to multibuffer points
21312 let start_point = editor_snapshot
21313 .display_snapshot
21314 .display_point_to_point(start.as_display_point(), Bias::Left);
21315 let end_point = editor_snapshot
21316 .display_snapshot
21317 .display_point_to_point(end.as_display_point(), Bias::Left);
21318 let end_multi_buffer_row = MultiBufferRow(end_point.row);
21319
21320 // Create anchor range for the selected lines (start of first line to end of last line)
21321 let line_end = Point::new(
21322 end_point.row,
21323 buffer_snapshot.line_len(end_multi_buffer_row),
21324 );
21325 let anchor_range =
21326 buffer_snapshot.anchor_after(start_point)..buffer_snapshot.anchor_before(line_end);
21327
21328 // Compute the hunk key for this display row
21329 let file_path = buffer_snapshot
21330 .file_at(start_point)
21331 .map(|file: &Arc<dyn language::File>| file.path().clone())
21332 .unwrap_or_else(|| Arc::from(util::rel_path::RelPath::empty()));
21333 let hunk_start_anchor = buffer_snapshot.anchor_before(start_point);
21334 let new_hunk_key = DiffHunkKey {
21335 file_path,
21336 hunk_start_anchor,
21337 };
21338
21339 // Check if we already have an overlay for this hunk
21340 if let Some(existing_overlay) = self.diff_review_overlays.iter().find(|overlay| {
21341 Self::hunk_keys_match(&overlay.hunk_key, &new_hunk_key, &buffer_snapshot)
21342 }) {
21343 // Just focus the existing overlay's prompt editor
21344 let focus_handle = existing_overlay.prompt_editor.focus_handle(cx);
21345 window.focus(&focus_handle, cx);
21346 return;
21347 }
21348
21349 // Dismiss overlays that have no comments for their hunks
21350 self.dismiss_overlays_without_comments(cx);
21351
21352 // Get the current user's avatar URI from the project's user_store
21353 let user_avatar_uri = self.project.as_ref().and_then(|project| {
21354 let user_store = project.read(cx).user_store();
21355 user_store
21356 .read(cx)
21357 .current_user()
21358 .map(|user| user.avatar_uri.clone())
21359 });
21360
21361 // Create anchor at the end of the last row so the block appears immediately below it
21362 // Use multibuffer coordinates for anchor creation
21363 let line_len = buffer_snapshot.line_len(end_multi_buffer_row);
21364 let anchor = buffer_snapshot.anchor_after(Point::new(end_multi_buffer_row.0, line_len));
21365
21366 // Use the hunk key we already computed
21367 let hunk_key = new_hunk_key;
21368
21369 // Create the prompt editor for the review input
21370 let prompt_editor = cx.new(|cx| {
21371 let mut editor = Editor::single_line(window, cx);
21372 editor.set_placeholder_text("Add a review comment...", window, cx);
21373 editor
21374 });
21375
21376 // Register the Newline action on the prompt editor to submit the review
21377 let parent_editor = cx.entity().downgrade();
21378 let subscription = prompt_editor.update(cx, |prompt_editor, _cx| {
21379 prompt_editor.register_action({
21380 let parent_editor = parent_editor.clone();
21381 move |_: &crate::actions::Newline, window, cx| {
21382 if let Some(editor) = parent_editor.upgrade() {
21383 editor.update(cx, |editor, cx| {
21384 editor.submit_diff_review_comment(window, cx);
21385 });
21386 }
21387 }
21388 })
21389 });
21390
21391 // Calculate initial height based on existing comments for this hunk
21392 let initial_height = self.calculate_overlay_height(&hunk_key, true, &buffer_snapshot);
21393
21394 // Create the overlay block
21395 let prompt_editor_for_render = prompt_editor.clone();
21396 let hunk_key_for_render = hunk_key.clone();
21397 let editor_handle = cx.entity().downgrade();
21398 let block = BlockProperties {
21399 style: BlockStyle::Sticky,
21400 placement: BlockPlacement::Below(anchor),
21401 height: Some(initial_height),
21402 render: Arc::new(move |cx| {
21403 Self::render_diff_review_overlay(
21404 &prompt_editor_for_render,
21405 &hunk_key_for_render,
21406 &editor_handle,
21407 cx,
21408 )
21409 }),
21410 priority: 0,
21411 };
21412
21413 let block_ids = self.insert_blocks([block], None, cx);
21414 let Some(block_id) = block_ids.into_iter().next() else {
21415 log::error!("Failed to insert diff review overlay block");
21416 return;
21417 };
21418
21419 self.diff_review_overlays.push(DiffReviewOverlay {
21420 anchor_range,
21421 block_id,
21422 prompt_editor: prompt_editor.clone(),
21423 hunk_key,
21424 comments_expanded: true,
21425 inline_edit_editors: HashMap::default(),
21426 inline_edit_subscriptions: HashMap::default(),
21427 user_avatar_uri,
21428 _subscription: subscription,
21429 });
21430
21431 // Focus the prompt editor
21432 let focus_handle = prompt_editor.focus_handle(cx);
21433 window.focus(&focus_handle, cx);
21434
21435 cx.notify();
21436 }
21437
21438 /// Dismisses all diff review overlays.
21439 pub fn dismiss_all_diff_review_overlays(&mut self, cx: &mut Context<Self>) {
21440 if self.diff_review_overlays.is_empty() {
21441 return;
21442 }
21443 let block_ids: HashSet<_> = self
21444 .diff_review_overlays
21445 .drain(..)
21446 .map(|overlay| overlay.block_id)
21447 .collect();
21448 self.remove_blocks(block_ids, None, cx);
21449 cx.notify();
21450 }
21451
21452 /// Dismisses overlays that have no comments stored for their hunks.
21453 /// Keeps overlays that have at least one comment.
21454 fn dismiss_overlays_without_comments(&mut self, cx: &mut Context<Self>) {
21455 let snapshot = self.buffer.read(cx).snapshot(cx);
21456
21457 // First, compute which overlays have comments (to avoid borrow issues with retain)
21458 let overlays_with_comments: Vec<bool> = self
21459 .diff_review_overlays
21460 .iter()
21461 .map(|overlay| self.hunk_comment_count(&overlay.hunk_key, &snapshot) > 0)
21462 .collect();
21463
21464 // Now collect block IDs to remove and retain overlays
21465 let mut block_ids_to_remove = HashSet::default();
21466 let mut index = 0;
21467 self.diff_review_overlays.retain(|overlay| {
21468 let has_comments = overlays_with_comments[index];
21469 index += 1;
21470 if !has_comments {
21471 block_ids_to_remove.insert(overlay.block_id);
21472 }
21473 has_comments
21474 });
21475
21476 if !block_ids_to_remove.is_empty() {
21477 self.remove_blocks(block_ids_to_remove, None, cx);
21478 cx.notify();
21479 }
21480 }
21481
21482 /// Refreshes the diff review overlay block to update its height and render function.
21483 /// Uses resize_blocks and replace_blocks to avoid visual flicker from remove+insert.
21484 fn refresh_diff_review_overlay_height(
21485 &mut self,
21486 hunk_key: &DiffHunkKey,
21487 _window: &mut Window,
21488 cx: &mut Context<Self>,
21489 ) {
21490 // Extract all needed data from overlay first to avoid borrow conflicts
21491 let snapshot = self.buffer.read(cx).snapshot(cx);
21492 let (comments_expanded, block_id, prompt_editor) = {
21493 let Some(overlay) = self
21494 .diff_review_overlays
21495 .iter()
21496 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
21497 else {
21498 return;
21499 };
21500
21501 (
21502 overlay.comments_expanded,
21503 overlay.block_id,
21504 overlay.prompt_editor.clone(),
21505 )
21506 };
21507
21508 // Calculate new height
21509 let snapshot = self.buffer.read(cx).snapshot(cx);
21510 let new_height = self.calculate_overlay_height(hunk_key, comments_expanded, &snapshot);
21511
21512 // Update the block height using resize_blocks (avoids flicker)
21513 let mut heights = HashMap::default();
21514 heights.insert(block_id, new_height);
21515 self.resize_blocks(heights, None, cx);
21516
21517 // Update the render function using replace_blocks (avoids flicker)
21518 let hunk_key_for_render = hunk_key.clone();
21519 let editor_handle = cx.entity().downgrade();
21520 let render: Arc<dyn Fn(&mut BlockContext) -> AnyElement + Send + Sync> =
21521 Arc::new(move |cx| {
21522 Self::render_diff_review_overlay(
21523 &prompt_editor,
21524 &hunk_key_for_render,
21525 &editor_handle,
21526 cx,
21527 )
21528 });
21529
21530 let mut renderers = HashMap::default();
21531 renderers.insert(block_id, render);
21532 self.replace_blocks(renderers, None, cx);
21533 }
21534
21535 /// Action handler for SubmitDiffReviewComment.
21536 pub fn submit_diff_review_comment_action(
21537 &mut self,
21538 _: &SubmitDiffReviewComment,
21539 window: &mut Window,
21540 cx: &mut Context<Self>,
21541 ) {
21542 self.submit_diff_review_comment(window, cx);
21543 }
21544
21545 /// Stores the diff review comment locally.
21546 /// Comments are stored per-hunk and can later be batch-submitted to the Agent panel.
21547 pub fn submit_diff_review_comment(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21548 // Find the overlay that currently has focus
21549 let overlay_index = self
21550 .diff_review_overlays
21551 .iter()
21552 .position(|overlay| overlay.prompt_editor.focus_handle(cx).is_focused(window));
21553 let Some(overlay_index) = overlay_index else {
21554 return;
21555 };
21556 let overlay = &self.diff_review_overlays[overlay_index];
21557
21558 let comment_text = overlay.prompt_editor.read(cx).text(cx).trim().to_string();
21559 if comment_text.is_empty() {
21560 return;
21561 }
21562
21563 let anchor_range = overlay.anchor_range.clone();
21564 let hunk_key = overlay.hunk_key.clone();
21565
21566 self.add_review_comment(hunk_key.clone(), comment_text, anchor_range, cx);
21567
21568 // Clear the prompt editor but keep the overlay open
21569 if let Some(overlay) = self.diff_review_overlays.get(overlay_index) {
21570 overlay.prompt_editor.update(cx, |editor, cx| {
21571 editor.clear(window, cx);
21572 });
21573 }
21574
21575 // Refresh the overlay to update the block height for the new comment
21576 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
21577
21578 cx.notify();
21579 }
21580
21581 /// Returns the prompt editor for the diff review overlay, if one is active.
21582 /// This is primarily used for testing.
21583 pub fn diff_review_prompt_editor(&self) -> Option<&Entity<Editor>> {
21584 self.diff_review_overlays
21585 .first()
21586 .map(|overlay| &overlay.prompt_editor)
21587 }
21588
21589 /// Returns the line range for the first diff review overlay, if one is active.
21590 /// Returns (start_row, end_row) as physical line numbers in the underlying file.
21591 pub fn diff_review_line_range(&self, cx: &App) -> Option<(u32, u32)> {
21592 let overlay = self.diff_review_overlays.first()?;
21593 let snapshot = self.buffer.read(cx).snapshot(cx);
21594 let start_point = overlay.anchor_range.start.to_point(&snapshot);
21595 let end_point = overlay.anchor_range.end.to_point(&snapshot);
21596 let start_row = snapshot
21597 .point_to_buffer_point(start_point)
21598 .map(|(_, p, _)| p.row)
21599 .unwrap_or(start_point.row);
21600 let end_row = snapshot
21601 .point_to_buffer_point(end_point)
21602 .map(|(_, p, _)| p.row)
21603 .unwrap_or(end_point.row);
21604 Some((start_row, end_row))
21605 }
21606
21607 /// Sets whether the comments section is expanded in the diff review overlay.
21608 /// This is primarily used for testing.
21609 pub fn set_diff_review_comments_expanded(&mut self, expanded: bool, cx: &mut Context<Self>) {
21610 for overlay in &mut self.diff_review_overlays {
21611 overlay.comments_expanded = expanded;
21612 }
21613 cx.notify();
21614 }
21615
21616 /// Compares two DiffHunkKeys for equality by resolving their anchors.
21617 fn hunk_keys_match(a: &DiffHunkKey, b: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> bool {
21618 a.file_path == b.file_path
21619 && a.hunk_start_anchor.to_point(snapshot) == b.hunk_start_anchor.to_point(snapshot)
21620 }
21621
21622 /// Returns comments for a specific hunk, ordered by creation time.
21623 pub fn comments_for_hunk<'a>(
21624 &'a self,
21625 key: &DiffHunkKey,
21626 snapshot: &MultiBufferSnapshot,
21627 ) -> &'a [StoredReviewComment] {
21628 let key_point = key.hunk_start_anchor.to_point(snapshot);
21629 self.stored_review_comments
21630 .iter()
21631 .find(|(k, _)| {
21632 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
21633 })
21634 .map(|(_, comments)| comments.as_slice())
21635 .unwrap_or(&[])
21636 }
21637
21638 /// Returns the total count of stored review comments across all hunks.
21639 pub fn total_review_comment_count(&self) -> usize {
21640 self.stored_review_comments
21641 .iter()
21642 .map(|(_, v)| v.len())
21643 .sum()
21644 }
21645
21646 /// Returns the count of comments for a specific hunk.
21647 pub fn hunk_comment_count(&self, key: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> usize {
21648 let key_point = key.hunk_start_anchor.to_point(snapshot);
21649 self.stored_review_comments
21650 .iter()
21651 .find(|(k, _)| {
21652 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
21653 })
21654 .map(|(_, v)| v.len())
21655 .unwrap_or(0)
21656 }
21657
21658 /// Adds a new review comment to a specific hunk.
21659 pub fn add_review_comment(
21660 &mut self,
21661 hunk_key: DiffHunkKey,
21662 comment: String,
21663 anchor_range: Range<Anchor>,
21664 cx: &mut Context<Self>,
21665 ) -> usize {
21666 let id = self.next_review_comment_id;
21667 self.next_review_comment_id += 1;
21668
21669 let stored_comment = StoredReviewComment::new(id, comment, anchor_range);
21670
21671 let snapshot = self.buffer.read(cx).snapshot(cx);
21672 let key_point = hunk_key.hunk_start_anchor.to_point(&snapshot);
21673
21674 // Find existing entry for this hunk or add a new one
21675 if let Some((_, comments)) = self.stored_review_comments.iter_mut().find(|(k, _)| {
21676 k.file_path == hunk_key.file_path
21677 && k.hunk_start_anchor.to_point(&snapshot) == key_point
21678 }) {
21679 comments.push(stored_comment);
21680 } else {
21681 self.stored_review_comments
21682 .push((hunk_key, vec![stored_comment]));
21683 }
21684
21685 cx.emit(EditorEvent::ReviewCommentsChanged {
21686 total_count: self.total_review_comment_count(),
21687 });
21688 cx.notify();
21689 id
21690 }
21691
21692 /// Removes a review comment by ID from any hunk.
21693 pub fn remove_review_comment(&mut self, id: usize, cx: &mut Context<Self>) -> bool {
21694 for (_, comments) in self.stored_review_comments.iter_mut() {
21695 if let Some(index) = comments.iter().position(|c| c.id == id) {
21696 comments.remove(index);
21697 cx.emit(EditorEvent::ReviewCommentsChanged {
21698 total_count: self.total_review_comment_count(),
21699 });
21700 cx.notify();
21701 return true;
21702 }
21703 }
21704 false
21705 }
21706
21707 /// Updates a review comment's text by ID.
21708 pub fn update_review_comment(
21709 &mut self,
21710 id: usize,
21711 new_comment: String,
21712 cx: &mut Context<Self>,
21713 ) -> bool {
21714 for (_, comments) in self.stored_review_comments.iter_mut() {
21715 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
21716 comment.comment = new_comment;
21717 comment.is_editing = false;
21718 cx.emit(EditorEvent::ReviewCommentsChanged {
21719 total_count: self.total_review_comment_count(),
21720 });
21721 cx.notify();
21722 return true;
21723 }
21724 }
21725 false
21726 }
21727
21728 /// Sets a comment's editing state.
21729 pub fn set_comment_editing(&mut self, id: usize, is_editing: bool, cx: &mut Context<Self>) {
21730 for (_, comments) in self.stored_review_comments.iter_mut() {
21731 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
21732 comment.is_editing = is_editing;
21733 cx.notify();
21734 return;
21735 }
21736 }
21737 }
21738
21739 /// Takes all stored comments from all hunks, clearing the storage.
21740 /// Returns a Vec of (hunk_key, comments) pairs.
21741 pub fn take_all_review_comments(
21742 &mut self,
21743 cx: &mut Context<Self>,
21744 ) -> Vec<(DiffHunkKey, Vec<StoredReviewComment>)> {
21745 // Dismiss all overlays when taking comments (e.g., when sending to agent)
21746 self.dismiss_all_diff_review_overlays(cx);
21747 let comments = std::mem::take(&mut self.stored_review_comments);
21748 // Reset the ID counter since all comments have been taken
21749 self.next_review_comment_id = 0;
21750 cx.emit(EditorEvent::ReviewCommentsChanged { total_count: 0 });
21751 cx.notify();
21752 comments
21753 }
21754
21755 /// Removes review comments whose anchors are no longer valid or whose
21756 /// associated diff hunks no longer exist.
21757 ///
21758 /// This should be called when the buffer changes to prevent orphaned comments
21759 /// from accumulating.
21760 pub fn cleanup_orphaned_review_comments(&mut self, cx: &mut Context<Self>) {
21761 let snapshot = self.buffer.read(cx).snapshot(cx);
21762 let original_count = self.total_review_comment_count();
21763
21764 // Remove comments with invalid hunk anchors
21765 self.stored_review_comments
21766 .retain(|(hunk_key, _)| hunk_key.hunk_start_anchor.is_valid(&snapshot));
21767
21768 // Also clean up individual comments with invalid anchor ranges
21769 for (_, comments) in &mut self.stored_review_comments {
21770 comments.retain(|comment| {
21771 comment.range.start.is_valid(&snapshot) && comment.range.end.is_valid(&snapshot)
21772 });
21773 }
21774
21775 // Remove empty hunk entries
21776 self.stored_review_comments
21777 .retain(|(_, comments)| !comments.is_empty());
21778
21779 let new_count = self.total_review_comment_count();
21780 if new_count != original_count {
21781 cx.emit(EditorEvent::ReviewCommentsChanged {
21782 total_count: new_count,
21783 });
21784 cx.notify();
21785 }
21786 }
21787
21788 /// Toggles the expanded state of the comments section in the overlay.
21789 pub fn toggle_review_comments_expanded(
21790 &mut self,
21791 _: &ToggleReviewCommentsExpanded,
21792 window: &mut Window,
21793 cx: &mut Context<Self>,
21794 ) {
21795 // Find the overlay that currently has focus, or use the first one
21796 let overlay_info = self.diff_review_overlays.iter_mut().find_map(|overlay| {
21797 if overlay.prompt_editor.focus_handle(cx).is_focused(window) {
21798 overlay.comments_expanded = !overlay.comments_expanded;
21799 Some(overlay.hunk_key.clone())
21800 } else {
21801 None
21802 }
21803 });
21804
21805 // If no focused overlay found, toggle the first one
21806 let hunk_key = overlay_info.or_else(|| {
21807 self.diff_review_overlays.first_mut().map(|overlay| {
21808 overlay.comments_expanded = !overlay.comments_expanded;
21809 overlay.hunk_key.clone()
21810 })
21811 });
21812
21813 if let Some(hunk_key) = hunk_key {
21814 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
21815 cx.notify();
21816 }
21817 }
21818
21819 /// Handles the EditReviewComment action - sets a comment into editing mode.
21820 pub fn edit_review_comment(
21821 &mut self,
21822 action: &EditReviewComment,
21823 window: &mut Window,
21824 cx: &mut Context<Self>,
21825 ) {
21826 let comment_id = action.id;
21827
21828 // Set the comment to editing mode
21829 self.set_comment_editing(comment_id, true, cx);
21830
21831 // Find the overlay that contains this comment and create an inline editor if needed
21832 // First, find which hunk this comment belongs to
21833 let hunk_key = self
21834 .stored_review_comments
21835 .iter()
21836 .find_map(|(key, comments)| {
21837 if comments.iter().any(|c| c.id == comment_id) {
21838 Some(key.clone())
21839 } else {
21840 None
21841 }
21842 });
21843
21844 let snapshot = self.buffer.read(cx).snapshot(cx);
21845 if let Some(hunk_key) = hunk_key {
21846 if let Some(overlay) = self
21847 .diff_review_overlays
21848 .iter_mut()
21849 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
21850 {
21851 if let std::collections::hash_map::Entry::Vacant(entry) =
21852 overlay.inline_edit_editors.entry(comment_id)
21853 {
21854 // Find the comment text
21855 let comment_text = self
21856 .stored_review_comments
21857 .iter()
21858 .flat_map(|(_, comments)| comments)
21859 .find(|c| c.id == comment_id)
21860 .map(|c| c.comment.clone())
21861 .unwrap_or_default();
21862
21863 // Create inline editor
21864 let parent_editor = cx.entity().downgrade();
21865 let inline_editor = cx.new(|cx| {
21866 let mut editor = Editor::single_line(window, cx);
21867 editor.set_text(&*comment_text, window, cx);
21868 // Select all text for easy replacement
21869 editor.select_all(&crate::actions::SelectAll, window, cx);
21870 editor
21871 });
21872
21873 // Register the Newline action to confirm the edit
21874 let subscription = inline_editor.update(cx, |inline_editor, _cx| {
21875 inline_editor.register_action({
21876 let parent_editor = parent_editor.clone();
21877 move |_: &crate::actions::Newline, window, cx| {
21878 if let Some(editor) = parent_editor.upgrade() {
21879 editor.update(cx, |editor, cx| {
21880 editor.confirm_edit_review_comment(comment_id, window, cx);
21881 });
21882 }
21883 }
21884 })
21885 });
21886
21887 // Store the subscription to keep the action handler alive
21888 overlay
21889 .inline_edit_subscriptions
21890 .insert(comment_id, subscription);
21891
21892 // Focus the inline editor
21893 let focus_handle = inline_editor.focus_handle(cx);
21894 window.focus(&focus_handle, cx);
21895
21896 entry.insert(inline_editor);
21897 }
21898 }
21899 }
21900
21901 cx.notify();
21902 }
21903
21904 /// Confirms an inline edit of a review comment.
21905 pub fn confirm_edit_review_comment(
21906 &mut self,
21907 comment_id: usize,
21908 _window: &mut Window,
21909 cx: &mut Context<Self>,
21910 ) {
21911 // Get the new text from the inline editor
21912 // Find the overlay containing this comment's inline editor
21913 let snapshot = self.buffer.read(cx).snapshot(cx);
21914 let hunk_key = self
21915 .stored_review_comments
21916 .iter()
21917 .find_map(|(key, comments)| {
21918 if comments.iter().any(|c| c.id == comment_id) {
21919 Some(key.clone())
21920 } else {
21921 None
21922 }
21923 });
21924
21925 let new_text = hunk_key
21926 .as_ref()
21927 .and_then(|hunk_key| {
21928 self.diff_review_overlays
21929 .iter()
21930 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
21931 })
21932 .as_ref()
21933 .and_then(|overlay| overlay.inline_edit_editors.get(&comment_id))
21934 .map(|editor| editor.read(cx).text(cx).trim().to_string());
21935
21936 if let Some(new_text) = new_text {
21937 if !new_text.is_empty() {
21938 self.update_review_comment(comment_id, new_text, cx);
21939 }
21940 }
21941
21942 // Remove the inline editor and its subscription
21943 if let Some(hunk_key) = hunk_key {
21944 if let Some(overlay) = self
21945 .diff_review_overlays
21946 .iter_mut()
21947 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
21948 {
21949 overlay.inline_edit_editors.remove(&comment_id);
21950 overlay.inline_edit_subscriptions.remove(&comment_id);
21951 }
21952 }
21953
21954 // Clear editing state
21955 self.set_comment_editing(comment_id, false, cx);
21956 }
21957
21958 /// Cancels an inline edit of a review comment.
21959 pub fn cancel_edit_review_comment(
21960 &mut self,
21961 comment_id: usize,
21962 _window: &mut Window,
21963 cx: &mut Context<Self>,
21964 ) {
21965 // Find which hunk this comment belongs to
21966 let hunk_key = self
21967 .stored_review_comments
21968 .iter()
21969 .find_map(|(key, comments)| {
21970 if comments.iter().any(|c| c.id == comment_id) {
21971 Some(key.clone())
21972 } else {
21973 None
21974 }
21975 });
21976
21977 // Remove the inline editor and its subscription
21978 if let Some(hunk_key) = hunk_key {
21979 let snapshot = self.buffer.read(cx).snapshot(cx);
21980 if let Some(overlay) = self
21981 .diff_review_overlays
21982 .iter_mut()
21983 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
21984 {
21985 overlay.inline_edit_editors.remove(&comment_id);
21986 overlay.inline_edit_subscriptions.remove(&comment_id);
21987 }
21988 }
21989
21990 // Clear editing state
21991 self.set_comment_editing(comment_id, false, cx);
21992 }
21993
21994 /// Action handler for ConfirmEditReviewComment.
21995 pub fn confirm_edit_review_comment_action(
21996 &mut self,
21997 action: &ConfirmEditReviewComment,
21998 window: &mut Window,
21999 cx: &mut Context<Self>,
22000 ) {
22001 self.confirm_edit_review_comment(action.id, window, cx);
22002 }
22003
22004 /// Action handler for CancelEditReviewComment.
22005 pub fn cancel_edit_review_comment_action(
22006 &mut self,
22007 action: &CancelEditReviewComment,
22008 window: &mut Window,
22009 cx: &mut Context<Self>,
22010 ) {
22011 self.cancel_edit_review_comment(action.id, window, cx);
22012 }
22013
22014 /// Handles the DeleteReviewComment action - removes a comment.
22015 pub fn delete_review_comment(
22016 &mut self,
22017 action: &DeleteReviewComment,
22018 window: &mut Window,
22019 cx: &mut Context<Self>,
22020 ) {
22021 // Get the hunk key before removing the comment
22022 // Find the hunk key from the comment itself
22023 let comment_id = action.id;
22024 let hunk_key = self
22025 .stored_review_comments
22026 .iter()
22027 .find_map(|(key, comments)| {
22028 if comments.iter().any(|c| c.id == comment_id) {
22029 Some(key.clone())
22030 } else {
22031 None
22032 }
22033 });
22034
22035 // Also get it from the overlay for refresh purposes
22036 let overlay_hunk_key = self
22037 .diff_review_overlays
22038 .first()
22039 .map(|o| o.hunk_key.clone());
22040
22041 self.remove_review_comment(action.id, cx);
22042
22043 // Refresh the overlay height after removing a comment
22044 if let Some(hunk_key) = hunk_key.or(overlay_hunk_key) {
22045 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22046 }
22047 }
22048
22049 fn render_diff_review_overlay(
22050 prompt_editor: &Entity<Editor>,
22051 hunk_key: &DiffHunkKey,
22052 editor_handle: &WeakEntity<Editor>,
22053 cx: &mut BlockContext,
22054 ) -> AnyElement {
22055 fn format_line_ranges(ranges: &[(u32, u32)]) -> Option<String> {
22056 if ranges.is_empty() {
22057 return None;
22058 }
22059 let formatted: Vec<String> = ranges
22060 .iter()
22061 .map(|(start, end)| {
22062 let start_line = start + 1;
22063 let end_line = end + 1;
22064 if start_line == end_line {
22065 format!("Line {start_line}")
22066 } else {
22067 format!("Lines {start_line}-{end_line}")
22068 }
22069 })
22070 .collect();
22071 // Don't show label for single line in single excerpt
22072 if ranges.len() == 1 && ranges[0].0 == ranges[0].1 {
22073 return None;
22074 }
22075 Some(formatted.join(" ⋯ "))
22076 }
22077
22078 let theme = cx.theme();
22079 let colors = theme.colors();
22080
22081 let (comments, comments_expanded, inline_editors, user_avatar_uri, line_ranges) =
22082 editor_handle
22083 .upgrade()
22084 .map(|editor| {
22085 let editor = editor.read(cx);
22086 let snapshot = editor.buffer().read(cx).snapshot(cx);
22087 let comments = editor.comments_for_hunk(hunk_key, &snapshot).to_vec();
22088 let (expanded, editors, avatar_uri, line_ranges) = editor
22089 .diff_review_overlays
22090 .iter()
22091 .find(|overlay| {
22092 Editor::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot)
22093 })
22094 .map(|o| {
22095 let start_point = o.anchor_range.start.to_point(&snapshot);
22096 let end_point = o.anchor_range.end.to_point(&snapshot);
22097 // Get line ranges per excerpt to detect discontinuities
22098 let buffer_ranges =
22099 snapshot.range_to_buffer_ranges(start_point..end_point);
22100 let ranges: Vec<(u32, u32)> = buffer_ranges
22101 .iter()
22102 .map(|(buffer, range, _)| {
22103 let start = buffer.offset_to_point(range.start.0).row;
22104 let end = buffer.offset_to_point(range.end.0).row;
22105 (start, end)
22106 })
22107 .collect();
22108 (
22109 o.comments_expanded,
22110 o.inline_edit_editors.clone(),
22111 o.user_avatar_uri.clone(),
22112 if ranges.is_empty() {
22113 None
22114 } else {
22115 Some(ranges)
22116 },
22117 )
22118 })
22119 .unwrap_or((true, HashMap::default(), None, None));
22120 (comments, expanded, editors, avatar_uri, line_ranges)
22121 })
22122 .unwrap_or((Vec::new(), true, HashMap::default(), None, None));
22123
22124 let comment_count = comments.len();
22125 let avatar_size = px(20.);
22126 let action_icon_size = IconSize::XSmall;
22127
22128 v_flex()
22129 .w_full()
22130 .bg(colors.editor_background)
22131 .border_b_1()
22132 .border_color(colors.border)
22133 .px_2()
22134 .pb_2()
22135 .gap_2()
22136 // Line range indicator (only shown for multi-line selections or multiple excerpts)
22137 .when_some(line_ranges, |el, ranges| {
22138 let label = format_line_ranges(&ranges);
22139 if let Some(label) = label {
22140 el.child(
22141 h_flex()
22142 .w_full()
22143 .px_2()
22144 .child(Label::new(label).size(LabelSize::Small).color(Color::Muted)),
22145 )
22146 } else {
22147 el
22148 }
22149 })
22150 // Top row: editable input with user's avatar
22151 .child(
22152 h_flex()
22153 .w_full()
22154 .items_center()
22155 .gap_2()
22156 .px_2()
22157 .py_1p5()
22158 .rounded_md()
22159 .bg(colors.surface_background)
22160 .child(
22161 div()
22162 .size(avatar_size)
22163 .flex_shrink_0()
22164 .rounded_full()
22165 .overflow_hidden()
22166 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22167 Avatar::new(avatar_uri.clone())
22168 .size(avatar_size)
22169 .into_any_element()
22170 } else {
22171 Icon::new(IconName::Person)
22172 .size(IconSize::Small)
22173 .color(ui::Color::Muted)
22174 .into_any_element()
22175 }),
22176 )
22177 .child(
22178 div()
22179 .flex_1()
22180 .border_1()
22181 .border_color(colors.border)
22182 .rounded_md()
22183 .bg(colors.editor_background)
22184 .px_2()
22185 .py_1()
22186 .child(prompt_editor.clone()),
22187 )
22188 .child(
22189 h_flex()
22190 .flex_shrink_0()
22191 .gap_1()
22192 .child(
22193 IconButton::new("diff-review-close", IconName::Close)
22194 .icon_color(ui::Color::Muted)
22195 .icon_size(action_icon_size)
22196 .tooltip(Tooltip::text("Close"))
22197 .on_click(|_, window, cx| {
22198 window
22199 .dispatch_action(Box::new(crate::actions::Cancel), cx);
22200 }),
22201 )
22202 .child(
22203 IconButton::new("diff-review-add", IconName::Return)
22204 .icon_color(ui::Color::Muted)
22205 .icon_size(action_icon_size)
22206 .tooltip(Tooltip::text("Add comment"))
22207 .on_click(|_, window, cx| {
22208 window.dispatch_action(
22209 Box::new(crate::actions::SubmitDiffReviewComment),
22210 cx,
22211 );
22212 }),
22213 ),
22214 ),
22215 )
22216 // Expandable comments section (only shown when there are comments)
22217 .when(comment_count > 0, |el| {
22218 el.child(Self::render_comments_section(
22219 comments,
22220 comments_expanded,
22221 inline_editors,
22222 user_avatar_uri,
22223 avatar_size,
22224 action_icon_size,
22225 colors,
22226 ))
22227 })
22228 .into_any_element()
22229 }
22230
22231 fn render_comments_section(
22232 comments: Vec<StoredReviewComment>,
22233 expanded: bool,
22234 inline_editors: HashMap<usize, Entity<Editor>>,
22235 user_avatar_uri: Option<SharedUri>,
22236 avatar_size: Pixels,
22237 action_icon_size: IconSize,
22238 colors: &theme::ThemeColors,
22239 ) -> impl IntoElement {
22240 let comment_count = comments.len();
22241
22242 v_flex()
22243 .w_full()
22244 .gap_1()
22245 // Header with expand/collapse toggle
22246 .child(
22247 h_flex()
22248 .id("review-comments-header")
22249 .w_full()
22250 .items_center()
22251 .gap_1()
22252 .px_2()
22253 .py_1()
22254 .cursor_pointer()
22255 .rounded_md()
22256 .hover(|style| style.bg(colors.ghost_element_hover))
22257 .on_click(|_, window: &mut Window, cx| {
22258 window.dispatch_action(
22259 Box::new(crate::actions::ToggleReviewCommentsExpanded),
22260 cx,
22261 );
22262 })
22263 .child(
22264 Icon::new(if expanded {
22265 IconName::ChevronDown
22266 } else {
22267 IconName::ChevronRight
22268 })
22269 .size(IconSize::Small)
22270 .color(ui::Color::Muted),
22271 )
22272 .child(
22273 Label::new(format!(
22274 "{} Comment{}",
22275 comment_count,
22276 if comment_count == 1 { "" } else { "s" }
22277 ))
22278 .size(LabelSize::Small)
22279 .color(Color::Muted),
22280 ),
22281 )
22282 // Comments list (when expanded)
22283 .when(expanded, |el| {
22284 el.children(comments.into_iter().map(|comment| {
22285 let inline_editor = inline_editors.get(&comment.id).cloned();
22286 Self::render_comment_row(
22287 comment,
22288 inline_editor,
22289 user_avatar_uri.clone(),
22290 avatar_size,
22291 action_icon_size,
22292 colors,
22293 )
22294 }))
22295 })
22296 }
22297
22298 fn render_comment_row(
22299 comment: StoredReviewComment,
22300 inline_editor: Option<Entity<Editor>>,
22301 user_avatar_uri: Option<SharedUri>,
22302 avatar_size: Pixels,
22303 action_icon_size: IconSize,
22304 colors: &theme::ThemeColors,
22305 ) -> impl IntoElement {
22306 let comment_id = comment.id;
22307 let is_editing = inline_editor.is_some();
22308
22309 h_flex()
22310 .w_full()
22311 .items_center()
22312 .gap_2()
22313 .px_2()
22314 .py_1p5()
22315 .rounded_md()
22316 .bg(colors.surface_background)
22317 .child(
22318 div()
22319 .size(avatar_size)
22320 .flex_shrink_0()
22321 .rounded_full()
22322 .overflow_hidden()
22323 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22324 Avatar::new(avatar_uri.clone())
22325 .size(avatar_size)
22326 .into_any_element()
22327 } else {
22328 Icon::new(IconName::Person)
22329 .size(IconSize::Small)
22330 .color(ui::Color::Muted)
22331 .into_any_element()
22332 }),
22333 )
22334 .child(if let Some(editor) = inline_editor {
22335 // Inline edit mode: show an editable text field
22336 div()
22337 .flex_1()
22338 .border_1()
22339 .border_color(colors.border)
22340 .rounded_md()
22341 .bg(colors.editor_background)
22342 .px_2()
22343 .py_1()
22344 .child(editor)
22345 .into_any_element()
22346 } else {
22347 // Display mode: show the comment text
22348 div()
22349 .flex_1()
22350 .text_sm()
22351 .text_color(colors.text)
22352 .child(comment.comment)
22353 .into_any_element()
22354 })
22355 .child(if is_editing {
22356 // Editing mode: show close and confirm buttons
22357 h_flex()
22358 .gap_1()
22359 .child(
22360 IconButton::new(
22361 format!("diff-review-cancel-edit-{comment_id}"),
22362 IconName::Close,
22363 )
22364 .icon_color(ui::Color::Muted)
22365 .icon_size(action_icon_size)
22366 .tooltip(Tooltip::text("Cancel"))
22367 .on_click(move |_, window, cx| {
22368 window.dispatch_action(
22369 Box::new(crate::actions::CancelEditReviewComment {
22370 id: comment_id,
22371 }),
22372 cx,
22373 );
22374 }),
22375 )
22376 .child(
22377 IconButton::new(
22378 format!("diff-review-confirm-edit-{comment_id}"),
22379 IconName::Return,
22380 )
22381 .icon_color(ui::Color::Muted)
22382 .icon_size(action_icon_size)
22383 .tooltip(Tooltip::text("Confirm"))
22384 .on_click(move |_, window, cx| {
22385 window.dispatch_action(
22386 Box::new(crate::actions::ConfirmEditReviewComment {
22387 id: comment_id,
22388 }),
22389 cx,
22390 );
22391 }),
22392 )
22393 .into_any_element()
22394 } else {
22395 // Display mode: no action buttons for now (edit/delete not yet implemented)
22396 gpui::Empty.into_any_element()
22397 })
22398 }
22399
22400 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
22401 if self.display_map.read(cx).masked != masked {
22402 self.display_map.update(cx, |map, _| map.masked = masked);
22403 }
22404 cx.notify()
22405 }
22406
22407 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
22408 self.show_wrap_guides = Some(show_wrap_guides);
22409 cx.notify();
22410 }
22411
22412 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
22413 self.show_indent_guides = Some(show_indent_guides);
22414 cx.notify();
22415 }
22416
22417 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
22418 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
22419 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
22420 && let Some(dir) = file.abs_path(cx).parent()
22421 {
22422 return Some(dir.to_owned());
22423 }
22424 }
22425
22426 None
22427 }
22428
22429 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
22430 self.active_excerpt(cx)?
22431 .1
22432 .read(cx)
22433 .file()
22434 .and_then(|f| f.as_local())
22435 }
22436
22437 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
22438 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22439 let buffer = buffer.read(cx);
22440 if let Some(project_path) = buffer.project_path(cx) {
22441 let project = self.project()?.read(cx);
22442 project.absolute_path(&project_path, cx)
22443 } else {
22444 buffer
22445 .file()
22446 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
22447 }
22448 })
22449 }
22450
22451 pub fn reveal_in_finder(
22452 &mut self,
22453 _: &RevealInFileManager,
22454 _window: &mut Window,
22455 cx: &mut Context<Self>,
22456 ) {
22457 if let Some(path) = self.target_file_abs_path(cx) {
22458 if let Some(project) = self.project() {
22459 project.update(cx, |project, cx| project.reveal_path(&path, cx));
22460 } else {
22461 cx.reveal_path(&path);
22462 }
22463 }
22464 }
22465
22466 pub fn copy_path(
22467 &mut self,
22468 _: &zed_actions::workspace::CopyPath,
22469 _window: &mut Window,
22470 cx: &mut Context<Self>,
22471 ) {
22472 if let Some(path) = self.target_file_abs_path(cx)
22473 && let Some(path) = path.to_str()
22474 {
22475 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22476 } else {
22477 cx.propagate();
22478 }
22479 }
22480
22481 pub fn copy_relative_path(
22482 &mut self,
22483 _: &zed_actions::workspace::CopyRelativePath,
22484 _window: &mut Window,
22485 cx: &mut Context<Self>,
22486 ) {
22487 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22488 let project = self.project()?.read(cx);
22489 let path = buffer.read(cx).file()?.path();
22490 let path = path.display(project.path_style(cx));
22491 Some(path)
22492 }) {
22493 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22494 } else {
22495 cx.propagate();
22496 }
22497 }
22498
22499 /// Returns the project path for the editor's buffer, if any buffer is
22500 /// opened in the editor.
22501 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
22502 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
22503 buffer.read(cx).project_path(cx)
22504 } else {
22505 None
22506 }
22507 }
22508
22509 // Returns true if the editor handled a go-to-line request
22510 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
22511 maybe!({
22512 let breakpoint_store = self.breakpoint_store.as_ref()?;
22513
22514 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
22515 else {
22516 self.clear_row_highlights::<ActiveDebugLine>();
22517 return None;
22518 };
22519
22520 let position = active_stack_frame.position;
22521 let buffer_id = position.buffer_id?;
22522 let snapshot = self
22523 .project
22524 .as_ref()?
22525 .read(cx)
22526 .buffer_for_id(buffer_id, cx)?
22527 .read(cx)
22528 .snapshot();
22529
22530 let mut handled = false;
22531 for (id, ExcerptRange { context, .. }) in
22532 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
22533 {
22534 if context.start.cmp(&position, &snapshot).is_ge()
22535 || context.end.cmp(&position, &snapshot).is_lt()
22536 {
22537 continue;
22538 }
22539 let snapshot = self.buffer.read(cx).snapshot(cx);
22540 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
22541
22542 handled = true;
22543 self.clear_row_highlights::<ActiveDebugLine>();
22544
22545 self.go_to_line::<ActiveDebugLine>(
22546 multibuffer_anchor,
22547 Some(cx.theme().colors().editor_debugger_active_line_background),
22548 window,
22549 cx,
22550 );
22551
22552 cx.notify();
22553 }
22554
22555 handled.then_some(())
22556 })
22557 .is_some()
22558 }
22559
22560 pub fn copy_file_name_without_extension(
22561 &mut self,
22562 _: &CopyFileNameWithoutExtension,
22563 _: &mut Window,
22564 cx: &mut Context<Self>,
22565 ) {
22566 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22567 let file = buffer.read(cx).file()?;
22568 file.path().file_stem()
22569 }) {
22570 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
22571 }
22572 }
22573
22574 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
22575 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22576 let file = buffer.read(cx).file()?;
22577 Some(file.file_name(cx))
22578 }) {
22579 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
22580 }
22581 }
22582
22583 pub fn toggle_git_blame(
22584 &mut self,
22585 _: &::git::Blame,
22586 window: &mut Window,
22587 cx: &mut Context<Self>,
22588 ) {
22589 self.show_git_blame_gutter = !self.show_git_blame_gutter;
22590
22591 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
22592 self.start_git_blame(true, window, cx);
22593 }
22594
22595 cx.notify();
22596 }
22597
22598 pub fn toggle_git_blame_inline(
22599 &mut self,
22600 _: &ToggleGitBlameInline,
22601 window: &mut Window,
22602 cx: &mut Context<Self>,
22603 ) {
22604 self.toggle_git_blame_inline_internal(true, window, cx);
22605 cx.notify();
22606 }
22607
22608 pub fn open_git_blame_commit(
22609 &mut self,
22610 _: &OpenGitBlameCommit,
22611 window: &mut Window,
22612 cx: &mut Context<Self>,
22613 ) {
22614 self.open_git_blame_commit_internal(window, cx);
22615 }
22616
22617 fn open_git_blame_commit_internal(
22618 &mut self,
22619 window: &mut Window,
22620 cx: &mut Context<Self>,
22621 ) -> Option<()> {
22622 let blame = self.blame.as_ref()?;
22623 let snapshot = self.snapshot(window, cx);
22624 let cursor = self
22625 .selections
22626 .newest::<Point>(&snapshot.display_snapshot)
22627 .head();
22628 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
22629 let (_, blame_entry) = blame
22630 .update(cx, |blame, cx| {
22631 blame
22632 .blame_for_rows(
22633 &[RowInfo {
22634 buffer_id: Some(buffer.remote_id()),
22635 buffer_row: Some(point.row),
22636 ..Default::default()
22637 }],
22638 cx,
22639 )
22640 .next()
22641 })
22642 .flatten()?;
22643 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22644 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
22645 let workspace = self.workspace()?.downgrade();
22646 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
22647 None
22648 }
22649
22650 pub fn git_blame_inline_enabled(&self) -> bool {
22651 self.git_blame_inline_enabled
22652 }
22653
22654 pub fn toggle_selection_menu(
22655 &mut self,
22656 _: &ToggleSelectionMenu,
22657 _: &mut Window,
22658 cx: &mut Context<Self>,
22659 ) {
22660 self.show_selection_menu = self
22661 .show_selection_menu
22662 .map(|show_selections_menu| !show_selections_menu)
22663 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
22664
22665 cx.notify();
22666 }
22667
22668 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
22669 self.show_selection_menu
22670 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
22671 }
22672
22673 fn start_git_blame(
22674 &mut self,
22675 user_triggered: bool,
22676 window: &mut Window,
22677 cx: &mut Context<Self>,
22678 ) {
22679 if let Some(project) = self.project() {
22680 if let Some(buffer) = self.buffer().read(cx).as_singleton()
22681 && buffer.read(cx).file().is_none()
22682 {
22683 return;
22684 }
22685
22686 let focused = self.focus_handle(cx).contains_focused(window, cx);
22687
22688 let project = project.clone();
22689 let blame = cx
22690 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
22691 self.blame_subscription =
22692 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
22693 self.blame = Some(blame);
22694 }
22695 }
22696
22697 fn toggle_git_blame_inline_internal(
22698 &mut self,
22699 user_triggered: bool,
22700 window: &mut Window,
22701 cx: &mut Context<Self>,
22702 ) {
22703 if self.git_blame_inline_enabled {
22704 self.git_blame_inline_enabled = false;
22705 self.show_git_blame_inline = false;
22706 self.show_git_blame_inline_delay_task.take();
22707 } else {
22708 self.git_blame_inline_enabled = true;
22709 self.start_git_blame_inline(user_triggered, window, cx);
22710 }
22711
22712 cx.notify();
22713 }
22714
22715 fn start_git_blame_inline(
22716 &mut self,
22717 user_triggered: bool,
22718 window: &mut Window,
22719 cx: &mut Context<Self>,
22720 ) {
22721 self.start_git_blame(user_triggered, window, cx);
22722
22723 if ProjectSettings::get_global(cx)
22724 .git
22725 .inline_blame_delay()
22726 .is_some()
22727 {
22728 self.start_inline_blame_timer(window, cx);
22729 } else {
22730 self.show_git_blame_inline = true
22731 }
22732 }
22733
22734 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
22735 self.blame.as_ref()
22736 }
22737
22738 pub fn show_git_blame_gutter(&self) -> bool {
22739 self.show_git_blame_gutter
22740 }
22741
22742 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
22743 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
22744 }
22745
22746 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
22747 self.show_git_blame_inline
22748 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
22749 && !self.newest_selection_head_on_empty_line(cx)
22750 && self.has_blame_entries(cx)
22751 }
22752
22753 fn has_blame_entries(&self, cx: &App) -> bool {
22754 self.blame()
22755 .is_some_and(|blame| blame.read(cx).has_generated_entries())
22756 }
22757
22758 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
22759 let cursor_anchor = self.selections.newest_anchor().head();
22760
22761 let snapshot = self.buffer.read(cx).snapshot(cx);
22762 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
22763
22764 snapshot.line_len(buffer_row) == 0
22765 }
22766
22767 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
22768 let buffer_and_selection = maybe!({
22769 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
22770 let selection_range = selection.range();
22771
22772 let multi_buffer = self.buffer().read(cx);
22773 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
22774 let buffer_ranges = multi_buffer_snapshot
22775 .range_to_buffer_ranges(selection_range.start..=selection_range.end);
22776
22777 let (buffer, range, _) = if selection.reversed {
22778 buffer_ranges.first()
22779 } else {
22780 buffer_ranges.last()
22781 }?;
22782
22783 let buffer_range = range.to_point(buffer);
22784
22785 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
22786 return Some((
22787 multi_buffer.buffer(buffer.remote_id()).unwrap(),
22788 buffer_range.start.row..buffer_range.end.row,
22789 ));
22790 };
22791
22792 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
22793 let start =
22794 buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.start, buffer);
22795 let end =
22796 buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.end, buffer);
22797
22798 Some((
22799 multi_buffer.buffer(buffer.remote_id()).unwrap(),
22800 start.row..end.row,
22801 ))
22802 });
22803
22804 let Some((buffer, selection)) = buffer_and_selection else {
22805 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
22806 };
22807
22808 let Some(project) = self.project() else {
22809 return Task::ready(Err(anyhow!("editor does not have project")));
22810 };
22811
22812 project.update(cx, |project, cx| {
22813 project.get_permalink_to_line(&buffer, selection, cx)
22814 })
22815 }
22816
22817 pub fn copy_permalink_to_line(
22818 &mut self,
22819 _: &CopyPermalinkToLine,
22820 window: &mut Window,
22821 cx: &mut Context<Self>,
22822 ) {
22823 let permalink_task = self.get_permalink_to_line(cx);
22824 let workspace = self.workspace();
22825
22826 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
22827 Ok(permalink) => {
22828 cx.update(|_, cx| {
22829 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
22830 })
22831 .ok();
22832 }
22833 Err(err) => {
22834 let message = format!("Failed to copy permalink: {err}");
22835
22836 anyhow::Result::<()>::Err(err).log_err();
22837
22838 if let Some(workspace) = workspace {
22839 workspace
22840 .update_in(cx, |workspace, _, cx| {
22841 struct CopyPermalinkToLine;
22842
22843 workspace.show_toast(
22844 Toast::new(
22845 NotificationId::unique::<CopyPermalinkToLine>(),
22846 message,
22847 ),
22848 cx,
22849 )
22850 })
22851 .ok();
22852 }
22853 }
22854 })
22855 .detach();
22856 }
22857
22858 pub fn copy_file_location(
22859 &mut self,
22860 _: &CopyFileLocation,
22861 _: &mut Window,
22862 cx: &mut Context<Self>,
22863 ) {
22864 let selection = self
22865 .selections
22866 .newest::<Point>(&self.display_snapshot(cx))
22867 .start
22868 .row
22869 + 1;
22870 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22871 let project = self.project()?.read(cx);
22872 let file = buffer.read(cx).file()?;
22873 let path = file.path().display(project.path_style(cx));
22874
22875 Some(format!("{path}:{selection}"))
22876 }) {
22877 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
22878 }
22879 }
22880
22881 pub fn open_permalink_to_line(
22882 &mut self,
22883 _: &OpenPermalinkToLine,
22884 window: &mut Window,
22885 cx: &mut Context<Self>,
22886 ) {
22887 let permalink_task = self.get_permalink_to_line(cx);
22888 let workspace = self.workspace();
22889
22890 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
22891 Ok(permalink) => {
22892 cx.update(|_, cx| {
22893 cx.open_url(permalink.as_ref());
22894 })
22895 .ok();
22896 }
22897 Err(err) => {
22898 let message = format!("Failed to open permalink: {err}");
22899
22900 anyhow::Result::<()>::Err(err).log_err();
22901
22902 if let Some(workspace) = workspace {
22903 workspace.update(cx, |workspace, cx| {
22904 struct OpenPermalinkToLine;
22905
22906 workspace.show_toast(
22907 Toast::new(NotificationId::unique::<OpenPermalinkToLine>(), message),
22908 cx,
22909 )
22910 });
22911 }
22912 }
22913 })
22914 .detach();
22915 }
22916
22917 pub fn insert_uuid_v4(
22918 &mut self,
22919 _: &InsertUuidV4,
22920 window: &mut Window,
22921 cx: &mut Context<Self>,
22922 ) {
22923 self.insert_uuid(UuidVersion::V4, window, cx);
22924 }
22925
22926 pub fn insert_uuid_v7(
22927 &mut self,
22928 _: &InsertUuidV7,
22929 window: &mut Window,
22930 cx: &mut Context<Self>,
22931 ) {
22932 self.insert_uuid(UuidVersion::V7, window, cx);
22933 }
22934
22935 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
22936 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
22937 self.transact(window, cx, |this, window, cx| {
22938 let edits = this
22939 .selections
22940 .all::<Point>(&this.display_snapshot(cx))
22941 .into_iter()
22942 .map(|selection| {
22943 let uuid = match version {
22944 UuidVersion::V4 => uuid::Uuid::new_v4(),
22945 UuidVersion::V7 => uuid::Uuid::now_v7(),
22946 };
22947
22948 (selection.range(), uuid.to_string())
22949 });
22950 this.edit(edits, cx);
22951 this.refresh_edit_prediction(true, false, window, cx);
22952 });
22953 }
22954
22955 pub fn open_selections_in_multibuffer(
22956 &mut self,
22957 _: &OpenSelectionsInMultibuffer,
22958 window: &mut Window,
22959 cx: &mut Context<Self>,
22960 ) {
22961 let multibuffer = self.buffer.read(cx);
22962
22963 let Some(buffer) = multibuffer.as_singleton() else {
22964 return;
22965 };
22966
22967 let Some(workspace) = self.workspace() else {
22968 return;
22969 };
22970
22971 let title = multibuffer.title(cx).to_string();
22972
22973 let locations = self
22974 .selections
22975 .all_anchors(&self.display_snapshot(cx))
22976 .iter()
22977 .map(|selection| {
22978 (
22979 buffer.clone(),
22980 (selection.start.text_anchor..selection.end.text_anchor)
22981 .to_point(buffer.read(cx)),
22982 )
22983 })
22984 .into_group_map();
22985
22986 cx.spawn_in(window, async move |_, cx| {
22987 workspace.update_in(cx, |workspace, window, cx| {
22988 Self::open_locations_in_multibuffer(
22989 workspace,
22990 locations,
22991 format!("Selections for '{title}'"),
22992 false,
22993 false,
22994 MultibufferSelectionMode::All,
22995 window,
22996 cx,
22997 );
22998 })
22999 })
23000 .detach();
23001 }
23002
23003 /// Adds a row highlight for the given range. If a row has multiple highlights, the
23004 /// last highlight added will be used.
23005 ///
23006 /// If the range ends at the beginning of a line, then that line will not be highlighted.
23007 pub fn highlight_rows<T: 'static>(
23008 &mut self,
23009 range: Range<Anchor>,
23010 color: Hsla,
23011 options: RowHighlightOptions,
23012 cx: &mut Context<Self>,
23013 ) {
23014 let snapshot = self.buffer().read(cx).snapshot(cx);
23015 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23016 let ix = row_highlights.binary_search_by(|highlight| {
23017 Ordering::Equal
23018 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
23019 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
23020 });
23021
23022 if let Err(mut ix) = ix {
23023 let index = post_inc(&mut self.highlight_order);
23024
23025 // If this range intersects with the preceding highlight, then merge it with
23026 // the preceding highlight. Otherwise insert a new highlight.
23027 let mut merged = false;
23028 if ix > 0 {
23029 let prev_highlight = &mut row_highlights[ix - 1];
23030 if prev_highlight
23031 .range
23032 .end
23033 .cmp(&range.start, &snapshot)
23034 .is_ge()
23035 {
23036 ix -= 1;
23037 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
23038 prev_highlight.range.end = range.end;
23039 }
23040 merged = true;
23041 prev_highlight.index = index;
23042 prev_highlight.color = color;
23043 prev_highlight.options = options;
23044 }
23045 }
23046
23047 if !merged {
23048 row_highlights.insert(
23049 ix,
23050 RowHighlight {
23051 range,
23052 index,
23053 color,
23054 options,
23055 type_id: TypeId::of::<T>(),
23056 },
23057 );
23058 }
23059
23060 // If any of the following highlights intersect with this one, merge them.
23061 while let Some(next_highlight) = row_highlights.get(ix + 1) {
23062 let highlight = &row_highlights[ix];
23063 if next_highlight
23064 .range
23065 .start
23066 .cmp(&highlight.range.end, &snapshot)
23067 .is_le()
23068 {
23069 if next_highlight
23070 .range
23071 .end
23072 .cmp(&highlight.range.end, &snapshot)
23073 .is_gt()
23074 {
23075 row_highlights[ix].range.end = next_highlight.range.end;
23076 }
23077 row_highlights.remove(ix + 1);
23078 } else {
23079 break;
23080 }
23081 }
23082 }
23083 }
23084
23085 /// Remove any highlighted row ranges of the given type that intersect the
23086 /// given ranges.
23087 pub fn remove_highlighted_rows<T: 'static>(
23088 &mut self,
23089 ranges_to_remove: Vec<Range<Anchor>>,
23090 cx: &mut Context<Self>,
23091 ) {
23092 let snapshot = self.buffer().read(cx).snapshot(cx);
23093 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23094 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23095 row_highlights.retain(|highlight| {
23096 while let Some(range_to_remove) = ranges_to_remove.peek() {
23097 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
23098 Ordering::Less | Ordering::Equal => {
23099 ranges_to_remove.next();
23100 }
23101 Ordering::Greater => {
23102 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
23103 Ordering::Less | Ordering::Equal => {
23104 return false;
23105 }
23106 Ordering::Greater => break,
23107 }
23108 }
23109 }
23110 }
23111
23112 true
23113 })
23114 }
23115
23116 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
23117 pub fn clear_row_highlights<T: 'static>(&mut self) {
23118 self.highlighted_rows.remove(&TypeId::of::<T>());
23119 }
23120
23121 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
23122 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
23123 self.highlighted_rows
23124 .get(&TypeId::of::<T>())
23125 .map_or(&[] as &[_], |vec| vec.as_slice())
23126 .iter()
23127 .map(|highlight| (highlight.range.clone(), highlight.color))
23128 }
23129
23130 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
23131 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
23132 /// Allows to ignore certain kinds of highlights.
23133 pub fn highlighted_display_rows(
23134 &self,
23135 window: &mut Window,
23136 cx: &mut App,
23137 ) -> BTreeMap<DisplayRow, LineHighlight> {
23138 let snapshot = self.snapshot(window, cx);
23139 let mut used_highlight_orders = HashMap::default();
23140 self.highlighted_rows
23141 .iter()
23142 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
23143 .fold(
23144 BTreeMap::<DisplayRow, LineHighlight>::new(),
23145 |mut unique_rows, highlight| {
23146 let start = highlight.range.start.to_display_point(&snapshot);
23147 let end = highlight.range.end.to_display_point(&snapshot);
23148 let start_row = start.row().0;
23149 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
23150 {
23151 end.row().0.saturating_sub(1)
23152 } else {
23153 end.row().0
23154 };
23155 for row in start_row..=end_row {
23156 let used_index =
23157 used_highlight_orders.entry(row).or_insert(highlight.index);
23158 if highlight.index >= *used_index {
23159 *used_index = highlight.index;
23160 unique_rows.insert(
23161 DisplayRow(row),
23162 LineHighlight {
23163 include_gutter: highlight.options.include_gutter,
23164 border: None,
23165 background: highlight.color.into(),
23166 type_id: Some(highlight.type_id),
23167 },
23168 );
23169 }
23170 }
23171 unique_rows
23172 },
23173 )
23174 }
23175
23176 pub fn highlighted_display_row_for_autoscroll(
23177 &self,
23178 snapshot: &DisplaySnapshot,
23179 ) -> Option<DisplayRow> {
23180 self.highlighted_rows
23181 .values()
23182 .flat_map(|highlighted_rows| highlighted_rows.iter())
23183 .filter_map(|highlight| {
23184 if highlight.options.autoscroll {
23185 Some(highlight.range.start.to_display_point(snapshot).row())
23186 } else {
23187 None
23188 }
23189 })
23190 .min()
23191 }
23192
23193 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
23194 self.highlight_background::<SearchWithinRange>(
23195 ranges,
23196 |_, colors| colors.colors().editor_document_highlight_read_background,
23197 cx,
23198 )
23199 }
23200
23201 pub fn set_breadcrumb_header(&mut self, new_header: String) {
23202 self.breadcrumb_header = Some(new_header);
23203 }
23204
23205 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
23206 self.clear_background_highlights::<SearchWithinRange>(cx);
23207 }
23208
23209 pub fn highlight_background<T: 'static>(
23210 &mut self,
23211 ranges: &[Range<Anchor>],
23212 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
23213 cx: &mut Context<Self>,
23214 ) {
23215 self.background_highlights.insert(
23216 HighlightKey::Type(TypeId::of::<T>()),
23217 (Arc::new(color_fetcher), Arc::from(ranges)),
23218 );
23219 self.scrollbar_marker_state.dirty = true;
23220 cx.notify();
23221 }
23222
23223 pub fn highlight_background_key<T: 'static>(
23224 &mut self,
23225 key: usize,
23226 ranges: &[Range<Anchor>],
23227 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
23228 cx: &mut Context<Self>,
23229 ) {
23230 self.background_highlights.insert(
23231 HighlightKey::TypePlus(TypeId::of::<T>(), key),
23232 (Arc::new(color_fetcher), Arc::from(ranges)),
23233 );
23234 self.scrollbar_marker_state.dirty = true;
23235 cx.notify();
23236 }
23237
23238 pub fn clear_background_highlights<T: 'static>(
23239 &mut self,
23240 cx: &mut Context<Self>,
23241 ) -> Option<BackgroundHighlight> {
23242 let text_highlights = self
23243 .background_highlights
23244 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
23245 if !text_highlights.1.is_empty() {
23246 self.scrollbar_marker_state.dirty = true;
23247 cx.notify();
23248 }
23249 Some(text_highlights)
23250 }
23251
23252 pub fn clear_background_highlights_key<T: 'static>(
23253 &mut self,
23254 key: usize,
23255 cx: &mut Context<Self>,
23256 ) -> Option<BackgroundHighlight> {
23257 let text_highlights = self
23258 .background_highlights
23259 .remove(&HighlightKey::TypePlus(TypeId::of::<T>(), key))?;
23260 if !text_highlights.1.is_empty() {
23261 self.scrollbar_marker_state.dirty = true;
23262 cx.notify();
23263 }
23264 Some(text_highlights)
23265 }
23266
23267 pub fn highlight_gutter<T: 'static>(
23268 &mut self,
23269 ranges: impl Into<Vec<Range<Anchor>>>,
23270 color_fetcher: fn(&App) -> Hsla,
23271 cx: &mut Context<Self>,
23272 ) {
23273 self.gutter_highlights
23274 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
23275 cx.notify();
23276 }
23277
23278 pub fn clear_gutter_highlights<T: 'static>(
23279 &mut self,
23280 cx: &mut Context<Self>,
23281 ) -> Option<GutterHighlight> {
23282 cx.notify();
23283 self.gutter_highlights.remove(&TypeId::of::<T>())
23284 }
23285
23286 pub fn insert_gutter_highlight<T: 'static>(
23287 &mut self,
23288 range: Range<Anchor>,
23289 color_fetcher: fn(&App) -> Hsla,
23290 cx: &mut Context<Self>,
23291 ) {
23292 let snapshot = self.buffer().read(cx).snapshot(cx);
23293 let mut highlights = self
23294 .gutter_highlights
23295 .remove(&TypeId::of::<T>())
23296 .map(|(_, highlights)| highlights)
23297 .unwrap_or_default();
23298 let ix = highlights.binary_search_by(|highlight| {
23299 Ordering::Equal
23300 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
23301 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
23302 });
23303 if let Err(ix) = ix {
23304 highlights.insert(ix, range);
23305 }
23306 self.gutter_highlights
23307 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
23308 }
23309
23310 pub fn remove_gutter_highlights<T: 'static>(
23311 &mut self,
23312 ranges_to_remove: Vec<Range<Anchor>>,
23313 cx: &mut Context<Self>,
23314 ) {
23315 let snapshot = self.buffer().read(cx).snapshot(cx);
23316 let Some((color_fetcher, mut gutter_highlights)) =
23317 self.gutter_highlights.remove(&TypeId::of::<T>())
23318 else {
23319 return;
23320 };
23321 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23322 gutter_highlights.retain(|highlight| {
23323 while let Some(range_to_remove) = ranges_to_remove.peek() {
23324 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
23325 Ordering::Less | Ordering::Equal => {
23326 ranges_to_remove.next();
23327 }
23328 Ordering::Greater => {
23329 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
23330 Ordering::Less | Ordering::Equal => {
23331 return false;
23332 }
23333 Ordering::Greater => break,
23334 }
23335 }
23336 }
23337 }
23338
23339 true
23340 });
23341 self.gutter_highlights
23342 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
23343 }
23344
23345 #[cfg(feature = "test-support")]
23346 pub fn all_text_highlights(
23347 &self,
23348 window: &mut Window,
23349 cx: &mut Context<Self>,
23350 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
23351 let snapshot = self.snapshot(window, cx);
23352 self.display_map.update(cx, |display_map, _| {
23353 display_map
23354 .all_text_highlights()
23355 .map(|highlight| {
23356 let (style, ranges) = highlight.as_ref();
23357 (
23358 *style,
23359 ranges
23360 .iter()
23361 .map(|range| range.clone().to_display_points(&snapshot))
23362 .collect(),
23363 )
23364 })
23365 .collect()
23366 })
23367 }
23368
23369 #[cfg(feature = "test-support")]
23370 pub fn all_text_background_highlights(
23371 &self,
23372 window: &mut Window,
23373 cx: &mut Context<Self>,
23374 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23375 let snapshot = self.snapshot(window, cx);
23376 let buffer = &snapshot.buffer_snapshot();
23377 let start = buffer.anchor_before(MultiBufferOffset(0));
23378 let end = buffer.anchor_after(buffer.len());
23379 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
23380 }
23381
23382 #[cfg(any(test, feature = "test-support"))]
23383 pub fn sorted_background_highlights_in_range(
23384 &self,
23385 search_range: Range<Anchor>,
23386 display_snapshot: &DisplaySnapshot,
23387 theme: &Theme,
23388 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23389 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
23390 res.sort_by(|a, b| {
23391 a.0.start
23392 .cmp(&b.0.start)
23393 .then_with(|| a.0.end.cmp(&b.0.end))
23394 .then_with(|| a.1.cmp(&b.1))
23395 });
23396 res
23397 }
23398
23399 #[cfg(feature = "test-support")]
23400 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
23401 let snapshot = self.buffer().read(cx).snapshot(cx);
23402
23403 let highlights = self
23404 .background_highlights
23405 .get(&HighlightKey::Type(TypeId::of::<
23406 items::BufferSearchHighlights,
23407 >()));
23408
23409 if let Some((_color, ranges)) = highlights {
23410 ranges
23411 .iter()
23412 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
23413 .collect_vec()
23414 } else {
23415 vec![]
23416 }
23417 }
23418
23419 fn document_highlights_for_position<'a>(
23420 &'a self,
23421 position: Anchor,
23422 buffer: &'a MultiBufferSnapshot,
23423 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
23424 let read_highlights = self
23425 .background_highlights
23426 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
23427 .map(|h| &h.1);
23428 let write_highlights = self
23429 .background_highlights
23430 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
23431 .map(|h| &h.1);
23432 let left_position = position.bias_left(buffer);
23433 let right_position = position.bias_right(buffer);
23434 read_highlights
23435 .into_iter()
23436 .chain(write_highlights)
23437 .flat_map(move |ranges| {
23438 let start_ix = match ranges.binary_search_by(|probe| {
23439 let cmp = probe.end.cmp(&left_position, buffer);
23440 if cmp.is_ge() {
23441 Ordering::Greater
23442 } else {
23443 Ordering::Less
23444 }
23445 }) {
23446 Ok(i) | Err(i) => i,
23447 };
23448
23449 ranges[start_ix..]
23450 .iter()
23451 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
23452 })
23453 }
23454
23455 pub fn has_background_highlights<T: 'static>(&self) -> bool {
23456 self.background_highlights
23457 .get(&HighlightKey::Type(TypeId::of::<T>()))
23458 .is_some_and(|(_, highlights)| !highlights.is_empty())
23459 }
23460
23461 /// Returns all background highlights for a given range.
23462 ///
23463 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
23464 pub fn background_highlights_in_range(
23465 &self,
23466 search_range: Range<Anchor>,
23467 display_snapshot: &DisplaySnapshot,
23468 theme: &Theme,
23469 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23470 let mut results = Vec::new();
23471 for (color_fetcher, ranges) in self.background_highlights.values() {
23472 let start_ix = match ranges.binary_search_by(|probe| {
23473 let cmp = probe
23474 .end
23475 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23476 if cmp.is_gt() {
23477 Ordering::Greater
23478 } else {
23479 Ordering::Less
23480 }
23481 }) {
23482 Ok(i) | Err(i) => i,
23483 };
23484 for (index, range) in ranges[start_ix..].iter().enumerate() {
23485 if range
23486 .start
23487 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23488 .is_ge()
23489 {
23490 break;
23491 }
23492
23493 let color = color_fetcher(&(start_ix + index), theme);
23494 let start = range.start.to_display_point(display_snapshot);
23495 let end = range.end.to_display_point(display_snapshot);
23496 results.push((start..end, color))
23497 }
23498 }
23499 results
23500 }
23501
23502 pub fn gutter_highlights_in_range(
23503 &self,
23504 search_range: Range<Anchor>,
23505 display_snapshot: &DisplaySnapshot,
23506 cx: &App,
23507 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23508 let mut results = Vec::new();
23509 for (color_fetcher, ranges) in self.gutter_highlights.values() {
23510 let color = color_fetcher(cx);
23511 let start_ix = match ranges.binary_search_by(|probe| {
23512 let cmp = probe
23513 .end
23514 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23515 if cmp.is_gt() {
23516 Ordering::Greater
23517 } else {
23518 Ordering::Less
23519 }
23520 }) {
23521 Ok(i) | Err(i) => i,
23522 };
23523 for range in &ranges[start_ix..] {
23524 if range
23525 .start
23526 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23527 .is_ge()
23528 {
23529 break;
23530 }
23531
23532 let start = range.start.to_display_point(display_snapshot);
23533 let end = range.end.to_display_point(display_snapshot);
23534 results.push((start..end, color))
23535 }
23536 }
23537 results
23538 }
23539
23540 /// Get the text ranges corresponding to the redaction query
23541 pub fn redacted_ranges(
23542 &self,
23543 search_range: Range<Anchor>,
23544 display_snapshot: &DisplaySnapshot,
23545 cx: &App,
23546 ) -> Vec<Range<DisplayPoint>> {
23547 display_snapshot
23548 .buffer_snapshot()
23549 .redacted_ranges(search_range, |file| {
23550 if let Some(file) = file {
23551 file.is_private()
23552 && EditorSettings::get(
23553 Some(SettingsLocation {
23554 worktree_id: file.worktree_id(cx),
23555 path: file.path().as_ref(),
23556 }),
23557 cx,
23558 )
23559 .redact_private_values
23560 } else {
23561 false
23562 }
23563 })
23564 .map(|range| {
23565 range.start.to_display_point(display_snapshot)
23566 ..range.end.to_display_point(display_snapshot)
23567 })
23568 .collect()
23569 }
23570
23571 pub fn highlight_text_key<T: 'static>(
23572 &mut self,
23573 key: usize,
23574 ranges: Vec<Range<Anchor>>,
23575 style: HighlightStyle,
23576 merge: bool,
23577 cx: &mut Context<Self>,
23578 ) {
23579 self.display_map.update(cx, |map, cx| {
23580 map.highlight_text(
23581 HighlightKey::TypePlus(TypeId::of::<T>(), key),
23582 ranges,
23583 style,
23584 merge,
23585 cx,
23586 );
23587 });
23588 cx.notify();
23589 }
23590
23591 pub fn highlight_text<T: 'static>(
23592 &mut self,
23593 ranges: Vec<Range<Anchor>>,
23594 style: HighlightStyle,
23595 cx: &mut Context<Self>,
23596 ) {
23597 self.display_map.update(cx, |map, cx| {
23598 map.highlight_text(
23599 HighlightKey::Type(TypeId::of::<T>()),
23600 ranges,
23601 style,
23602 false,
23603 cx,
23604 )
23605 });
23606 cx.notify();
23607 }
23608
23609 pub fn text_highlights<'a, T: 'static>(
23610 &'a self,
23611 cx: &'a App,
23612 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
23613 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
23614 }
23615
23616 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
23617 let cleared = self
23618 .display_map
23619 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
23620 if cleared {
23621 cx.notify();
23622 }
23623 }
23624
23625 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
23626 (self.read_only(cx) || self.blink_manager.read(cx).visible())
23627 && self.focus_handle.is_focused(window)
23628 }
23629
23630 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
23631 self.show_cursor_when_unfocused = is_enabled;
23632 cx.notify();
23633 }
23634
23635 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
23636 cx.notify();
23637 }
23638
23639 fn on_debug_session_event(
23640 &mut self,
23641 _session: Entity<Session>,
23642 event: &SessionEvent,
23643 cx: &mut Context<Self>,
23644 ) {
23645 if let SessionEvent::InvalidateInlineValue = event {
23646 self.refresh_inline_values(cx);
23647 }
23648 }
23649
23650 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
23651 let Some(project) = self.project.clone() else {
23652 return;
23653 };
23654
23655 if !self.inline_value_cache.enabled {
23656 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
23657 self.splice_inlays(&inlays, Vec::new(), cx);
23658 return;
23659 }
23660
23661 let current_execution_position = self
23662 .highlighted_rows
23663 .get(&TypeId::of::<ActiveDebugLine>())
23664 .and_then(|lines| lines.last().map(|line| line.range.end));
23665
23666 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
23667 let inline_values = editor
23668 .update(cx, |editor, cx| {
23669 let Some(current_execution_position) = current_execution_position else {
23670 return Some(Task::ready(Ok(Vec::new())));
23671 };
23672
23673 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
23674 let snapshot = buffer.snapshot(cx);
23675
23676 let excerpt = snapshot.excerpt_containing(
23677 current_execution_position..current_execution_position,
23678 )?;
23679
23680 editor.buffer.read(cx).buffer(excerpt.buffer_id())
23681 })?;
23682
23683 let range =
23684 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
23685
23686 project.inline_values(buffer, range, cx)
23687 })
23688 .ok()
23689 .flatten()?
23690 .await
23691 .context("refreshing debugger inlays")
23692 .log_err()?;
23693
23694 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
23695
23696 for (buffer_id, inline_value) in inline_values
23697 .into_iter()
23698 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
23699 {
23700 buffer_inline_values
23701 .entry(buffer_id)
23702 .or_default()
23703 .push(inline_value);
23704 }
23705
23706 editor
23707 .update(cx, |editor, cx| {
23708 let snapshot = editor.buffer.read(cx).snapshot(cx);
23709 let mut new_inlays = Vec::default();
23710
23711 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
23712 let buffer_id = buffer_snapshot.remote_id();
23713 buffer_inline_values
23714 .get(&buffer_id)
23715 .into_iter()
23716 .flatten()
23717 .for_each(|hint| {
23718 let inlay = Inlay::debugger(
23719 post_inc(&mut editor.next_inlay_id),
23720 Anchor::in_buffer(excerpt_id, hint.position),
23721 hint.text(),
23722 );
23723 if !inlay.text().chars().contains(&'\n') {
23724 new_inlays.push(inlay);
23725 }
23726 });
23727 }
23728
23729 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
23730 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
23731
23732 editor.splice_inlays(&inlay_ids, new_inlays, cx);
23733 })
23734 .ok()?;
23735 Some(())
23736 });
23737 }
23738
23739 fn on_buffer_event(
23740 &mut self,
23741 multibuffer: &Entity<MultiBuffer>,
23742 event: &multi_buffer::Event,
23743 window: &mut Window,
23744 cx: &mut Context<Self>,
23745 ) {
23746 match event {
23747 multi_buffer::Event::Edited { edited_buffer } => {
23748 self.scrollbar_marker_state.dirty = true;
23749 self.active_indent_guides_state.dirty = true;
23750 self.refresh_active_diagnostics(cx);
23751 self.refresh_code_actions(window, cx);
23752 self.refresh_single_line_folds(window, cx);
23753 self.refresh_matching_bracket_highlights(window, cx);
23754 if self.has_active_edit_prediction() {
23755 self.update_visible_edit_prediction(window, cx);
23756 }
23757
23758 // Clean up orphaned review comments after edits
23759 self.cleanup_orphaned_review_comments(cx);
23760
23761 if let Some(buffer) = edited_buffer {
23762 if buffer.read(cx).file().is_none() {
23763 cx.emit(EditorEvent::TitleChanged);
23764 }
23765
23766 if self.project.is_some() {
23767 let buffer_id = buffer.read(cx).remote_id();
23768 self.register_buffer(buffer_id, cx);
23769 self.update_lsp_data(Some(buffer_id), window, cx);
23770 self.refresh_inlay_hints(
23771 InlayHintRefreshReason::BufferEdited(buffer_id),
23772 cx,
23773 );
23774 }
23775 }
23776
23777 cx.emit(EditorEvent::BufferEdited);
23778 cx.emit(SearchEvent::MatchesInvalidated);
23779
23780 let Some(project) = &self.project else { return };
23781 let (telemetry, is_via_ssh) = {
23782 let project = project.read(cx);
23783 let telemetry = project.client().telemetry().clone();
23784 let is_via_ssh = project.is_via_remote_server();
23785 (telemetry, is_via_ssh)
23786 };
23787 telemetry.log_edit_event("editor", is_via_ssh);
23788 }
23789 multi_buffer::Event::ExcerptsAdded {
23790 buffer,
23791 predecessor,
23792 excerpts,
23793 } => {
23794 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
23795 let buffer_id = buffer.read(cx).remote_id();
23796 if self.buffer.read(cx).diff_for(buffer_id).is_none()
23797 && let Some(project) = &self.project
23798 {
23799 update_uncommitted_diff_for_buffer(
23800 cx.entity(),
23801 project,
23802 [buffer.clone()],
23803 self.buffer.clone(),
23804 cx,
23805 )
23806 .detach();
23807 }
23808 self.update_lsp_data(Some(buffer_id), window, cx);
23809 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
23810 self.colorize_brackets(false, cx);
23811 self.refresh_selected_text_highlights(true, window, cx);
23812 cx.emit(EditorEvent::ExcerptsAdded {
23813 buffer: buffer.clone(),
23814 predecessor: *predecessor,
23815 excerpts: excerpts.clone(),
23816 });
23817 }
23818 multi_buffer::Event::ExcerptsRemoved {
23819 ids,
23820 removed_buffer_ids,
23821 } => {
23822 if let Some(inlay_hints) = &mut self.inlay_hints {
23823 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
23824 }
23825 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
23826 for buffer_id in removed_buffer_ids {
23827 self.registered_buffers.remove(buffer_id);
23828 }
23829 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
23830 cx.emit(EditorEvent::ExcerptsRemoved {
23831 ids: ids.clone(),
23832 removed_buffer_ids: removed_buffer_ids.clone(),
23833 });
23834 }
23835 multi_buffer::Event::ExcerptsEdited {
23836 excerpt_ids,
23837 buffer_ids,
23838 } => {
23839 self.display_map.update(cx, |map, cx| {
23840 map.unfold_buffers(buffer_ids.iter().copied(), cx)
23841 });
23842 cx.emit(EditorEvent::ExcerptsEdited {
23843 ids: excerpt_ids.clone(),
23844 });
23845 }
23846 multi_buffer::Event::ExcerptsExpanded { ids } => {
23847 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
23848 self.refresh_document_highlights(cx);
23849 for id in ids {
23850 self.fetched_tree_sitter_chunks.remove(id);
23851 }
23852 self.colorize_brackets(false, cx);
23853 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
23854 }
23855 multi_buffer::Event::Reparsed(buffer_id) => {
23856 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
23857 self.refresh_selected_text_highlights(true, window, cx);
23858 self.colorize_brackets(true, cx);
23859 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
23860
23861 cx.emit(EditorEvent::Reparsed(*buffer_id));
23862 }
23863 multi_buffer::Event::DiffHunksToggled => {
23864 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
23865 }
23866 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
23867 if !is_fresh_language {
23868 self.registered_buffers.remove(&buffer_id);
23869 }
23870 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
23871 cx.emit(EditorEvent::Reparsed(*buffer_id));
23872 cx.notify();
23873 }
23874 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
23875 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
23876 multi_buffer::Event::FileHandleChanged
23877 | multi_buffer::Event::Reloaded
23878 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
23879 multi_buffer::Event::DiagnosticsUpdated => {
23880 self.update_diagnostics_state(window, cx);
23881 }
23882 _ => {}
23883 };
23884 }
23885
23886 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
23887 if !self.diagnostics_enabled() {
23888 return;
23889 }
23890 self.refresh_active_diagnostics(cx);
23891 self.refresh_inline_diagnostics(true, window, cx);
23892 self.scrollbar_marker_state.dirty = true;
23893 cx.notify();
23894 }
23895
23896 pub fn start_temporary_diff_override(&mut self) {
23897 self.load_diff_task.take();
23898 self.temporary_diff_override = true;
23899 }
23900
23901 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
23902 self.temporary_diff_override = false;
23903 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
23904 self.buffer.update(cx, |buffer, cx| {
23905 buffer.set_all_diff_hunks_collapsed(cx);
23906 });
23907
23908 if let Some(project) = self.project.clone() {
23909 self.load_diff_task = Some(
23910 update_uncommitted_diff_for_buffer(
23911 cx.entity(),
23912 &project,
23913 self.buffer.read(cx).all_buffers(),
23914 self.buffer.clone(),
23915 cx,
23916 )
23917 .shared(),
23918 );
23919 }
23920 }
23921
23922 fn on_display_map_changed(
23923 &mut self,
23924 _: Entity<DisplayMap>,
23925 _: &mut Window,
23926 cx: &mut Context<Self>,
23927 ) {
23928 cx.notify();
23929 }
23930
23931 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
23932 if !self.mode.is_full() {
23933 return None;
23934 }
23935
23936 let theme_settings = theme::ThemeSettings::get_global(cx);
23937 let theme = cx.theme();
23938 let accent_colors = theme.accents().clone();
23939
23940 let accent_overrides = theme_settings
23941 .theme_overrides
23942 .get(theme.name.as_ref())
23943 .map(|theme_style| &theme_style.accents)
23944 .into_iter()
23945 .flatten()
23946 .chain(
23947 theme_settings
23948 .experimental_theme_overrides
23949 .as_ref()
23950 .map(|overrides| &overrides.accents)
23951 .into_iter()
23952 .flatten(),
23953 )
23954 .flat_map(|accent| accent.0.clone().map(SharedString::from))
23955 .collect();
23956
23957 Some(AccentData {
23958 colors: accent_colors,
23959 overrides: accent_overrides,
23960 })
23961 }
23962
23963 fn fetch_applicable_language_settings(
23964 &self,
23965 cx: &App,
23966 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
23967 if !self.mode.is_full() {
23968 return HashMap::default();
23969 }
23970
23971 self.buffer().read(cx).all_buffers().into_iter().fold(
23972 HashMap::default(),
23973 |mut acc, buffer| {
23974 let buffer = buffer.read(cx);
23975 let language = buffer.language().map(|language| language.name());
23976 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
23977 let file = buffer.file();
23978 v.insert(language_settings(language, file, cx).into_owned());
23979 }
23980 acc
23981 },
23982 )
23983 }
23984
23985 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
23986 let new_language_settings = self.fetch_applicable_language_settings(cx);
23987 let language_settings_changed = new_language_settings != self.applicable_language_settings;
23988 self.applicable_language_settings = new_language_settings;
23989
23990 let new_accents = self.fetch_accent_data(cx);
23991 let accents_changed = new_accents != self.accent_data;
23992 self.accent_data = new_accents;
23993
23994 if self.diagnostics_enabled() {
23995 let new_severity = EditorSettings::get_global(cx)
23996 .diagnostics_max_severity
23997 .unwrap_or(DiagnosticSeverity::Hint);
23998 self.set_max_diagnostics_severity(new_severity, cx);
23999 }
24000 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
24001 self.update_edit_prediction_settings(cx);
24002 self.refresh_edit_prediction(true, false, window, cx);
24003 self.refresh_inline_values(cx);
24004 self.refresh_inlay_hints(
24005 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
24006 self.selections.newest_anchor().head(),
24007 &self.buffer.read(cx).snapshot(cx),
24008 cx,
24009 )),
24010 cx,
24011 );
24012
24013 let old_cursor_shape = self.cursor_shape;
24014 let old_show_breadcrumbs = self.show_breadcrumbs;
24015
24016 {
24017 let editor_settings = EditorSettings::get_global(cx);
24018 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
24019 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
24020 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
24021 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
24022 }
24023
24024 if old_cursor_shape != self.cursor_shape {
24025 cx.emit(EditorEvent::CursorShapeChanged);
24026 }
24027
24028 if old_show_breadcrumbs != self.show_breadcrumbs {
24029 cx.emit(EditorEvent::BreadcrumbsChanged);
24030 }
24031
24032 let project_settings = ProjectSettings::get_global(cx);
24033 self.buffer_serialization = self
24034 .should_serialize_buffer()
24035 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
24036
24037 if self.mode.is_full() {
24038 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
24039 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
24040 if self.show_inline_diagnostics != show_inline_diagnostics {
24041 self.show_inline_diagnostics = show_inline_diagnostics;
24042 self.refresh_inline_diagnostics(false, window, cx);
24043 }
24044
24045 if self.git_blame_inline_enabled != inline_blame_enabled {
24046 self.toggle_git_blame_inline_internal(false, window, cx);
24047 }
24048
24049 let minimap_settings = EditorSettings::get_global(cx).minimap;
24050 if self.minimap_visibility != MinimapVisibility::Disabled {
24051 if self.minimap_visibility.settings_visibility()
24052 != minimap_settings.minimap_enabled()
24053 {
24054 self.set_minimap_visibility(
24055 MinimapVisibility::for_mode(self.mode(), cx),
24056 window,
24057 cx,
24058 );
24059 } else if let Some(minimap_entity) = self.minimap.as_ref() {
24060 minimap_entity.update(cx, |minimap_editor, cx| {
24061 minimap_editor.update_minimap_configuration(minimap_settings, cx)
24062 })
24063 }
24064 }
24065
24066 if language_settings_changed || accents_changed {
24067 self.colorize_brackets(true, cx);
24068 }
24069
24070 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
24071 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
24072 }) {
24073 if !inlay_splice.is_empty() {
24074 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
24075 }
24076 self.refresh_colors_for_visible_range(None, window, cx);
24077 }
24078 }
24079
24080 cx.notify();
24081 }
24082
24083 fn theme_changed(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24084 if !self.mode.is_full() {
24085 return;
24086 }
24087
24088 let new_accents = self.fetch_accent_data(cx);
24089 if new_accents != self.accent_data {
24090 self.accent_data = new_accents;
24091 self.colorize_brackets(true, cx);
24092 }
24093 }
24094
24095 pub fn set_searchable(&mut self, searchable: bool) {
24096 self.searchable = searchable;
24097 }
24098
24099 pub fn searchable(&self) -> bool {
24100 self.searchable
24101 }
24102
24103 pub fn open_excerpts_in_split(
24104 &mut self,
24105 _: &OpenExcerptsSplit,
24106 window: &mut Window,
24107 cx: &mut Context<Self>,
24108 ) {
24109 self.open_excerpts_common(None, true, window, cx)
24110 }
24111
24112 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
24113 self.open_excerpts_common(None, false, window, cx)
24114 }
24115
24116 pub(crate) fn open_excerpts_common(
24117 &mut self,
24118 jump_data: Option<JumpData>,
24119 split: bool,
24120 window: &mut Window,
24121 cx: &mut Context<Self>,
24122 ) {
24123 let Some(workspace) = self.workspace() else {
24124 cx.propagate();
24125 return;
24126 };
24127
24128 if self.buffer.read(cx).is_singleton() {
24129 cx.propagate();
24130 return;
24131 }
24132
24133 let mut new_selections_by_buffer = HashMap::default();
24134 match &jump_data {
24135 Some(JumpData::MultiBufferPoint {
24136 excerpt_id,
24137 position,
24138 anchor,
24139 line_offset_from_top,
24140 }) => {
24141 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
24142 if let Some(buffer) = multi_buffer_snapshot
24143 .buffer_id_for_excerpt(*excerpt_id)
24144 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
24145 {
24146 let buffer_snapshot = buffer.read(cx).snapshot();
24147 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
24148 language::ToPoint::to_point(anchor, &buffer_snapshot)
24149 } else {
24150 buffer_snapshot.clip_point(*position, Bias::Left)
24151 };
24152 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
24153 new_selections_by_buffer.insert(
24154 buffer,
24155 (
24156 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
24157 Some(*line_offset_from_top),
24158 ),
24159 );
24160 }
24161 }
24162 Some(JumpData::MultiBufferRow {
24163 row,
24164 line_offset_from_top,
24165 }) => {
24166 let point = MultiBufferPoint::new(row.0, 0);
24167 if let Some((buffer, buffer_point, _)) =
24168 self.buffer.read(cx).point_to_buffer_point(point, cx)
24169 {
24170 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
24171 new_selections_by_buffer
24172 .entry(buffer)
24173 .or_insert((Vec::new(), Some(*line_offset_from_top)))
24174 .0
24175 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
24176 }
24177 }
24178 None => {
24179 let selections = self
24180 .selections
24181 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
24182 let multi_buffer = self.buffer.read(cx);
24183 for selection in selections {
24184 for (snapshot, range, _, anchor) in multi_buffer
24185 .snapshot(cx)
24186 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
24187 {
24188 if let Some(anchor) = anchor {
24189 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
24190 else {
24191 continue;
24192 };
24193 let offset = text::ToOffset::to_offset(
24194 &anchor.text_anchor,
24195 &buffer_handle.read(cx).snapshot(),
24196 );
24197 let range = BufferOffset(offset)..BufferOffset(offset);
24198 new_selections_by_buffer
24199 .entry(buffer_handle)
24200 .or_insert((Vec::new(), None))
24201 .0
24202 .push(range)
24203 } else {
24204 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
24205 else {
24206 continue;
24207 };
24208 new_selections_by_buffer
24209 .entry(buffer_handle)
24210 .or_insert((Vec::new(), None))
24211 .0
24212 .push(range)
24213 }
24214 }
24215 }
24216 }
24217 }
24218
24219 new_selections_by_buffer
24220 .retain(|buffer, _| buffer.read(cx).file().is_none_or(|file| file.can_open()));
24221
24222 if new_selections_by_buffer.is_empty() {
24223 return;
24224 }
24225
24226 // We defer the pane interaction because we ourselves are a workspace item
24227 // and activating a new item causes the pane to call a method on us reentrantly,
24228 // which panics if we're on the stack.
24229 window.defer(cx, move |window, cx| {
24230 workspace.update(cx, |workspace, cx| {
24231 let pane = if split {
24232 workspace.adjacent_pane(window, cx)
24233 } else {
24234 workspace.active_pane().clone()
24235 };
24236
24237 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
24238 let buffer_read = buffer.read(cx);
24239 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
24240 (true, project::File::from_dyn(Some(file)).is_some())
24241 } else {
24242 (false, false)
24243 };
24244
24245 // If project file is none workspace.open_project_item will fail to open the excerpt
24246 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
24247 // so we check if there's a tab match in that case first
24248 let editor = (!has_file || !is_project_file)
24249 .then(|| {
24250 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
24251 // so `workspace.open_project_item` will never find them, always opening a new editor.
24252 // Instead, we try to activate the existing editor in the pane first.
24253 let (editor, pane_item_index, pane_item_id) =
24254 pane.read(cx).items().enumerate().find_map(|(i, item)| {
24255 let editor = item.downcast::<Editor>()?;
24256 let singleton_buffer =
24257 editor.read(cx).buffer().read(cx).as_singleton()?;
24258 if singleton_buffer == buffer {
24259 Some((editor, i, item.item_id()))
24260 } else {
24261 None
24262 }
24263 })?;
24264 pane.update(cx, |pane, cx| {
24265 pane.activate_item(pane_item_index, true, true, window, cx);
24266 if !PreviewTabsSettings::get_global(cx)
24267 .enable_preview_from_multibuffer
24268 {
24269 pane.unpreview_item_if_preview(pane_item_id);
24270 }
24271 });
24272 Some(editor)
24273 })
24274 .flatten()
24275 .unwrap_or_else(|| {
24276 let keep_old_preview = PreviewTabsSettings::get_global(cx)
24277 .enable_keep_preview_on_code_navigation;
24278 let allow_new_preview =
24279 PreviewTabsSettings::get_global(cx).enable_preview_from_multibuffer;
24280 workspace.open_project_item::<Self>(
24281 pane.clone(),
24282 buffer,
24283 true,
24284 true,
24285 keep_old_preview,
24286 allow_new_preview,
24287 window,
24288 cx,
24289 )
24290 });
24291
24292 editor.update(cx, |editor, cx| {
24293 if has_file && !is_project_file {
24294 editor.set_read_only(true);
24295 }
24296 let autoscroll = match scroll_offset {
24297 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
24298 None => Autoscroll::newest(),
24299 };
24300 let nav_history = editor.nav_history.take();
24301 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
24302 let Some((&excerpt_id, _, buffer_snapshot)) =
24303 multibuffer_snapshot.as_singleton()
24304 else {
24305 return;
24306 };
24307 editor.change_selections(
24308 SelectionEffects::scroll(autoscroll),
24309 window,
24310 cx,
24311 |s| {
24312 s.select_ranges(ranges.into_iter().map(|range| {
24313 let range = buffer_snapshot.anchor_before(range.start)
24314 ..buffer_snapshot.anchor_after(range.end);
24315 multibuffer_snapshot
24316 .anchor_range_in_excerpt(excerpt_id, range)
24317 .unwrap()
24318 }));
24319 },
24320 );
24321 editor.nav_history = nav_history;
24322 });
24323 }
24324 })
24325 });
24326 }
24327
24328 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
24329 let snapshot = self.buffer.read(cx).read(cx);
24330 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
24331 Some(
24332 ranges
24333 .iter()
24334 .map(move |range| {
24335 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
24336 })
24337 .collect(),
24338 )
24339 }
24340
24341 fn selection_replacement_ranges(
24342 &self,
24343 range: Range<MultiBufferOffsetUtf16>,
24344 cx: &mut App,
24345 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
24346 let selections = self
24347 .selections
24348 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24349 let newest_selection = selections
24350 .iter()
24351 .max_by_key(|selection| selection.id)
24352 .unwrap();
24353 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
24354 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
24355 let snapshot = self.buffer.read(cx).read(cx);
24356 selections
24357 .into_iter()
24358 .map(|mut selection| {
24359 selection.start.0.0 =
24360 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
24361 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
24362 snapshot.clip_offset_utf16(selection.start, Bias::Left)
24363 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
24364 })
24365 .collect()
24366 }
24367
24368 fn report_editor_event(
24369 &self,
24370 reported_event: ReportEditorEvent,
24371 file_extension: Option<String>,
24372 cx: &App,
24373 ) {
24374 if cfg!(any(test, feature = "test-support")) {
24375 return;
24376 }
24377
24378 let Some(project) = &self.project else { return };
24379
24380 // If None, we are in a file without an extension
24381 let file = self
24382 .buffer
24383 .read(cx)
24384 .as_singleton()
24385 .and_then(|b| b.read(cx).file());
24386 let file_extension = file_extension.or(file
24387 .as_ref()
24388 .and_then(|file| Path::new(file.file_name(cx)).extension())
24389 .and_then(|e| e.to_str())
24390 .map(|a| a.to_string()));
24391
24392 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
24393 .map(|vim_mode| vim_mode.0)
24394 .unwrap_or(false);
24395
24396 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
24397 let copilot_enabled = edit_predictions_provider
24398 == language::language_settings::EditPredictionProvider::Copilot;
24399 let copilot_enabled_for_language = self
24400 .buffer
24401 .read(cx)
24402 .language_settings(cx)
24403 .show_edit_predictions;
24404
24405 let project = project.read(cx);
24406 let event_type = reported_event.event_type();
24407
24408 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
24409 telemetry::event!(
24410 event_type,
24411 type = if auto_saved {"autosave"} else {"manual"},
24412 file_extension,
24413 vim_mode,
24414 copilot_enabled,
24415 copilot_enabled_for_language,
24416 edit_predictions_provider,
24417 is_via_ssh = project.is_via_remote_server(),
24418 );
24419 } else {
24420 telemetry::event!(
24421 event_type,
24422 file_extension,
24423 vim_mode,
24424 copilot_enabled,
24425 copilot_enabled_for_language,
24426 edit_predictions_provider,
24427 is_via_ssh = project.is_via_remote_server(),
24428 );
24429 };
24430 }
24431
24432 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
24433 /// with each line being an array of {text, highlight} objects.
24434 fn copy_highlight_json(
24435 &mut self,
24436 _: &CopyHighlightJson,
24437 window: &mut Window,
24438 cx: &mut Context<Self>,
24439 ) {
24440 #[derive(Serialize)]
24441 struct Chunk<'a> {
24442 text: String,
24443 highlight: Option<&'a str>,
24444 }
24445
24446 let snapshot = self.buffer.read(cx).snapshot(cx);
24447 let range = self
24448 .selected_text_range(false, window, cx)
24449 .and_then(|selection| {
24450 if selection.range.is_empty() {
24451 None
24452 } else {
24453 Some(
24454 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
24455 selection.range.start,
24456 )))
24457 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
24458 selection.range.end,
24459 ))),
24460 )
24461 }
24462 })
24463 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
24464
24465 let chunks = snapshot.chunks(range, true);
24466 let mut lines = Vec::new();
24467 let mut line: VecDeque<Chunk> = VecDeque::new();
24468
24469 let Some(style) = self.style.as_ref() else {
24470 return;
24471 };
24472
24473 for chunk in chunks {
24474 let highlight = chunk
24475 .syntax_highlight_id
24476 .and_then(|id| id.name(&style.syntax));
24477 let mut chunk_lines = chunk.text.split('\n').peekable();
24478 while let Some(text) = chunk_lines.next() {
24479 let mut merged_with_last_token = false;
24480 if let Some(last_token) = line.back_mut()
24481 && last_token.highlight == highlight
24482 {
24483 last_token.text.push_str(text);
24484 merged_with_last_token = true;
24485 }
24486
24487 if !merged_with_last_token {
24488 line.push_back(Chunk {
24489 text: text.into(),
24490 highlight,
24491 });
24492 }
24493
24494 if chunk_lines.peek().is_some() {
24495 if line.len() > 1 && line.front().unwrap().text.is_empty() {
24496 line.pop_front();
24497 }
24498 if line.len() > 1 && line.back().unwrap().text.is_empty() {
24499 line.pop_back();
24500 }
24501
24502 lines.push(mem::take(&mut line));
24503 }
24504 }
24505 }
24506
24507 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
24508 return;
24509 };
24510 cx.write_to_clipboard(ClipboardItem::new_string(lines));
24511 }
24512
24513 pub fn open_context_menu(
24514 &mut self,
24515 _: &OpenContextMenu,
24516 window: &mut Window,
24517 cx: &mut Context<Self>,
24518 ) {
24519 self.request_autoscroll(Autoscroll::newest(), cx);
24520 let position = self
24521 .selections
24522 .newest_display(&self.display_snapshot(cx))
24523 .start;
24524 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
24525 }
24526
24527 pub fn replay_insert_event(
24528 &mut self,
24529 text: &str,
24530 relative_utf16_range: Option<Range<isize>>,
24531 window: &mut Window,
24532 cx: &mut Context<Self>,
24533 ) {
24534 if !self.input_enabled {
24535 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24536 return;
24537 }
24538 if let Some(relative_utf16_range) = relative_utf16_range {
24539 let selections = self
24540 .selections
24541 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24542 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24543 let new_ranges = selections.into_iter().map(|range| {
24544 let start = MultiBufferOffsetUtf16(OffsetUtf16(
24545 range
24546 .head()
24547 .0
24548 .0
24549 .saturating_add_signed(relative_utf16_range.start),
24550 ));
24551 let end = MultiBufferOffsetUtf16(OffsetUtf16(
24552 range
24553 .head()
24554 .0
24555 .0
24556 .saturating_add_signed(relative_utf16_range.end),
24557 ));
24558 start..end
24559 });
24560 s.select_ranges(new_ranges);
24561 });
24562 }
24563
24564 self.handle_input(text, window, cx);
24565 }
24566
24567 pub fn is_focused(&self, window: &Window) -> bool {
24568 self.focus_handle.is_focused(window)
24569 }
24570
24571 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24572 cx.emit(EditorEvent::Focused);
24573
24574 if let Some(descendant) = self
24575 .last_focused_descendant
24576 .take()
24577 .and_then(|descendant| descendant.upgrade())
24578 {
24579 window.focus(&descendant, cx);
24580 } else {
24581 if let Some(blame) = self.blame.as_ref() {
24582 blame.update(cx, GitBlame::focus)
24583 }
24584
24585 self.blink_manager.update(cx, BlinkManager::enable);
24586 self.show_cursor_names(window, cx);
24587 self.buffer.update(cx, |buffer, cx| {
24588 buffer.finalize_last_transaction(cx);
24589 if self.leader_id.is_none() {
24590 buffer.set_active_selections(
24591 &self.selections.disjoint_anchors_arc(),
24592 self.selections.line_mode(),
24593 self.cursor_shape,
24594 cx,
24595 );
24596 }
24597 });
24598
24599 if let Some(position_map) = self.last_position_map.clone() {
24600 EditorElement::mouse_moved(
24601 self,
24602 &MouseMoveEvent {
24603 position: window.mouse_position(),
24604 pressed_button: None,
24605 modifiers: window.modifiers(),
24606 },
24607 &position_map,
24608 None,
24609 window,
24610 cx,
24611 );
24612 }
24613 }
24614 }
24615
24616 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24617 cx.emit(EditorEvent::FocusedIn)
24618 }
24619
24620 fn handle_focus_out(
24621 &mut self,
24622 event: FocusOutEvent,
24623 _window: &mut Window,
24624 cx: &mut Context<Self>,
24625 ) {
24626 if event.blurred != self.focus_handle {
24627 self.last_focused_descendant = Some(event.blurred);
24628 }
24629 self.selection_drag_state = SelectionDragState::None;
24630 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
24631 }
24632
24633 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24634 self.blink_manager.update(cx, BlinkManager::disable);
24635 self.buffer
24636 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
24637
24638 if let Some(blame) = self.blame.as_ref() {
24639 blame.update(cx, GitBlame::blur)
24640 }
24641 if !self.hover_state.focused(window, cx) {
24642 hide_hover(self, cx);
24643 }
24644 if !self
24645 .context_menu
24646 .borrow()
24647 .as_ref()
24648 .is_some_and(|context_menu| context_menu.focused(window, cx))
24649 {
24650 self.hide_context_menu(window, cx);
24651 }
24652 self.take_active_edit_prediction(cx);
24653 cx.emit(EditorEvent::Blurred);
24654 cx.notify();
24655 }
24656
24657 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24658 let mut pending: String = window
24659 .pending_input_keystrokes()
24660 .into_iter()
24661 .flatten()
24662 .filter_map(|keystroke| keystroke.key_char.clone())
24663 .collect();
24664
24665 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
24666 pending = "".to_string();
24667 }
24668
24669 let existing_pending = self
24670 .text_highlights::<PendingInput>(cx)
24671 .map(|(_, ranges)| ranges.to_vec());
24672 if existing_pending.is_none() && pending.is_empty() {
24673 return;
24674 }
24675 let transaction =
24676 self.transact(window, cx, |this, window, cx| {
24677 let selections = this
24678 .selections
24679 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
24680 let edits = selections
24681 .iter()
24682 .map(|selection| (selection.end..selection.end, pending.clone()));
24683 this.edit(edits, cx);
24684 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24685 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
24686 sel.start + ix * pending.len()..sel.end + ix * pending.len()
24687 }));
24688 });
24689 if let Some(existing_ranges) = existing_pending {
24690 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
24691 this.edit(edits, cx);
24692 }
24693 });
24694
24695 let snapshot = self.snapshot(window, cx);
24696 let ranges = self
24697 .selections
24698 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
24699 .into_iter()
24700 .map(|selection| {
24701 snapshot.buffer_snapshot().anchor_after(selection.end)
24702 ..snapshot
24703 .buffer_snapshot()
24704 .anchor_before(selection.end + pending.len())
24705 })
24706 .collect();
24707
24708 if pending.is_empty() {
24709 self.clear_highlights::<PendingInput>(cx);
24710 } else {
24711 self.highlight_text::<PendingInput>(
24712 ranges,
24713 HighlightStyle {
24714 underline: Some(UnderlineStyle {
24715 thickness: px(1.),
24716 color: None,
24717 wavy: false,
24718 }),
24719 ..Default::default()
24720 },
24721 cx,
24722 );
24723 }
24724
24725 self.ime_transaction = self.ime_transaction.or(transaction);
24726 if let Some(transaction) = self.ime_transaction {
24727 self.buffer.update(cx, |buffer, cx| {
24728 buffer.group_until_transaction(transaction, cx);
24729 });
24730 }
24731
24732 if self.text_highlights::<PendingInput>(cx).is_none() {
24733 self.ime_transaction.take();
24734 }
24735 }
24736
24737 pub fn register_action_renderer(
24738 &mut self,
24739 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
24740 ) -> Subscription {
24741 let id = self.next_editor_action_id.post_inc();
24742 self.editor_actions
24743 .borrow_mut()
24744 .insert(id, Box::new(listener));
24745
24746 let editor_actions = self.editor_actions.clone();
24747 Subscription::new(move || {
24748 editor_actions.borrow_mut().remove(&id);
24749 })
24750 }
24751
24752 pub fn register_action<A: Action>(
24753 &mut self,
24754 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
24755 ) -> Subscription {
24756 let id = self.next_editor_action_id.post_inc();
24757 let listener = Arc::new(listener);
24758 self.editor_actions.borrow_mut().insert(
24759 id,
24760 Box::new(move |_, window, _| {
24761 let listener = listener.clone();
24762 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
24763 let action = action.downcast_ref().unwrap();
24764 if phase == DispatchPhase::Bubble {
24765 listener(action, window, cx)
24766 }
24767 })
24768 }),
24769 );
24770
24771 let editor_actions = self.editor_actions.clone();
24772 Subscription::new(move || {
24773 editor_actions.borrow_mut().remove(&id);
24774 })
24775 }
24776
24777 pub fn file_header_size(&self) -> u32 {
24778 FILE_HEADER_HEIGHT
24779 }
24780
24781 pub fn restore(
24782 &mut self,
24783 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
24784 window: &mut Window,
24785 cx: &mut Context<Self>,
24786 ) {
24787 self.buffer().update(cx, |multi_buffer, cx| {
24788 for (buffer_id, changes) in revert_changes {
24789 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
24790 buffer.update(cx, |buffer, cx| {
24791 buffer.edit(
24792 changes
24793 .into_iter()
24794 .map(|(range, text)| (range, text.to_string())),
24795 None,
24796 cx,
24797 );
24798 });
24799 }
24800 }
24801 });
24802 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24803 selections.refresh()
24804 });
24805 }
24806
24807 pub fn to_pixel_point(
24808 &mut self,
24809 source: Anchor,
24810 editor_snapshot: &EditorSnapshot,
24811 window: &mut Window,
24812 cx: &mut App,
24813 ) -> Option<gpui::Point<Pixels>> {
24814 let source_point = source.to_display_point(editor_snapshot);
24815 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
24816 }
24817
24818 pub fn display_to_pixel_point(
24819 &mut self,
24820 source: DisplayPoint,
24821 editor_snapshot: &EditorSnapshot,
24822 window: &mut Window,
24823 cx: &mut App,
24824 ) -> Option<gpui::Point<Pixels>> {
24825 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
24826 let text_layout_details = self.text_layout_details(window, cx);
24827 let scroll_top = text_layout_details
24828 .scroll_anchor
24829 .scroll_position(editor_snapshot)
24830 .y;
24831
24832 if source.row().as_f64() < scroll_top.floor() {
24833 return None;
24834 }
24835 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
24836 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
24837 Some(gpui::Point::new(source_x, source_y))
24838 }
24839
24840 pub fn has_visible_completions_menu(&self) -> bool {
24841 !self.edit_prediction_preview_is_active()
24842 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
24843 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
24844 })
24845 }
24846
24847 pub fn register_addon<T: Addon>(&mut self, instance: T) {
24848 if self.mode.is_minimap() {
24849 return;
24850 }
24851 self.addons
24852 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
24853 }
24854
24855 pub fn unregister_addon<T: Addon>(&mut self) {
24856 self.addons.remove(&std::any::TypeId::of::<T>());
24857 }
24858
24859 pub fn addon<T: Addon>(&self) -> Option<&T> {
24860 let type_id = std::any::TypeId::of::<T>();
24861 self.addons
24862 .get(&type_id)
24863 .and_then(|item| item.to_any().downcast_ref::<T>())
24864 }
24865
24866 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
24867 let type_id = std::any::TypeId::of::<T>();
24868 self.addons
24869 .get_mut(&type_id)
24870 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
24871 }
24872
24873 fn character_dimensions(&self, window: &mut Window, cx: &mut App) -> CharacterDimensions {
24874 let text_layout_details = self.text_layout_details(window, cx);
24875 let style = &text_layout_details.editor_style;
24876 let font_id = window.text_system().resolve_font(&style.text.font());
24877 let font_size = style.text.font_size.to_pixels(window.rem_size());
24878 let line_height = style.text.line_height_in_pixels(window.rem_size());
24879 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
24880 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
24881
24882 CharacterDimensions {
24883 em_width,
24884 em_advance,
24885 line_height,
24886 }
24887 }
24888
24889 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
24890 self.load_diff_task.clone()
24891 }
24892
24893 fn read_metadata_from_db(
24894 &mut self,
24895 item_id: u64,
24896 workspace_id: WorkspaceId,
24897 window: &mut Window,
24898 cx: &mut Context<Editor>,
24899 ) {
24900 if self.buffer_kind(cx) == ItemBufferKind::Singleton
24901 && !self.mode.is_minimap()
24902 && WorkspaceSettings::get(None, cx).restore_on_startup
24903 != RestoreOnStartupBehavior::EmptyTab
24904 {
24905 let buffer_snapshot = OnceCell::new();
24906
24907 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
24908 && !folds.is_empty()
24909 {
24910 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
24911 let snapshot_len = snapshot.len().0;
24912
24913 // Helper: search for fingerprint in buffer, return offset if found
24914 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
24915 // Ensure we start at a character boundary (defensive)
24916 let search_start = snapshot
24917 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
24918 .0;
24919 let search_end = snapshot_len.saturating_sub(fingerprint.len());
24920
24921 let mut byte_offset = search_start;
24922 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
24923 if byte_offset > search_end {
24924 break;
24925 }
24926 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
24927 return Some(byte_offset);
24928 }
24929 byte_offset += ch.len_utf8();
24930 }
24931 None
24932 };
24933
24934 // Track search position to handle duplicate fingerprints correctly.
24935 // Folds are stored in document order, so we advance after each match.
24936 let mut search_start = 0usize;
24937
24938 let valid_folds: Vec<_> = folds
24939 .into_iter()
24940 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
24941 // Skip folds without fingerprints (old data before migration)
24942 let sfp = start_fp?;
24943 let efp = end_fp?;
24944 let efp_len = efp.len();
24945
24946 // Fast path: check if fingerprints match at stored offsets
24947 // Note: end_fp is content BEFORE fold end, so check at (stored_end - efp_len)
24948 let start_matches = stored_start < snapshot_len
24949 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
24950 let efp_check_pos = stored_end.saturating_sub(efp_len);
24951 let end_matches = efp_check_pos >= stored_start
24952 && stored_end <= snapshot_len
24953 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
24954
24955 let (new_start, new_end) = if start_matches && end_matches {
24956 // Offsets unchanged, use stored values
24957 (stored_start, stored_end)
24958 } else if sfp == efp {
24959 // Short fold: identical fingerprints can only match once per search
24960 // Use stored fold length to compute new_end
24961 let new_start = find_fingerprint(&sfp, search_start)?;
24962 let fold_len = stored_end - stored_start;
24963 let new_end = new_start + fold_len;
24964 (new_start, new_end)
24965 } else {
24966 // Slow path: search for fingerprints in buffer
24967 let new_start = find_fingerprint(&sfp, search_start)?;
24968 // Search for end_fp after start, then add efp_len to get actual fold end
24969 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
24970 let new_end = efp_pos + efp_len;
24971 (new_start, new_end)
24972 };
24973
24974 // Advance search position for next fold
24975 search_start = new_end;
24976
24977 // Validate fold makes sense (end must be after start)
24978 if new_end <= new_start {
24979 return None;
24980 }
24981
24982 Some(
24983 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
24984 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
24985 )
24986 })
24987 .collect();
24988
24989 if !valid_folds.is_empty() {
24990 self.fold_ranges(valid_folds, false, window, cx);
24991
24992 // Migrate folds to current entity_id before workspace cleanup runs.
24993 // Entity IDs change between sessions, but workspace cleanup deletes
24994 // old editor rows (cascading to folds) based on current entity IDs.
24995 let new_editor_id = cx.entity().entity_id().as_u64() as ItemId;
24996 if new_editor_id != item_id {
24997 cx.spawn(async move |_, _| {
24998 DB.migrate_editor_folds(item_id, new_editor_id, workspace_id)
24999 .await
25000 .log_err();
25001 })
25002 .detach();
25003 }
25004 }
25005 }
25006
25007 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
25008 && !selections.is_empty()
25009 {
25010 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25011 // skip adding the initial selection to selection history
25012 self.selection_history.mode = SelectionHistoryMode::Skipping;
25013 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25014 s.select_ranges(selections.into_iter().map(|(start, end)| {
25015 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
25016 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
25017 }));
25018 });
25019 self.selection_history.mode = SelectionHistoryMode::Normal;
25020 };
25021 }
25022
25023 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
25024 }
25025
25026 fn update_lsp_data(
25027 &mut self,
25028 for_buffer: Option<BufferId>,
25029 window: &mut Window,
25030 cx: &mut Context<'_, Self>,
25031 ) {
25032 if let Some(buffer_id) = for_buffer {
25033 self.pull_diagnostics(buffer_id, window, cx);
25034 }
25035 self.refresh_colors_for_visible_range(for_buffer, window, cx);
25036 }
25037
25038 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
25039 if self.ignore_lsp_data() {
25040 return;
25041 }
25042 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
25043 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
25044 }
25045 }
25046
25047 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
25048 if self.ignore_lsp_data() {
25049 return;
25050 }
25051
25052 if !self.registered_buffers.contains_key(&buffer_id)
25053 && let Some(project) = self.project.as_ref()
25054 {
25055 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
25056 project.update(cx, |project, cx| {
25057 self.registered_buffers.insert(
25058 buffer_id,
25059 project.register_buffer_with_language_servers(&buffer, cx),
25060 );
25061 });
25062 } else {
25063 self.registered_buffers.remove(&buffer_id);
25064 }
25065 }
25066 }
25067
25068 fn ignore_lsp_data(&self) -> bool {
25069 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
25070 // skip any LSP updates for it.
25071 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
25072 }
25073
25074 pub(crate) fn create_style(&self, cx: &App) -> EditorStyle {
25075 let settings = ThemeSettings::get_global(cx);
25076
25077 let mut text_style = match self.mode {
25078 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
25079 color: cx.theme().colors().editor_foreground,
25080 font_family: settings.ui_font.family.clone(),
25081 font_features: settings.ui_font.features.clone(),
25082 font_fallbacks: settings.ui_font.fallbacks.clone(),
25083 font_size: rems(0.875).into(),
25084 font_weight: settings.ui_font.weight,
25085 line_height: relative(settings.buffer_line_height.value()),
25086 ..Default::default()
25087 },
25088 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
25089 color: cx.theme().colors().editor_foreground,
25090 font_family: settings.buffer_font.family.clone(),
25091 font_features: settings.buffer_font.features.clone(),
25092 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25093 font_size: settings.buffer_font_size(cx).into(),
25094 font_weight: settings.buffer_font.weight,
25095 line_height: relative(settings.buffer_line_height.value()),
25096 ..Default::default()
25097 },
25098 };
25099 if let Some(text_style_refinement) = &self.text_style_refinement {
25100 text_style.refine(text_style_refinement)
25101 }
25102
25103 let background = match self.mode {
25104 EditorMode::SingleLine => cx.theme().system().transparent,
25105 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
25106 EditorMode::Full { .. } => cx.theme().colors().editor_background,
25107 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
25108 };
25109
25110 EditorStyle {
25111 background,
25112 border: cx.theme().colors().border,
25113 local_player: cx.theme().players().local(),
25114 text: text_style,
25115 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
25116 syntax: cx.theme().syntax().clone(),
25117 status: cx.theme().status().clone(),
25118 inlay_hints_style: make_inlay_hints_style(cx),
25119 edit_prediction_styles: make_suggestion_styles(cx),
25120 unnecessary_code_fade: settings.unnecessary_code_fade,
25121 show_underlines: self.diagnostics_enabled(),
25122 }
25123 }
25124 fn breadcrumbs_inner(&self, variant: &Theme, cx: &App) -> Option<Vec<BreadcrumbText>> {
25125 let cursor = self.selections.newest_anchor().head();
25126 let multibuffer = self.buffer().read(cx);
25127 let is_singleton = multibuffer.is_singleton();
25128 let (buffer_id, symbols) = multibuffer
25129 .read(cx)
25130 .symbols_containing(cursor, Some(variant.syntax()))?;
25131 let buffer = multibuffer.buffer(buffer_id)?;
25132
25133 let buffer = buffer.read(cx);
25134 let settings = ThemeSettings::get_global(cx);
25135 // In a multi-buffer layout, we don't want to include the filename in the breadcrumbs
25136 let mut breadcrumbs = if is_singleton {
25137 let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
25138 buffer
25139 .snapshot()
25140 .resolve_file_path(
25141 self.project
25142 .as_ref()
25143 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
25144 .unwrap_or_default(),
25145 cx,
25146 )
25147 .unwrap_or_else(|| {
25148 if multibuffer.is_singleton() {
25149 multibuffer.title(cx).to_string()
25150 } else {
25151 "untitled".to_string()
25152 }
25153 })
25154 });
25155 vec![BreadcrumbText {
25156 text,
25157 highlights: None,
25158 font: Some(settings.buffer_font.clone()),
25159 }]
25160 } else {
25161 vec![]
25162 };
25163
25164 breadcrumbs.extend(symbols.into_iter().map(|symbol| BreadcrumbText {
25165 text: symbol.text,
25166 highlights: Some(symbol.highlight_ranges),
25167 font: Some(settings.buffer_font.clone()),
25168 }));
25169 Some(breadcrumbs)
25170 }
25171}
25172
25173fn edit_for_markdown_paste<'a>(
25174 buffer: &MultiBufferSnapshot,
25175 range: Range<MultiBufferOffset>,
25176 to_insert: &'a str,
25177 url: Option<url::Url>,
25178) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
25179 if url.is_none() {
25180 return (range, Cow::Borrowed(to_insert));
25181 };
25182
25183 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
25184
25185 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
25186 Cow::Borrowed(to_insert)
25187 } else {
25188 Cow::Owned(format!("[{old_text}]({to_insert})"))
25189 };
25190 (range, new_text)
25191}
25192
25193fn process_completion_for_edit(
25194 completion: &Completion,
25195 intent: CompletionIntent,
25196 buffer: &Entity<Buffer>,
25197 cursor_position: &text::Anchor,
25198 cx: &mut Context<Editor>,
25199) -> CompletionEdit {
25200 let buffer = buffer.read(cx);
25201 let buffer_snapshot = buffer.snapshot();
25202 let (snippet, new_text) = if completion.is_snippet() {
25203 let mut snippet_source = completion.new_text.clone();
25204 // Workaround for typescript language server issues so that methods don't expand within
25205 // strings and functions with type expressions. The previous point is used because the query
25206 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
25207 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
25208 let previous_point = if previous_point.column > 0 {
25209 cursor_position.to_previous_offset(&buffer_snapshot)
25210 } else {
25211 cursor_position.to_offset(&buffer_snapshot)
25212 };
25213 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
25214 && scope.prefers_label_for_snippet_in_completion()
25215 && let Some(label) = completion.label()
25216 && matches!(
25217 completion.kind(),
25218 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
25219 )
25220 {
25221 snippet_source = label;
25222 }
25223 match Snippet::parse(&snippet_source).log_err() {
25224 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
25225 None => (None, completion.new_text.clone()),
25226 }
25227 } else {
25228 (None, completion.new_text.clone())
25229 };
25230
25231 let mut range_to_replace = {
25232 let replace_range = &completion.replace_range;
25233 if let CompletionSource::Lsp {
25234 insert_range: Some(insert_range),
25235 ..
25236 } = &completion.source
25237 {
25238 debug_assert_eq!(
25239 insert_range.start, replace_range.start,
25240 "insert_range and replace_range should start at the same position"
25241 );
25242 debug_assert!(
25243 insert_range
25244 .start
25245 .cmp(cursor_position, &buffer_snapshot)
25246 .is_le(),
25247 "insert_range should start before or at cursor position"
25248 );
25249 debug_assert!(
25250 replace_range
25251 .start
25252 .cmp(cursor_position, &buffer_snapshot)
25253 .is_le(),
25254 "replace_range should start before or at cursor position"
25255 );
25256
25257 let should_replace = match intent {
25258 CompletionIntent::CompleteWithInsert => false,
25259 CompletionIntent::CompleteWithReplace => true,
25260 CompletionIntent::Complete | CompletionIntent::Compose => {
25261 let insert_mode =
25262 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
25263 .completions
25264 .lsp_insert_mode;
25265 match insert_mode {
25266 LspInsertMode::Insert => false,
25267 LspInsertMode::Replace => true,
25268 LspInsertMode::ReplaceSubsequence => {
25269 let mut text_to_replace = buffer.chars_for_range(
25270 buffer.anchor_before(replace_range.start)
25271 ..buffer.anchor_after(replace_range.end),
25272 );
25273 let mut current_needle = text_to_replace.next();
25274 for haystack_ch in completion.label.text.chars() {
25275 if let Some(needle_ch) = current_needle
25276 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
25277 {
25278 current_needle = text_to_replace.next();
25279 }
25280 }
25281 current_needle.is_none()
25282 }
25283 LspInsertMode::ReplaceSuffix => {
25284 if replace_range
25285 .end
25286 .cmp(cursor_position, &buffer_snapshot)
25287 .is_gt()
25288 {
25289 let range_after_cursor = *cursor_position..replace_range.end;
25290 let text_after_cursor = buffer
25291 .text_for_range(
25292 buffer.anchor_before(range_after_cursor.start)
25293 ..buffer.anchor_after(range_after_cursor.end),
25294 )
25295 .collect::<String>()
25296 .to_ascii_lowercase();
25297 completion
25298 .label
25299 .text
25300 .to_ascii_lowercase()
25301 .ends_with(&text_after_cursor)
25302 } else {
25303 true
25304 }
25305 }
25306 }
25307 }
25308 };
25309
25310 if should_replace {
25311 replace_range.clone()
25312 } else {
25313 insert_range.clone()
25314 }
25315 } else {
25316 replace_range.clone()
25317 }
25318 };
25319
25320 if range_to_replace
25321 .end
25322 .cmp(cursor_position, &buffer_snapshot)
25323 .is_lt()
25324 {
25325 range_to_replace.end = *cursor_position;
25326 }
25327
25328 let replace_range = range_to_replace.to_offset(buffer);
25329 CompletionEdit {
25330 new_text,
25331 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
25332 snippet,
25333 }
25334}
25335
25336struct CompletionEdit {
25337 new_text: String,
25338 replace_range: Range<BufferOffset>,
25339 snippet: Option<Snippet>,
25340}
25341
25342fn comment_delimiter_for_newline(
25343 start_point: &Point,
25344 buffer: &MultiBufferSnapshot,
25345 language: &LanguageScope,
25346) -> Option<Arc<str>> {
25347 let delimiters = language.line_comment_prefixes();
25348 let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
25349 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25350
25351 let num_of_whitespaces = snapshot
25352 .chars_for_range(range.clone())
25353 .take_while(|c| c.is_whitespace())
25354 .count();
25355 let comment_candidate = snapshot
25356 .chars_for_range(range.clone())
25357 .skip(num_of_whitespaces)
25358 .take(max_len_of_delimiter)
25359 .collect::<String>();
25360 let (delimiter, trimmed_len) = delimiters
25361 .iter()
25362 .filter_map(|delimiter| {
25363 let prefix = delimiter.trim_end();
25364 if comment_candidate.starts_with(prefix) {
25365 Some((delimiter, prefix.len()))
25366 } else {
25367 None
25368 }
25369 })
25370 .max_by_key(|(_, len)| *len)?;
25371
25372 if let Some(BlockCommentConfig {
25373 start: block_start, ..
25374 }) = language.block_comment()
25375 {
25376 let block_start_trimmed = block_start.trim_end();
25377 if block_start_trimmed.starts_with(delimiter.trim_end()) {
25378 let line_content = snapshot
25379 .chars_for_range(range)
25380 .skip(num_of_whitespaces)
25381 .take(block_start_trimmed.len())
25382 .collect::<String>();
25383
25384 if line_content.starts_with(block_start_trimmed) {
25385 return None;
25386 }
25387 }
25388 }
25389
25390 let cursor_is_placed_after_comment_marker =
25391 num_of_whitespaces + trimmed_len <= start_point.column as usize;
25392 if cursor_is_placed_after_comment_marker {
25393 Some(delimiter.clone())
25394 } else {
25395 None
25396 }
25397}
25398
25399fn documentation_delimiter_for_newline(
25400 start_point: &Point,
25401 buffer: &MultiBufferSnapshot,
25402 language: &LanguageScope,
25403 newline_config: &mut NewlineConfig,
25404) -> Option<Arc<str>> {
25405 let BlockCommentConfig {
25406 start: start_tag,
25407 end: end_tag,
25408 prefix: delimiter,
25409 tab_size: len,
25410 } = language.documentation_comment()?;
25411 let is_within_block_comment = buffer
25412 .language_scope_at(*start_point)
25413 .is_some_and(|scope| scope.override_name() == Some("comment"));
25414 if !is_within_block_comment {
25415 return None;
25416 }
25417
25418 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25419
25420 let num_of_whitespaces = snapshot
25421 .chars_for_range(range.clone())
25422 .take_while(|c| c.is_whitespace())
25423 .count();
25424
25425 // 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.
25426 let column = start_point.column;
25427 let cursor_is_after_start_tag = {
25428 let start_tag_len = start_tag.len();
25429 let start_tag_line = snapshot
25430 .chars_for_range(range.clone())
25431 .skip(num_of_whitespaces)
25432 .take(start_tag_len)
25433 .collect::<String>();
25434 if start_tag_line.starts_with(start_tag.as_ref()) {
25435 num_of_whitespaces + start_tag_len <= column as usize
25436 } else {
25437 false
25438 }
25439 };
25440
25441 let cursor_is_after_delimiter = {
25442 let delimiter_trim = delimiter.trim_end();
25443 let delimiter_line = snapshot
25444 .chars_for_range(range.clone())
25445 .skip(num_of_whitespaces)
25446 .take(delimiter_trim.len())
25447 .collect::<String>();
25448 if delimiter_line.starts_with(delimiter_trim) {
25449 num_of_whitespaces + delimiter_trim.len() <= column as usize
25450 } else {
25451 false
25452 }
25453 };
25454
25455 let mut needs_extra_line = false;
25456 let mut extra_line_additional_indent = IndentSize::spaces(0);
25457
25458 let cursor_is_before_end_tag_if_exists = {
25459 let mut char_position = 0u32;
25460 let mut end_tag_offset = None;
25461
25462 'outer: for chunk in snapshot.text_for_range(range) {
25463 if let Some(byte_pos) = chunk.find(&**end_tag) {
25464 let chars_before_match = chunk[..byte_pos].chars().count() as u32;
25465 end_tag_offset = Some(char_position + chars_before_match);
25466 break 'outer;
25467 }
25468 char_position += chunk.chars().count() as u32;
25469 }
25470
25471 if let Some(end_tag_offset) = end_tag_offset {
25472 let cursor_is_before_end_tag = column <= end_tag_offset;
25473 if cursor_is_after_start_tag {
25474 if cursor_is_before_end_tag {
25475 needs_extra_line = true;
25476 }
25477 let cursor_is_at_start_of_end_tag = column == end_tag_offset;
25478 if cursor_is_at_start_of_end_tag {
25479 extra_line_additional_indent.len = *len;
25480 }
25481 }
25482 cursor_is_before_end_tag
25483 } else {
25484 true
25485 }
25486 };
25487
25488 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
25489 && cursor_is_before_end_tag_if_exists
25490 {
25491 let additional_indent = if cursor_is_after_start_tag {
25492 IndentSize::spaces(*len)
25493 } else {
25494 IndentSize::spaces(0)
25495 };
25496
25497 *newline_config = NewlineConfig::Newline {
25498 additional_indent,
25499 extra_line_additional_indent: if needs_extra_line {
25500 Some(extra_line_additional_indent)
25501 } else {
25502 None
25503 },
25504 prevent_auto_indent: true,
25505 };
25506 Some(delimiter.clone())
25507 } else {
25508 None
25509 }
25510}
25511
25512const ORDERED_LIST_MAX_MARKER_LEN: usize = 16;
25513
25514fn list_delimiter_for_newline(
25515 start_point: &Point,
25516 buffer: &MultiBufferSnapshot,
25517 language: &LanguageScope,
25518 newline_config: &mut NewlineConfig,
25519) -> Option<Arc<str>> {
25520 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25521
25522 let num_of_whitespaces = snapshot
25523 .chars_for_range(range.clone())
25524 .take_while(|c| c.is_whitespace())
25525 .count();
25526
25527 let task_list_entries: Vec<_> = language
25528 .task_list()
25529 .into_iter()
25530 .flat_map(|config| {
25531 config
25532 .prefixes
25533 .iter()
25534 .map(|prefix| (prefix.as_ref(), config.continuation.as_ref()))
25535 })
25536 .collect();
25537 let unordered_list_entries: Vec<_> = language
25538 .unordered_list()
25539 .iter()
25540 .map(|marker| (marker.as_ref(), marker.as_ref()))
25541 .collect();
25542
25543 let all_entries: Vec<_> = task_list_entries
25544 .into_iter()
25545 .chain(unordered_list_entries)
25546 .collect();
25547
25548 if let Some(max_prefix_len) = all_entries.iter().map(|(p, _)| p.len()).max() {
25549 let candidate: String = snapshot
25550 .chars_for_range(range.clone())
25551 .skip(num_of_whitespaces)
25552 .take(max_prefix_len)
25553 .collect();
25554
25555 if let Some((prefix, continuation)) = all_entries
25556 .iter()
25557 .filter(|(prefix, _)| candidate.starts_with(*prefix))
25558 .max_by_key(|(prefix, _)| prefix.len())
25559 {
25560 let end_of_prefix = num_of_whitespaces + prefix.len();
25561 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
25562 let has_content_after_marker = snapshot
25563 .chars_for_range(range)
25564 .skip(end_of_prefix)
25565 .any(|c| !c.is_whitespace());
25566
25567 if has_content_after_marker && cursor_is_after_prefix {
25568 return Some((*continuation).into());
25569 }
25570
25571 if start_point.column as usize == end_of_prefix {
25572 if num_of_whitespaces == 0 {
25573 *newline_config = NewlineConfig::ClearCurrentLine;
25574 } else {
25575 *newline_config = NewlineConfig::UnindentCurrentLine {
25576 continuation: (*continuation).into(),
25577 };
25578 }
25579 }
25580
25581 return None;
25582 }
25583 }
25584
25585 let candidate: String = snapshot
25586 .chars_for_range(range.clone())
25587 .skip(num_of_whitespaces)
25588 .take(ORDERED_LIST_MAX_MARKER_LEN)
25589 .collect();
25590
25591 for ordered_config in language.ordered_list() {
25592 let regex = match Regex::new(&ordered_config.pattern) {
25593 Ok(r) => r,
25594 Err(_) => continue,
25595 };
25596
25597 if let Some(captures) = regex.captures(&candidate) {
25598 let full_match = captures.get(0)?;
25599 let marker_len = full_match.len();
25600 let end_of_prefix = num_of_whitespaces + marker_len;
25601 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
25602
25603 let has_content_after_marker = snapshot
25604 .chars_for_range(range)
25605 .skip(end_of_prefix)
25606 .any(|c| !c.is_whitespace());
25607
25608 if has_content_after_marker && cursor_is_after_prefix {
25609 let number: u32 = captures.get(1)?.as_str().parse().ok()?;
25610 let continuation = ordered_config
25611 .format
25612 .replace("{1}", &(number + 1).to_string());
25613 return Some(continuation.into());
25614 }
25615
25616 if start_point.column as usize == end_of_prefix {
25617 let continuation = ordered_config.format.replace("{1}", "1");
25618 if num_of_whitespaces == 0 {
25619 *newline_config = NewlineConfig::ClearCurrentLine;
25620 } else {
25621 *newline_config = NewlineConfig::UnindentCurrentLine {
25622 continuation: continuation.into(),
25623 };
25624 }
25625 }
25626
25627 return None;
25628 }
25629 }
25630
25631 None
25632}
25633
25634fn is_list_prefix_row(
25635 row: MultiBufferRow,
25636 buffer: &MultiBufferSnapshot,
25637 language: &LanguageScope,
25638) -> bool {
25639 let Some((snapshot, range)) = buffer.buffer_line_for_row(row) else {
25640 return false;
25641 };
25642
25643 let num_of_whitespaces = snapshot
25644 .chars_for_range(range.clone())
25645 .take_while(|c| c.is_whitespace())
25646 .count();
25647
25648 let task_list_prefixes: Vec<_> = language
25649 .task_list()
25650 .into_iter()
25651 .flat_map(|config| {
25652 config
25653 .prefixes
25654 .iter()
25655 .map(|p| p.as_ref())
25656 .collect::<Vec<_>>()
25657 })
25658 .collect();
25659 let unordered_list_markers: Vec<_> = language
25660 .unordered_list()
25661 .iter()
25662 .map(|marker| marker.as_ref())
25663 .collect();
25664 let all_prefixes: Vec<_> = task_list_prefixes
25665 .into_iter()
25666 .chain(unordered_list_markers)
25667 .collect();
25668 if let Some(max_prefix_len) = all_prefixes.iter().map(|p| p.len()).max() {
25669 let candidate: String = snapshot
25670 .chars_for_range(range.clone())
25671 .skip(num_of_whitespaces)
25672 .take(max_prefix_len)
25673 .collect();
25674 if all_prefixes
25675 .iter()
25676 .any(|prefix| candidate.starts_with(*prefix))
25677 {
25678 return true;
25679 }
25680 }
25681
25682 let ordered_list_candidate: String = snapshot
25683 .chars_for_range(range)
25684 .skip(num_of_whitespaces)
25685 .take(ORDERED_LIST_MAX_MARKER_LEN)
25686 .collect();
25687 for ordered_config in language.ordered_list() {
25688 let regex = match Regex::new(&ordered_config.pattern) {
25689 Ok(r) => r,
25690 Err(_) => continue,
25691 };
25692 if let Some(captures) = regex.captures(&ordered_list_candidate) {
25693 return captures.get(0).is_some();
25694 }
25695 }
25696
25697 false
25698}
25699
25700#[derive(Debug)]
25701enum NewlineConfig {
25702 /// Insert newline with optional additional indent and optional extra blank line
25703 Newline {
25704 additional_indent: IndentSize,
25705 extra_line_additional_indent: Option<IndentSize>,
25706 prevent_auto_indent: bool,
25707 },
25708 /// Clear the current line
25709 ClearCurrentLine,
25710 /// Unindent the current line and add continuation
25711 UnindentCurrentLine { continuation: Arc<str> },
25712}
25713
25714impl NewlineConfig {
25715 fn has_extra_line(&self) -> bool {
25716 matches!(
25717 self,
25718 Self::Newline {
25719 extra_line_additional_indent: Some(_),
25720 ..
25721 }
25722 )
25723 }
25724
25725 fn insert_extra_newline_brackets(
25726 buffer: &MultiBufferSnapshot,
25727 range: Range<MultiBufferOffset>,
25728 language: &language::LanguageScope,
25729 ) -> bool {
25730 let leading_whitespace_len = buffer
25731 .reversed_chars_at(range.start)
25732 .take_while(|c| c.is_whitespace() && *c != '\n')
25733 .map(|c| c.len_utf8())
25734 .sum::<usize>();
25735 let trailing_whitespace_len = buffer
25736 .chars_at(range.end)
25737 .take_while(|c| c.is_whitespace() && *c != '\n')
25738 .map(|c| c.len_utf8())
25739 .sum::<usize>();
25740 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
25741
25742 language.brackets().any(|(pair, enabled)| {
25743 let pair_start = pair.start.trim_end();
25744 let pair_end = pair.end.trim_start();
25745
25746 enabled
25747 && pair.newline
25748 && buffer.contains_str_at(range.end, pair_end)
25749 && buffer.contains_str_at(
25750 range.start.saturating_sub_usize(pair_start.len()),
25751 pair_start,
25752 )
25753 })
25754 }
25755
25756 fn insert_extra_newline_tree_sitter(
25757 buffer: &MultiBufferSnapshot,
25758 range: Range<MultiBufferOffset>,
25759 ) -> bool {
25760 let (buffer, range) = match buffer
25761 .range_to_buffer_ranges(range.start..=range.end)
25762 .as_slice()
25763 {
25764 [(buffer, range, _)] => (*buffer, range.clone()),
25765 _ => return false,
25766 };
25767 let pair = {
25768 let mut result: Option<BracketMatch<usize>> = None;
25769
25770 for pair in buffer
25771 .all_bracket_ranges(range.start.0..range.end.0)
25772 .filter(move |pair| {
25773 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
25774 })
25775 {
25776 let len = pair.close_range.end - pair.open_range.start;
25777
25778 if let Some(existing) = &result {
25779 let existing_len = existing.close_range.end - existing.open_range.start;
25780 if len > existing_len {
25781 continue;
25782 }
25783 }
25784
25785 result = Some(pair);
25786 }
25787
25788 result
25789 };
25790 let Some(pair) = pair else {
25791 return false;
25792 };
25793 pair.newline_only
25794 && buffer
25795 .chars_for_range(pair.open_range.end..range.start.0)
25796 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
25797 .all(|c| c.is_whitespace() && c != '\n')
25798 }
25799}
25800
25801fn update_uncommitted_diff_for_buffer(
25802 editor: Entity<Editor>,
25803 project: &Entity<Project>,
25804 buffers: impl IntoIterator<Item = Entity<Buffer>>,
25805 buffer: Entity<MultiBuffer>,
25806 cx: &mut App,
25807) -> Task<()> {
25808 let mut tasks = Vec::new();
25809 project.update(cx, |project, cx| {
25810 for buffer in buffers {
25811 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
25812 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
25813 }
25814 }
25815 });
25816 cx.spawn(async move |cx| {
25817 let diffs = future::join_all(tasks).await;
25818 if editor.read_with(cx, |editor, _cx| editor.temporary_diff_override) {
25819 return;
25820 }
25821
25822 buffer.update(cx, |buffer, cx| {
25823 for diff in diffs.into_iter().flatten() {
25824 buffer.add_diff(diff, cx);
25825 }
25826 });
25827 })
25828}
25829
25830fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
25831 let tab_size = tab_size.get() as usize;
25832 let mut width = offset;
25833
25834 for ch in text.chars() {
25835 width += if ch == '\t' {
25836 tab_size - (width % tab_size)
25837 } else {
25838 1
25839 };
25840 }
25841
25842 width - offset
25843}
25844
25845#[cfg(test)]
25846mod tests {
25847 use super::*;
25848
25849 #[test]
25850 fn test_string_size_with_expanded_tabs() {
25851 let nz = |val| NonZeroU32::new(val).unwrap();
25852 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
25853 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
25854 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
25855 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
25856 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
25857 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
25858 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
25859 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
25860 }
25861}
25862
25863/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
25864struct WordBreakingTokenizer<'a> {
25865 input: &'a str,
25866}
25867
25868impl<'a> WordBreakingTokenizer<'a> {
25869 fn new(input: &'a str) -> Self {
25870 Self { input }
25871 }
25872}
25873
25874fn is_char_ideographic(ch: char) -> bool {
25875 use unicode_script::Script::*;
25876 use unicode_script::UnicodeScript;
25877 matches!(ch.script(), Han | Tangut | Yi)
25878}
25879
25880fn is_grapheme_ideographic(text: &str) -> bool {
25881 text.chars().any(is_char_ideographic)
25882}
25883
25884fn is_grapheme_whitespace(text: &str) -> bool {
25885 text.chars().any(|x| x.is_whitespace())
25886}
25887
25888fn should_stay_with_preceding_ideograph(text: &str) -> bool {
25889 text.chars()
25890 .next()
25891 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
25892}
25893
25894#[derive(PartialEq, Eq, Debug, Clone, Copy)]
25895enum WordBreakToken<'a> {
25896 Word { token: &'a str, grapheme_len: usize },
25897 InlineWhitespace { token: &'a str, grapheme_len: usize },
25898 Newline,
25899}
25900
25901impl<'a> Iterator for WordBreakingTokenizer<'a> {
25902 /// Yields a span, the count of graphemes in the token, and whether it was
25903 /// whitespace. Note that it also breaks at word boundaries.
25904 type Item = WordBreakToken<'a>;
25905
25906 fn next(&mut self) -> Option<Self::Item> {
25907 use unicode_segmentation::UnicodeSegmentation;
25908 if self.input.is_empty() {
25909 return None;
25910 }
25911
25912 let mut iter = self.input.graphemes(true).peekable();
25913 let mut offset = 0;
25914 let mut grapheme_len = 0;
25915 if let Some(first_grapheme) = iter.next() {
25916 let is_newline = first_grapheme == "\n";
25917 let is_whitespace = is_grapheme_whitespace(first_grapheme);
25918 offset += first_grapheme.len();
25919 grapheme_len += 1;
25920 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
25921 if let Some(grapheme) = iter.peek().copied()
25922 && should_stay_with_preceding_ideograph(grapheme)
25923 {
25924 offset += grapheme.len();
25925 grapheme_len += 1;
25926 }
25927 } else {
25928 let mut words = self.input[offset..].split_word_bound_indices().peekable();
25929 let mut next_word_bound = words.peek().copied();
25930 if next_word_bound.is_some_and(|(i, _)| i == 0) {
25931 next_word_bound = words.next();
25932 }
25933 while let Some(grapheme) = iter.peek().copied() {
25934 if next_word_bound.is_some_and(|(i, _)| i == offset) {
25935 break;
25936 };
25937 if is_grapheme_whitespace(grapheme) != is_whitespace
25938 || (grapheme == "\n") != is_newline
25939 {
25940 break;
25941 };
25942 offset += grapheme.len();
25943 grapheme_len += 1;
25944 iter.next();
25945 }
25946 }
25947 let token = &self.input[..offset];
25948 self.input = &self.input[offset..];
25949 if token == "\n" {
25950 Some(WordBreakToken::Newline)
25951 } else if is_whitespace {
25952 Some(WordBreakToken::InlineWhitespace {
25953 token,
25954 grapheme_len,
25955 })
25956 } else {
25957 Some(WordBreakToken::Word {
25958 token,
25959 grapheme_len,
25960 })
25961 }
25962 } else {
25963 None
25964 }
25965 }
25966}
25967
25968#[test]
25969fn test_word_breaking_tokenizer() {
25970 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
25971 ("", &[]),
25972 (" ", &[whitespace(" ", 2)]),
25973 ("Ʒ", &[word("Ʒ", 1)]),
25974 ("Ǽ", &[word("Ǽ", 1)]),
25975 ("⋑", &[word("⋑", 1)]),
25976 ("⋑⋑", &[word("⋑⋑", 2)]),
25977 (
25978 "原理,进而",
25979 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
25980 ),
25981 (
25982 "hello world",
25983 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
25984 ),
25985 (
25986 "hello, world",
25987 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
25988 ),
25989 (
25990 " hello world",
25991 &[
25992 whitespace(" ", 2),
25993 word("hello", 5),
25994 whitespace(" ", 1),
25995 word("world", 5),
25996 ],
25997 ),
25998 (
25999 "这是什么 \n 钢笔",
26000 &[
26001 word("这", 1),
26002 word("是", 1),
26003 word("什", 1),
26004 word("么", 1),
26005 whitespace(" ", 1),
26006 newline(),
26007 whitespace(" ", 1),
26008 word("钢", 1),
26009 word("笔", 1),
26010 ],
26011 ),
26012 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
26013 ];
26014
26015 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26016 WordBreakToken::Word {
26017 token,
26018 grapheme_len,
26019 }
26020 }
26021
26022 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26023 WordBreakToken::InlineWhitespace {
26024 token,
26025 grapheme_len,
26026 }
26027 }
26028
26029 fn newline() -> WordBreakToken<'static> {
26030 WordBreakToken::Newline
26031 }
26032
26033 for (input, result) in tests {
26034 assert_eq!(
26035 WordBreakingTokenizer::new(input)
26036 .collect::<Vec<_>>()
26037 .as_slice(),
26038 *result,
26039 );
26040 }
26041}
26042
26043fn wrap_with_prefix(
26044 first_line_prefix: String,
26045 subsequent_lines_prefix: String,
26046 unwrapped_text: String,
26047 wrap_column: usize,
26048 tab_size: NonZeroU32,
26049 preserve_existing_whitespace: bool,
26050) -> String {
26051 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
26052 let subsequent_lines_prefix_len =
26053 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
26054 let mut wrapped_text = String::new();
26055 let mut current_line = first_line_prefix;
26056 let mut is_first_line = true;
26057
26058 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
26059 let mut current_line_len = first_line_prefix_len;
26060 let mut in_whitespace = false;
26061 for token in tokenizer {
26062 let have_preceding_whitespace = in_whitespace;
26063 match token {
26064 WordBreakToken::Word {
26065 token,
26066 grapheme_len,
26067 } => {
26068 in_whitespace = false;
26069 let current_prefix_len = if is_first_line {
26070 first_line_prefix_len
26071 } else {
26072 subsequent_lines_prefix_len
26073 };
26074 if current_line_len + grapheme_len > wrap_column
26075 && current_line_len != current_prefix_len
26076 {
26077 wrapped_text.push_str(current_line.trim_end());
26078 wrapped_text.push('\n');
26079 is_first_line = false;
26080 current_line = subsequent_lines_prefix.clone();
26081 current_line_len = subsequent_lines_prefix_len;
26082 }
26083 current_line.push_str(token);
26084 current_line_len += grapheme_len;
26085 }
26086 WordBreakToken::InlineWhitespace {
26087 mut token,
26088 mut grapheme_len,
26089 } => {
26090 in_whitespace = true;
26091 if have_preceding_whitespace && !preserve_existing_whitespace {
26092 continue;
26093 }
26094 if !preserve_existing_whitespace {
26095 // Keep a single whitespace grapheme as-is
26096 if let Some(first) =
26097 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
26098 {
26099 token = first;
26100 } else {
26101 token = " ";
26102 }
26103 grapheme_len = 1;
26104 }
26105 let current_prefix_len = if is_first_line {
26106 first_line_prefix_len
26107 } else {
26108 subsequent_lines_prefix_len
26109 };
26110 if current_line_len + grapheme_len > wrap_column {
26111 wrapped_text.push_str(current_line.trim_end());
26112 wrapped_text.push('\n');
26113 is_first_line = false;
26114 current_line = subsequent_lines_prefix.clone();
26115 current_line_len = subsequent_lines_prefix_len;
26116 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
26117 current_line.push_str(token);
26118 current_line_len += grapheme_len;
26119 }
26120 }
26121 WordBreakToken::Newline => {
26122 in_whitespace = true;
26123 let current_prefix_len = if is_first_line {
26124 first_line_prefix_len
26125 } else {
26126 subsequent_lines_prefix_len
26127 };
26128 if preserve_existing_whitespace {
26129 wrapped_text.push_str(current_line.trim_end());
26130 wrapped_text.push('\n');
26131 is_first_line = false;
26132 current_line = subsequent_lines_prefix.clone();
26133 current_line_len = subsequent_lines_prefix_len;
26134 } else if have_preceding_whitespace {
26135 continue;
26136 } else if current_line_len + 1 > wrap_column
26137 && current_line_len != current_prefix_len
26138 {
26139 wrapped_text.push_str(current_line.trim_end());
26140 wrapped_text.push('\n');
26141 is_first_line = false;
26142 current_line = subsequent_lines_prefix.clone();
26143 current_line_len = subsequent_lines_prefix_len;
26144 } else if current_line_len != current_prefix_len {
26145 current_line.push(' ');
26146 current_line_len += 1;
26147 }
26148 }
26149 }
26150 }
26151
26152 if !current_line.is_empty() {
26153 wrapped_text.push_str(¤t_line);
26154 }
26155 wrapped_text
26156}
26157
26158#[test]
26159fn test_wrap_with_prefix() {
26160 assert_eq!(
26161 wrap_with_prefix(
26162 "# ".to_string(),
26163 "# ".to_string(),
26164 "abcdefg".to_string(),
26165 4,
26166 NonZeroU32::new(4).unwrap(),
26167 false,
26168 ),
26169 "# abcdefg"
26170 );
26171 assert_eq!(
26172 wrap_with_prefix(
26173 "".to_string(),
26174 "".to_string(),
26175 "\thello world".to_string(),
26176 8,
26177 NonZeroU32::new(4).unwrap(),
26178 false,
26179 ),
26180 "hello\nworld"
26181 );
26182 assert_eq!(
26183 wrap_with_prefix(
26184 "// ".to_string(),
26185 "// ".to_string(),
26186 "xx \nyy zz aa bb cc".to_string(),
26187 12,
26188 NonZeroU32::new(4).unwrap(),
26189 false,
26190 ),
26191 "// xx yy zz\n// aa bb cc"
26192 );
26193 assert_eq!(
26194 wrap_with_prefix(
26195 String::new(),
26196 String::new(),
26197 "这是什么 \n 钢笔".to_string(),
26198 3,
26199 NonZeroU32::new(4).unwrap(),
26200 false,
26201 ),
26202 "这是什\n么 钢\n笔"
26203 );
26204 assert_eq!(
26205 wrap_with_prefix(
26206 String::new(),
26207 String::new(),
26208 format!("foo{}bar", '\u{2009}'), // thin space
26209 80,
26210 NonZeroU32::new(4).unwrap(),
26211 false,
26212 ),
26213 format!("foo{}bar", '\u{2009}')
26214 );
26215}
26216
26217pub trait CollaborationHub {
26218 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
26219 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
26220 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
26221}
26222
26223impl CollaborationHub for Entity<Project> {
26224 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
26225 self.read(cx).collaborators()
26226 }
26227
26228 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
26229 self.read(cx).user_store().read(cx).participant_indices()
26230 }
26231
26232 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
26233 let this = self.read(cx);
26234 let user_ids = this.collaborators().values().map(|c| c.user_id);
26235 this.user_store().read(cx).participant_names(user_ids, cx)
26236 }
26237}
26238
26239pub trait SemanticsProvider {
26240 fn hover(
26241 &self,
26242 buffer: &Entity<Buffer>,
26243 position: text::Anchor,
26244 cx: &mut App,
26245 ) -> Option<Task<Option<Vec<project::Hover>>>>;
26246
26247 fn inline_values(
26248 &self,
26249 buffer_handle: Entity<Buffer>,
26250 range: Range<text::Anchor>,
26251 cx: &mut App,
26252 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
26253
26254 fn applicable_inlay_chunks(
26255 &self,
26256 buffer: &Entity<Buffer>,
26257 ranges: &[Range<text::Anchor>],
26258 cx: &mut App,
26259 ) -> Vec<Range<BufferRow>>;
26260
26261 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
26262
26263 fn inlay_hints(
26264 &self,
26265 invalidate: InvalidationStrategy,
26266 buffer: Entity<Buffer>,
26267 ranges: Vec<Range<text::Anchor>>,
26268 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
26269 cx: &mut App,
26270 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
26271
26272 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
26273
26274 fn document_highlights(
26275 &self,
26276 buffer: &Entity<Buffer>,
26277 position: text::Anchor,
26278 cx: &mut App,
26279 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
26280
26281 fn definitions(
26282 &self,
26283 buffer: &Entity<Buffer>,
26284 position: text::Anchor,
26285 kind: GotoDefinitionKind,
26286 cx: &mut App,
26287 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
26288
26289 fn range_for_rename(
26290 &self,
26291 buffer: &Entity<Buffer>,
26292 position: text::Anchor,
26293 cx: &mut App,
26294 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
26295
26296 fn perform_rename(
26297 &self,
26298 buffer: &Entity<Buffer>,
26299 position: text::Anchor,
26300 new_name: String,
26301 cx: &mut App,
26302 ) -> Option<Task<Result<ProjectTransaction>>>;
26303}
26304
26305pub trait CompletionProvider {
26306 fn completions(
26307 &self,
26308 excerpt_id: ExcerptId,
26309 buffer: &Entity<Buffer>,
26310 buffer_position: text::Anchor,
26311 trigger: CompletionContext,
26312 window: &mut Window,
26313 cx: &mut Context<Editor>,
26314 ) -> Task<Result<Vec<CompletionResponse>>>;
26315
26316 fn resolve_completions(
26317 &self,
26318 _buffer: Entity<Buffer>,
26319 _completion_indices: Vec<usize>,
26320 _completions: Rc<RefCell<Box<[Completion]>>>,
26321 _cx: &mut Context<Editor>,
26322 ) -> Task<Result<bool>> {
26323 Task::ready(Ok(false))
26324 }
26325
26326 fn apply_additional_edits_for_completion(
26327 &self,
26328 _buffer: Entity<Buffer>,
26329 _completions: Rc<RefCell<Box<[Completion]>>>,
26330 _completion_index: usize,
26331 _push_to_history: bool,
26332 _cx: &mut Context<Editor>,
26333 ) -> Task<Result<Option<language::Transaction>>> {
26334 Task::ready(Ok(None))
26335 }
26336
26337 fn is_completion_trigger(
26338 &self,
26339 buffer: &Entity<Buffer>,
26340 position: language::Anchor,
26341 text: &str,
26342 trigger_in_words: bool,
26343 cx: &mut Context<Editor>,
26344 ) -> bool;
26345
26346 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
26347
26348 fn sort_completions(&self) -> bool {
26349 true
26350 }
26351
26352 fn filter_completions(&self) -> bool {
26353 true
26354 }
26355
26356 fn show_snippets(&self) -> bool {
26357 false
26358 }
26359}
26360
26361pub trait CodeActionProvider {
26362 fn id(&self) -> Arc<str>;
26363
26364 fn code_actions(
26365 &self,
26366 buffer: &Entity<Buffer>,
26367 range: Range<text::Anchor>,
26368 window: &mut Window,
26369 cx: &mut App,
26370 ) -> Task<Result<Vec<CodeAction>>>;
26371
26372 fn apply_code_action(
26373 &self,
26374 buffer_handle: Entity<Buffer>,
26375 action: CodeAction,
26376 excerpt_id: ExcerptId,
26377 push_to_history: bool,
26378 window: &mut Window,
26379 cx: &mut App,
26380 ) -> Task<Result<ProjectTransaction>>;
26381}
26382
26383impl CodeActionProvider for Entity<Project> {
26384 fn id(&self) -> Arc<str> {
26385 "project".into()
26386 }
26387
26388 fn code_actions(
26389 &self,
26390 buffer: &Entity<Buffer>,
26391 range: Range<text::Anchor>,
26392 _window: &mut Window,
26393 cx: &mut App,
26394 ) -> Task<Result<Vec<CodeAction>>> {
26395 self.update(cx, |project, cx| {
26396 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
26397 let code_actions = project.code_actions(buffer, range, None, cx);
26398 cx.background_spawn(async move {
26399 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
26400 Ok(code_lens_actions
26401 .context("code lens fetch")?
26402 .into_iter()
26403 .flatten()
26404 .chain(
26405 code_actions
26406 .context("code action fetch")?
26407 .into_iter()
26408 .flatten(),
26409 )
26410 .collect())
26411 })
26412 })
26413 }
26414
26415 fn apply_code_action(
26416 &self,
26417 buffer_handle: Entity<Buffer>,
26418 action: CodeAction,
26419 _excerpt_id: ExcerptId,
26420 push_to_history: bool,
26421 _window: &mut Window,
26422 cx: &mut App,
26423 ) -> Task<Result<ProjectTransaction>> {
26424 self.update(cx, |project, cx| {
26425 project.apply_code_action(buffer_handle, action, push_to_history, cx)
26426 })
26427 }
26428}
26429
26430fn snippet_completions(
26431 project: &Project,
26432 buffer: &Entity<Buffer>,
26433 buffer_anchor: text::Anchor,
26434 classifier: CharClassifier,
26435 cx: &mut App,
26436) -> Task<Result<CompletionResponse>> {
26437 let languages = buffer.read(cx).languages_at(buffer_anchor);
26438 let snippet_store = project.snippets().read(cx);
26439
26440 let scopes: Vec<_> = languages
26441 .iter()
26442 .filter_map(|language| {
26443 let language_name = language.lsp_id();
26444 let snippets = snippet_store.snippets_for(Some(language_name), cx);
26445
26446 if snippets.is_empty() {
26447 None
26448 } else {
26449 Some((language.default_scope(), snippets))
26450 }
26451 })
26452 .collect();
26453
26454 if scopes.is_empty() {
26455 return Task::ready(Ok(CompletionResponse {
26456 completions: vec![],
26457 display_options: CompletionDisplayOptions::default(),
26458 is_incomplete: false,
26459 }));
26460 }
26461
26462 let snapshot = buffer.read(cx).text_snapshot();
26463 let executor = cx.background_executor().clone();
26464
26465 cx.background_spawn(async move {
26466 let is_word_char = |c| classifier.is_word(c);
26467
26468 let mut is_incomplete = false;
26469 let mut completions: Vec<Completion> = Vec::new();
26470
26471 const MAX_PREFIX_LEN: usize = 128;
26472 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
26473 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
26474 let window_start = snapshot.clip_offset(window_start, Bias::Left);
26475
26476 let max_buffer_window: String = snapshot
26477 .text_for_range(window_start..buffer_offset)
26478 .collect();
26479
26480 if max_buffer_window.is_empty() {
26481 return Ok(CompletionResponse {
26482 completions: vec![],
26483 display_options: CompletionDisplayOptions::default(),
26484 is_incomplete: true,
26485 });
26486 }
26487
26488 for (_scope, snippets) in scopes.into_iter() {
26489 // Sort snippets by word count to match longer snippet prefixes first.
26490 let mut sorted_snippet_candidates = snippets
26491 .iter()
26492 .enumerate()
26493 .flat_map(|(snippet_ix, snippet)| {
26494 snippet
26495 .prefix
26496 .iter()
26497 .enumerate()
26498 .map(move |(prefix_ix, prefix)| {
26499 let word_count =
26500 snippet_candidate_suffixes(prefix, is_word_char).count();
26501 ((snippet_ix, prefix_ix), prefix, word_count)
26502 })
26503 })
26504 .collect_vec();
26505 sorted_snippet_candidates
26506 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
26507
26508 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
26509
26510 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
26511 .take(
26512 sorted_snippet_candidates
26513 .first()
26514 .map(|(_, _, word_count)| *word_count)
26515 .unwrap_or_default(),
26516 )
26517 .collect_vec();
26518
26519 const MAX_RESULTS: usize = 100;
26520 // Each match also remembers how many characters from the buffer it consumed
26521 let mut matches: Vec<(StringMatch, usize)> = vec![];
26522
26523 let mut snippet_list_cutoff_index = 0;
26524 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
26525 let word_count = buffer_index + 1;
26526 // Increase `snippet_list_cutoff_index` until we have all of the
26527 // snippets with sufficiently many words.
26528 while sorted_snippet_candidates
26529 .get(snippet_list_cutoff_index)
26530 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
26531 *snippet_word_count >= word_count
26532 })
26533 {
26534 snippet_list_cutoff_index += 1;
26535 }
26536
26537 // Take only the candidates with at least `word_count` many words
26538 let snippet_candidates_at_word_len =
26539 &sorted_snippet_candidates[..snippet_list_cutoff_index];
26540
26541 let candidates = snippet_candidates_at_word_len
26542 .iter()
26543 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
26544 .enumerate() // index in `sorted_snippet_candidates`
26545 // First char must match
26546 .filter(|(_ix, prefix)| {
26547 itertools::equal(
26548 prefix
26549 .chars()
26550 .next()
26551 .into_iter()
26552 .flat_map(|c| c.to_lowercase()),
26553 buffer_window
26554 .chars()
26555 .next()
26556 .into_iter()
26557 .flat_map(|c| c.to_lowercase()),
26558 )
26559 })
26560 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
26561 .collect::<Vec<StringMatchCandidate>>();
26562
26563 matches.extend(
26564 fuzzy::match_strings(
26565 &candidates,
26566 &buffer_window,
26567 buffer_window.chars().any(|c| c.is_uppercase()),
26568 true,
26569 MAX_RESULTS - matches.len(), // always prioritize longer snippets
26570 &Default::default(),
26571 executor.clone(),
26572 )
26573 .await
26574 .into_iter()
26575 .map(|string_match| (string_match, buffer_window.len())),
26576 );
26577
26578 if matches.len() >= MAX_RESULTS {
26579 break;
26580 }
26581 }
26582
26583 let to_lsp = |point: &text::Anchor| {
26584 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
26585 point_to_lsp(end)
26586 };
26587 let lsp_end = to_lsp(&buffer_anchor);
26588
26589 if matches.len() >= MAX_RESULTS {
26590 is_incomplete = true;
26591 }
26592
26593 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
26594 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
26595 sorted_snippet_candidates[string_match.candidate_id];
26596 let snippet = &snippets[snippet_index];
26597 let start = buffer_offset - buffer_window_len;
26598 let start = snapshot.anchor_before(start);
26599 let range = start..buffer_anchor;
26600 let lsp_start = to_lsp(&start);
26601 let lsp_range = lsp::Range {
26602 start: lsp_start,
26603 end: lsp_end,
26604 };
26605 Completion {
26606 replace_range: range,
26607 new_text: snippet.body.clone(),
26608 source: CompletionSource::Lsp {
26609 insert_range: None,
26610 server_id: LanguageServerId(usize::MAX),
26611 resolved: true,
26612 lsp_completion: Box::new(lsp::CompletionItem {
26613 label: snippet.prefix.first().unwrap().clone(),
26614 kind: Some(CompletionItemKind::SNIPPET),
26615 label_details: snippet.description.as_ref().map(|description| {
26616 lsp::CompletionItemLabelDetails {
26617 detail: Some(description.clone()),
26618 description: None,
26619 }
26620 }),
26621 insert_text_format: Some(InsertTextFormat::SNIPPET),
26622 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
26623 lsp::InsertReplaceEdit {
26624 new_text: snippet.body.clone(),
26625 insert: lsp_range,
26626 replace: lsp_range,
26627 },
26628 )),
26629 filter_text: Some(snippet.body.clone()),
26630 sort_text: Some(char::MAX.to_string()),
26631 ..lsp::CompletionItem::default()
26632 }),
26633 lsp_defaults: None,
26634 },
26635 label: CodeLabel {
26636 text: matching_prefix.clone(),
26637 runs: Vec::new(),
26638 filter_range: 0..matching_prefix.len(),
26639 },
26640 icon_path: None,
26641 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
26642 single_line: snippet.name.clone().into(),
26643 plain_text: snippet
26644 .description
26645 .clone()
26646 .map(|description| description.into()),
26647 }),
26648 insert_text_mode: None,
26649 confirm: None,
26650 match_start: Some(start),
26651 snippet_deduplication_key: Some((snippet_index, prefix_index)),
26652 }
26653 }));
26654 }
26655
26656 Ok(CompletionResponse {
26657 completions,
26658 display_options: CompletionDisplayOptions::default(),
26659 is_incomplete,
26660 })
26661 })
26662}
26663
26664impl CompletionProvider for Entity<Project> {
26665 fn completions(
26666 &self,
26667 _excerpt_id: ExcerptId,
26668 buffer: &Entity<Buffer>,
26669 buffer_position: text::Anchor,
26670 options: CompletionContext,
26671 _window: &mut Window,
26672 cx: &mut Context<Editor>,
26673 ) -> Task<Result<Vec<CompletionResponse>>> {
26674 self.update(cx, |project, cx| {
26675 let task = project.completions(buffer, buffer_position, options, cx);
26676 cx.background_spawn(task)
26677 })
26678 }
26679
26680 fn resolve_completions(
26681 &self,
26682 buffer: Entity<Buffer>,
26683 completion_indices: Vec<usize>,
26684 completions: Rc<RefCell<Box<[Completion]>>>,
26685 cx: &mut Context<Editor>,
26686 ) -> Task<Result<bool>> {
26687 self.update(cx, |project, cx| {
26688 project.lsp_store().update(cx, |lsp_store, cx| {
26689 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
26690 })
26691 })
26692 }
26693
26694 fn apply_additional_edits_for_completion(
26695 &self,
26696 buffer: Entity<Buffer>,
26697 completions: Rc<RefCell<Box<[Completion]>>>,
26698 completion_index: usize,
26699 push_to_history: bool,
26700 cx: &mut Context<Editor>,
26701 ) -> Task<Result<Option<language::Transaction>>> {
26702 self.update(cx, |project, cx| {
26703 project.lsp_store().update(cx, |lsp_store, cx| {
26704 lsp_store.apply_additional_edits_for_completion(
26705 buffer,
26706 completions,
26707 completion_index,
26708 push_to_history,
26709 cx,
26710 )
26711 })
26712 })
26713 }
26714
26715 fn is_completion_trigger(
26716 &self,
26717 buffer: &Entity<Buffer>,
26718 position: language::Anchor,
26719 text: &str,
26720 trigger_in_words: bool,
26721 cx: &mut Context<Editor>,
26722 ) -> bool {
26723 let mut chars = text.chars();
26724 let char = if let Some(char) = chars.next() {
26725 char
26726 } else {
26727 return false;
26728 };
26729 if chars.next().is_some() {
26730 return false;
26731 }
26732
26733 let buffer = buffer.read(cx);
26734 let snapshot = buffer.snapshot();
26735 let classifier = snapshot
26736 .char_classifier_at(position)
26737 .scope_context(Some(CharScopeContext::Completion));
26738 if trigger_in_words && classifier.is_word(char) {
26739 return true;
26740 }
26741
26742 buffer.completion_triggers().contains(text)
26743 }
26744
26745 fn show_snippets(&self) -> bool {
26746 true
26747 }
26748}
26749
26750impl SemanticsProvider for Entity<Project> {
26751 fn hover(
26752 &self,
26753 buffer: &Entity<Buffer>,
26754 position: text::Anchor,
26755 cx: &mut App,
26756 ) -> Option<Task<Option<Vec<project::Hover>>>> {
26757 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
26758 }
26759
26760 fn document_highlights(
26761 &self,
26762 buffer: &Entity<Buffer>,
26763 position: text::Anchor,
26764 cx: &mut App,
26765 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
26766 Some(self.update(cx, |project, cx| {
26767 project.document_highlights(buffer, position, cx)
26768 }))
26769 }
26770
26771 fn definitions(
26772 &self,
26773 buffer: &Entity<Buffer>,
26774 position: text::Anchor,
26775 kind: GotoDefinitionKind,
26776 cx: &mut App,
26777 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
26778 Some(self.update(cx, |project, cx| match kind {
26779 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
26780 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
26781 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
26782 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
26783 }))
26784 }
26785
26786 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
26787 self.update(cx, |project, cx| {
26788 if project
26789 .active_debug_session(cx)
26790 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
26791 {
26792 return true;
26793 }
26794
26795 buffer.update(cx, |buffer, cx| {
26796 project.any_language_server_supports_inlay_hints(buffer, cx)
26797 })
26798 })
26799 }
26800
26801 fn inline_values(
26802 &self,
26803 buffer_handle: Entity<Buffer>,
26804 range: Range<text::Anchor>,
26805 cx: &mut App,
26806 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
26807 self.update(cx, |project, cx| {
26808 let (session, active_stack_frame) = project.active_debug_session(cx)?;
26809
26810 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
26811 })
26812 }
26813
26814 fn applicable_inlay_chunks(
26815 &self,
26816 buffer: &Entity<Buffer>,
26817 ranges: &[Range<text::Anchor>],
26818 cx: &mut App,
26819 ) -> Vec<Range<BufferRow>> {
26820 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
26821 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
26822 })
26823 }
26824
26825 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
26826 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
26827 lsp_store.invalidate_inlay_hints(for_buffers)
26828 });
26829 }
26830
26831 fn inlay_hints(
26832 &self,
26833 invalidate: InvalidationStrategy,
26834 buffer: Entity<Buffer>,
26835 ranges: Vec<Range<text::Anchor>>,
26836 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
26837 cx: &mut App,
26838 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
26839 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
26840 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
26841 }))
26842 }
26843
26844 fn range_for_rename(
26845 &self,
26846 buffer: &Entity<Buffer>,
26847 position: text::Anchor,
26848 cx: &mut App,
26849 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
26850 Some(self.update(cx, |project, cx| {
26851 let buffer = buffer.clone();
26852 let task = project.prepare_rename(buffer.clone(), position, cx);
26853 cx.spawn(async move |_, cx| {
26854 Ok(match task.await? {
26855 PrepareRenameResponse::Success(range) => Some(range),
26856 PrepareRenameResponse::InvalidPosition => None,
26857 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
26858 // Fallback on using TreeSitter info to determine identifier range
26859 buffer.read_with(cx, |buffer, _| {
26860 let snapshot = buffer.snapshot();
26861 let (range, kind) = snapshot.surrounding_word(position, None);
26862 if kind != Some(CharKind::Word) {
26863 return None;
26864 }
26865 Some(
26866 snapshot.anchor_before(range.start)
26867 ..snapshot.anchor_after(range.end),
26868 )
26869 })
26870 }
26871 })
26872 })
26873 }))
26874 }
26875
26876 fn perform_rename(
26877 &self,
26878 buffer: &Entity<Buffer>,
26879 position: text::Anchor,
26880 new_name: String,
26881 cx: &mut App,
26882 ) -> Option<Task<Result<ProjectTransaction>>> {
26883 Some(self.update(cx, |project, cx| {
26884 project.perform_rename(buffer.clone(), position, new_name, cx)
26885 }))
26886 }
26887}
26888
26889fn consume_contiguous_rows(
26890 contiguous_row_selections: &mut Vec<Selection<Point>>,
26891 selection: &Selection<Point>,
26892 display_map: &DisplaySnapshot,
26893 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
26894) -> (MultiBufferRow, MultiBufferRow) {
26895 contiguous_row_selections.push(selection.clone());
26896 let start_row = starting_row(selection, display_map);
26897 let mut end_row = ending_row(selection, display_map);
26898
26899 while let Some(next_selection) = selections.peek() {
26900 if next_selection.start.row <= end_row.0 {
26901 end_row = ending_row(next_selection, display_map);
26902 contiguous_row_selections.push(selections.next().unwrap().clone());
26903 } else {
26904 break;
26905 }
26906 }
26907 (start_row, end_row)
26908}
26909
26910fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
26911 if selection.start.column > 0 {
26912 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
26913 } else {
26914 MultiBufferRow(selection.start.row)
26915 }
26916}
26917
26918fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
26919 if next_selection.end.column > 0 || next_selection.is_empty() {
26920 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
26921 } else {
26922 MultiBufferRow(next_selection.end.row)
26923 }
26924}
26925
26926impl EditorSnapshot {
26927 pub fn remote_selections_in_range<'a>(
26928 &'a self,
26929 range: &'a Range<Anchor>,
26930 collaboration_hub: &dyn CollaborationHub,
26931 cx: &'a App,
26932 ) -> impl 'a + Iterator<Item = RemoteSelection> {
26933 let participant_names = collaboration_hub.user_names(cx);
26934 let participant_indices = collaboration_hub.user_participant_indices(cx);
26935 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
26936 let collaborators_by_replica_id = collaborators_by_peer_id
26937 .values()
26938 .map(|collaborator| (collaborator.replica_id, collaborator))
26939 .collect::<HashMap<_, _>>();
26940 self.buffer_snapshot()
26941 .selections_in_range(range, false)
26942 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
26943 if replica_id == ReplicaId::AGENT {
26944 Some(RemoteSelection {
26945 replica_id,
26946 selection,
26947 cursor_shape,
26948 line_mode,
26949 collaborator_id: CollaboratorId::Agent,
26950 user_name: Some("Agent".into()),
26951 color: cx.theme().players().agent(),
26952 })
26953 } else {
26954 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
26955 let participant_index = participant_indices.get(&collaborator.user_id).copied();
26956 let user_name = participant_names.get(&collaborator.user_id).cloned();
26957 Some(RemoteSelection {
26958 replica_id,
26959 selection,
26960 cursor_shape,
26961 line_mode,
26962 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
26963 user_name,
26964 color: if let Some(index) = participant_index {
26965 cx.theme().players().color_for_participant(index.0)
26966 } else {
26967 cx.theme().players().absent()
26968 },
26969 })
26970 }
26971 })
26972 }
26973
26974 pub fn hunks_for_ranges(
26975 &self,
26976 ranges: impl IntoIterator<Item = Range<Point>>,
26977 ) -> Vec<MultiBufferDiffHunk> {
26978 let mut hunks = Vec::new();
26979 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
26980 HashMap::default();
26981 for query_range in ranges {
26982 let query_rows =
26983 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
26984 for hunk in self.buffer_snapshot().diff_hunks_in_range(
26985 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
26986 ) {
26987 // Include deleted hunks that are adjacent to the query range, because
26988 // otherwise they would be missed.
26989 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
26990 if hunk.status().is_deleted() {
26991 intersects_range |= hunk.row_range.start == query_rows.end;
26992 intersects_range |= hunk.row_range.end == query_rows.start;
26993 }
26994 if intersects_range {
26995 if !processed_buffer_rows
26996 .entry(hunk.buffer_id)
26997 .or_default()
26998 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
26999 {
27000 continue;
27001 }
27002 hunks.push(hunk);
27003 }
27004 }
27005 }
27006
27007 hunks
27008 }
27009
27010 fn display_diff_hunks_for_rows<'a>(
27011 &'a self,
27012 display_rows: Range<DisplayRow>,
27013 folded_buffers: &'a HashSet<BufferId>,
27014 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
27015 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
27016 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
27017
27018 self.buffer_snapshot()
27019 .diff_hunks_in_range(buffer_start..buffer_end)
27020 .filter_map(|hunk| {
27021 if folded_buffers.contains(&hunk.buffer_id) {
27022 return None;
27023 }
27024
27025 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
27026 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
27027
27028 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
27029 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
27030
27031 let display_hunk = if hunk_display_start.column() != 0 {
27032 DisplayDiffHunk::Folded {
27033 display_row: hunk_display_start.row(),
27034 }
27035 } else {
27036 let mut end_row = hunk_display_end.row();
27037 if hunk_display_end.column() > 0 {
27038 end_row.0 += 1;
27039 }
27040 let is_created_file = hunk.is_created_file();
27041
27042 DisplayDiffHunk::Unfolded {
27043 status: hunk.status(),
27044 diff_base_byte_range: hunk.diff_base_byte_range.start.0
27045 ..hunk.diff_base_byte_range.end.0,
27046 word_diffs: hunk.word_diffs,
27047 display_row_range: hunk_display_start.row()..end_row,
27048 multi_buffer_range: Anchor::range_in_buffer(
27049 hunk.excerpt_id,
27050 hunk.buffer_range,
27051 ),
27052 is_created_file,
27053 }
27054 };
27055
27056 Some(display_hunk)
27057 })
27058 }
27059
27060 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
27061 self.display_snapshot
27062 .buffer_snapshot()
27063 .language_at(position)
27064 }
27065
27066 pub fn is_focused(&self) -> bool {
27067 self.is_focused
27068 }
27069
27070 pub fn placeholder_text(&self) -> Option<String> {
27071 self.placeholder_display_snapshot
27072 .as_ref()
27073 .map(|display_map| display_map.text())
27074 }
27075
27076 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
27077 self.scroll_anchor.scroll_position(&self.display_snapshot)
27078 }
27079
27080 pub fn gutter_dimensions(
27081 &self,
27082 font_id: FontId,
27083 font_size: Pixels,
27084 style: &EditorStyle,
27085 window: &mut Window,
27086 cx: &App,
27087 ) -> GutterDimensions {
27088 if self.show_gutter
27089 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
27090 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
27091 {
27092 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
27093 matches!(
27094 ProjectSettings::get_global(cx).git.git_gutter,
27095 GitGutterSetting::TrackedFiles
27096 )
27097 });
27098 let gutter_settings = EditorSettings::get_global(cx).gutter;
27099 let show_line_numbers = self
27100 .show_line_numbers
27101 .unwrap_or(gutter_settings.line_numbers);
27102 let line_gutter_width = if show_line_numbers {
27103 // Avoid flicker-like gutter resizes when the line number gains another digit by
27104 // only resizing the gutter on files with > 10**min_line_number_digits lines.
27105 let min_width_for_number_on_gutter =
27106 ch_advance * gutter_settings.min_line_number_digits as f32;
27107 self.max_line_number_width(style, window)
27108 .max(min_width_for_number_on_gutter)
27109 } else {
27110 0.0.into()
27111 };
27112
27113 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
27114 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
27115
27116 let git_blame_entries_width =
27117 self.git_blame_gutter_max_author_length
27118 .map(|max_author_length| {
27119 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
27120 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
27121
27122 /// The number of characters to dedicate to gaps and margins.
27123 const SPACING_WIDTH: usize = 4;
27124
27125 let max_char_count = max_author_length.min(renderer.max_author_length())
27126 + ::git::SHORT_SHA_LENGTH
27127 + MAX_RELATIVE_TIMESTAMP.len()
27128 + SPACING_WIDTH;
27129
27130 ch_advance * max_char_count
27131 });
27132
27133 let is_singleton = self.buffer_snapshot().is_singleton();
27134
27135 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
27136 left_padding += if !is_singleton {
27137 ch_width * 4.0
27138 } else if show_runnables || show_breakpoints {
27139 ch_width * 3.0
27140 } else if show_git_gutter && show_line_numbers {
27141 ch_width * 2.0
27142 } else if show_git_gutter || show_line_numbers {
27143 ch_width
27144 } else {
27145 px(0.)
27146 };
27147
27148 let shows_folds = is_singleton && gutter_settings.folds;
27149
27150 let right_padding = if shows_folds && show_line_numbers {
27151 ch_width * 4.0
27152 } else if shows_folds || (!is_singleton && show_line_numbers) {
27153 ch_width * 3.0
27154 } else if show_line_numbers {
27155 ch_width
27156 } else {
27157 px(0.)
27158 };
27159
27160 GutterDimensions {
27161 left_padding,
27162 right_padding,
27163 width: line_gutter_width + left_padding + right_padding,
27164 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
27165 git_blame_entries_width,
27166 }
27167 } else if self.offset_content {
27168 GutterDimensions::default_with_margin(font_id, font_size, cx)
27169 } else {
27170 GutterDimensions::default()
27171 }
27172 }
27173
27174 pub fn render_crease_toggle(
27175 &self,
27176 buffer_row: MultiBufferRow,
27177 row_contains_cursor: bool,
27178 editor: Entity<Editor>,
27179 window: &mut Window,
27180 cx: &mut App,
27181 ) -> Option<AnyElement> {
27182 let folded = self.is_line_folded(buffer_row);
27183 let mut is_foldable = false;
27184
27185 if let Some(crease) = self
27186 .crease_snapshot
27187 .query_row(buffer_row, self.buffer_snapshot())
27188 {
27189 is_foldable = true;
27190 match crease {
27191 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
27192 if let Some(render_toggle) = render_toggle {
27193 let toggle_callback =
27194 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
27195 if folded {
27196 editor.update(cx, |editor, cx| {
27197 editor.fold_at(buffer_row, window, cx)
27198 });
27199 } else {
27200 editor.update(cx, |editor, cx| {
27201 editor.unfold_at(buffer_row, window, cx)
27202 });
27203 }
27204 });
27205 return Some((render_toggle)(
27206 buffer_row,
27207 folded,
27208 toggle_callback,
27209 window,
27210 cx,
27211 ));
27212 }
27213 }
27214 }
27215 }
27216
27217 is_foldable |= self.starts_indent(buffer_row);
27218
27219 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
27220 Some(
27221 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
27222 .toggle_state(folded)
27223 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
27224 if folded {
27225 this.unfold_at(buffer_row, window, cx);
27226 } else {
27227 this.fold_at(buffer_row, window, cx);
27228 }
27229 }))
27230 .into_any_element(),
27231 )
27232 } else {
27233 None
27234 }
27235 }
27236
27237 pub fn render_crease_trailer(
27238 &self,
27239 buffer_row: MultiBufferRow,
27240 window: &mut Window,
27241 cx: &mut App,
27242 ) -> Option<AnyElement> {
27243 let folded = self.is_line_folded(buffer_row);
27244 if let Crease::Inline { render_trailer, .. } = self
27245 .crease_snapshot
27246 .query_row(buffer_row, self.buffer_snapshot())?
27247 {
27248 let render_trailer = render_trailer.as_ref()?;
27249 Some(render_trailer(buffer_row, folded, window, cx))
27250 } else {
27251 None
27252 }
27253 }
27254
27255 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
27256 let digit_count = self.widest_line_number().ilog10() + 1;
27257 column_pixels(style, digit_count as usize, window)
27258 }
27259
27260 /// Returns the line delta from `base` to `line` in the multibuffer, ignoring wrapped lines.
27261 ///
27262 /// This is positive if `base` is before `line`.
27263 fn relative_line_delta(
27264 &self,
27265 current_selection_head: DisplayRow,
27266 first_visible_row: DisplayRow,
27267 consider_wrapped_lines: bool,
27268 ) -> i64 {
27269 let current_selection_head = current_selection_head.as_display_point().to_point(self);
27270 let first_visible_row = first_visible_row.as_display_point().to_point(self);
27271
27272 if consider_wrapped_lines {
27273 let wrap_snapshot = self.wrap_snapshot();
27274 let base_wrap_row = wrap_snapshot
27275 .make_wrap_point(current_selection_head, Bias::Left)
27276 .row();
27277 let wrap_row = wrap_snapshot
27278 .make_wrap_point(first_visible_row, Bias::Left)
27279 .row();
27280
27281 wrap_row.0 as i64 - base_wrap_row.0 as i64
27282 } else {
27283 let fold_snapshot = self.fold_snapshot();
27284 let base_fold_row = fold_snapshot
27285 .to_fold_point(self.to_inlay_point(current_selection_head), Bias::Left)
27286 .row();
27287 let fold_row = fold_snapshot
27288 .to_fold_point(self.to_inlay_point(first_visible_row), Bias::Left)
27289 .row();
27290
27291 fold_row as i64 - base_fold_row as i64
27292 }
27293 }
27294
27295 /// Returns the unsigned relative line number to display for each row in `rows`.
27296 ///
27297 /// Wrapped rows are excluded from the hashmap if `count_relative_lines` is `false`.
27298 pub fn calculate_relative_line_numbers(
27299 &self,
27300 rows: &Range<DisplayRow>,
27301 current_selection_head: DisplayRow,
27302 count_wrapped_lines: bool,
27303 ) -> HashMap<DisplayRow, u32> {
27304 let initial_offset =
27305 self.relative_line_delta(current_selection_head, rows.start, count_wrapped_lines);
27306 let current_selection_point = current_selection_head.as_display_point().to_point(self);
27307
27308 self.row_infos(rows.start)
27309 .take(rows.len())
27310 .enumerate()
27311 .map(|(i, row_info)| (DisplayRow(rows.start.0 + i as u32), row_info))
27312 .filter(|(_row, row_info)| {
27313 row_info.buffer_row.is_some()
27314 || (count_wrapped_lines && row_info.wrapped_buffer_row.is_some())
27315 })
27316 .enumerate()
27317 .filter(|(_, (row, row_info))| {
27318 // We want to check here that
27319 // - the row is not the current selection head to ensure the current
27320 // line has absolute numbering
27321 // - similarly, should the selection head live in a soft-wrapped line
27322 // and we are not counting those, that the parent line keeps its
27323 // absolute number
27324 // - lastly, if we are in a deleted line, it is fine to number this
27325 // relative with 0, as otherwise it would have no line number at all
27326 (*row != current_selection_head
27327 && (count_wrapped_lines
27328 || row_info.buffer_row != Some(current_selection_point.row)))
27329 || row_info
27330 .diff_status
27331 .is_some_and(|status| status.is_deleted())
27332 })
27333 .map(|(i, (row, _))| (row, (initial_offset + i as i64).unsigned_abs() as u32))
27334 .collect()
27335 }
27336}
27337
27338pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
27339 let font_size = style.text.font_size.to_pixels(window.rem_size());
27340 let layout = window.text_system().shape_line(
27341 SharedString::from(" ".repeat(column)),
27342 font_size,
27343 &[TextRun {
27344 len: column,
27345 font: style.text.font(),
27346 color: Hsla::default(),
27347 ..Default::default()
27348 }],
27349 None,
27350 );
27351
27352 layout.width
27353}
27354
27355impl Deref for EditorSnapshot {
27356 type Target = DisplaySnapshot;
27357
27358 fn deref(&self) -> &Self::Target {
27359 &self.display_snapshot
27360 }
27361}
27362
27363#[derive(Clone, Debug, PartialEq, Eq)]
27364pub enum EditorEvent {
27365 /// Emitted when the stored review comments change (added, removed, or updated).
27366 ReviewCommentsChanged {
27367 /// The new total count of review comments.
27368 total_count: usize,
27369 },
27370 InputIgnored {
27371 text: Arc<str>,
27372 },
27373 InputHandled {
27374 utf16_range_to_replace: Option<Range<isize>>,
27375 text: Arc<str>,
27376 },
27377 ExcerptsAdded {
27378 buffer: Entity<Buffer>,
27379 predecessor: ExcerptId,
27380 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
27381 },
27382 ExcerptsRemoved {
27383 ids: Vec<ExcerptId>,
27384 removed_buffer_ids: Vec<BufferId>,
27385 },
27386 BufferFoldToggled {
27387 ids: Vec<ExcerptId>,
27388 folded: bool,
27389 },
27390 ExcerptsEdited {
27391 ids: Vec<ExcerptId>,
27392 },
27393 ExcerptsExpanded {
27394 ids: Vec<ExcerptId>,
27395 },
27396 ExpandExcerptsRequested {
27397 excerpt_ids: Vec<ExcerptId>,
27398 lines: u32,
27399 direction: ExpandExcerptDirection,
27400 },
27401 StageOrUnstageRequested {
27402 stage: bool,
27403 hunks: Vec<MultiBufferDiffHunk>,
27404 },
27405 RestoreRequested {
27406 hunks: Vec<MultiBufferDiffHunk>,
27407 },
27408 BufferEdited,
27409 Edited {
27410 transaction_id: clock::Lamport,
27411 },
27412 Reparsed(BufferId),
27413 Focused,
27414 FocusedIn,
27415 Blurred,
27416 DirtyChanged,
27417 Saved,
27418 TitleChanged,
27419 SelectionsChanged {
27420 local: bool,
27421 },
27422 ScrollPositionChanged {
27423 local: bool,
27424 autoscroll: bool,
27425 },
27426 TransactionUndone {
27427 transaction_id: clock::Lamport,
27428 },
27429 TransactionBegun {
27430 transaction_id: clock::Lamport,
27431 },
27432 CursorShapeChanged,
27433 BreadcrumbsChanged,
27434 PushedToNavHistory {
27435 anchor: Anchor,
27436 is_deactivate: bool,
27437 },
27438}
27439
27440impl EventEmitter<EditorEvent> for Editor {}
27441
27442impl Focusable for Editor {
27443 fn focus_handle(&self, _cx: &App) -> FocusHandle {
27444 self.focus_handle.clone()
27445 }
27446}
27447
27448impl Render for Editor {
27449 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
27450 EditorElement::new(&cx.entity(), self.create_style(cx))
27451 }
27452}
27453
27454impl EntityInputHandler for Editor {
27455 fn text_for_range(
27456 &mut self,
27457 range_utf16: Range<usize>,
27458 adjusted_range: &mut Option<Range<usize>>,
27459 _: &mut Window,
27460 cx: &mut Context<Self>,
27461 ) -> Option<String> {
27462 let snapshot = self.buffer.read(cx).read(cx);
27463 let start = snapshot.clip_offset_utf16(
27464 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
27465 Bias::Left,
27466 );
27467 let end = snapshot.clip_offset_utf16(
27468 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
27469 Bias::Right,
27470 );
27471 if (start.0.0..end.0.0) != range_utf16 {
27472 adjusted_range.replace(start.0.0..end.0.0);
27473 }
27474 Some(snapshot.text_for_range(start..end).collect())
27475 }
27476
27477 fn selected_text_range(
27478 &mut self,
27479 ignore_disabled_input: bool,
27480 _: &mut Window,
27481 cx: &mut Context<Self>,
27482 ) -> Option<UTF16Selection> {
27483 // Prevent the IME menu from appearing when holding down an alphabetic key
27484 // while input is disabled.
27485 if !ignore_disabled_input && !self.input_enabled {
27486 return None;
27487 }
27488
27489 let selection = self
27490 .selections
27491 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
27492 let range = selection.range();
27493
27494 Some(UTF16Selection {
27495 range: range.start.0.0..range.end.0.0,
27496 reversed: selection.reversed,
27497 })
27498 }
27499
27500 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
27501 let snapshot = self.buffer.read(cx).read(cx);
27502 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
27503 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
27504 }
27505
27506 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
27507 self.clear_highlights::<InputComposition>(cx);
27508 self.ime_transaction.take();
27509 }
27510
27511 fn replace_text_in_range(
27512 &mut self,
27513 range_utf16: Option<Range<usize>>,
27514 text: &str,
27515 window: &mut Window,
27516 cx: &mut Context<Self>,
27517 ) {
27518 if !self.input_enabled {
27519 cx.emit(EditorEvent::InputIgnored { text: text.into() });
27520 return;
27521 }
27522
27523 self.transact(window, cx, |this, window, cx| {
27524 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
27525 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
27526 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
27527 Some(this.selection_replacement_ranges(range_utf16, cx))
27528 } else {
27529 this.marked_text_ranges(cx)
27530 };
27531
27532 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
27533 let newest_selection_id = this.selections.newest_anchor().id;
27534 this.selections
27535 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
27536 .iter()
27537 .zip(ranges_to_replace.iter())
27538 .find_map(|(selection, range)| {
27539 if selection.id == newest_selection_id {
27540 Some(
27541 (range.start.0.0 as isize - selection.head().0.0 as isize)
27542 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
27543 )
27544 } else {
27545 None
27546 }
27547 })
27548 });
27549
27550 cx.emit(EditorEvent::InputHandled {
27551 utf16_range_to_replace: range_to_replace,
27552 text: text.into(),
27553 });
27554
27555 if let Some(new_selected_ranges) = new_selected_ranges {
27556 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
27557 selections.select_ranges(new_selected_ranges)
27558 });
27559 this.backspace(&Default::default(), window, cx);
27560 }
27561
27562 this.handle_input(text, window, cx);
27563 });
27564
27565 if let Some(transaction) = self.ime_transaction {
27566 self.buffer.update(cx, |buffer, cx| {
27567 buffer.group_until_transaction(transaction, cx);
27568 });
27569 }
27570
27571 self.unmark_text(window, cx);
27572 }
27573
27574 fn replace_and_mark_text_in_range(
27575 &mut self,
27576 range_utf16: Option<Range<usize>>,
27577 text: &str,
27578 new_selected_range_utf16: Option<Range<usize>>,
27579 window: &mut Window,
27580 cx: &mut Context<Self>,
27581 ) {
27582 if !self.input_enabled {
27583 return;
27584 }
27585
27586 let transaction = self.transact(window, cx, |this, window, cx| {
27587 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
27588 let snapshot = this.buffer.read(cx).read(cx);
27589 if let Some(relative_range_utf16) = range_utf16.as_ref() {
27590 for marked_range in &mut marked_ranges {
27591 marked_range.end = marked_range.start + relative_range_utf16.end;
27592 marked_range.start += relative_range_utf16.start;
27593 marked_range.start =
27594 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
27595 marked_range.end =
27596 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
27597 }
27598 }
27599 Some(marked_ranges)
27600 } else if let Some(range_utf16) = range_utf16 {
27601 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
27602 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
27603 Some(this.selection_replacement_ranges(range_utf16, cx))
27604 } else {
27605 None
27606 };
27607
27608 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
27609 let newest_selection_id = this.selections.newest_anchor().id;
27610 this.selections
27611 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
27612 .iter()
27613 .zip(ranges_to_replace.iter())
27614 .find_map(|(selection, range)| {
27615 if selection.id == newest_selection_id {
27616 Some(
27617 (range.start.0.0 as isize - selection.head().0.0 as isize)
27618 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
27619 )
27620 } else {
27621 None
27622 }
27623 })
27624 });
27625
27626 cx.emit(EditorEvent::InputHandled {
27627 utf16_range_to_replace: range_to_replace,
27628 text: text.into(),
27629 });
27630
27631 if let Some(ranges) = ranges_to_replace {
27632 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27633 s.select_ranges(ranges)
27634 });
27635 }
27636
27637 let marked_ranges = {
27638 let snapshot = this.buffer.read(cx).read(cx);
27639 this.selections
27640 .disjoint_anchors_arc()
27641 .iter()
27642 .map(|selection| {
27643 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
27644 })
27645 .collect::<Vec<_>>()
27646 };
27647
27648 if text.is_empty() {
27649 this.unmark_text(window, cx);
27650 } else {
27651 this.highlight_text::<InputComposition>(
27652 marked_ranges.clone(),
27653 HighlightStyle {
27654 underline: Some(UnderlineStyle {
27655 thickness: px(1.),
27656 color: None,
27657 wavy: false,
27658 }),
27659 ..Default::default()
27660 },
27661 cx,
27662 );
27663 }
27664
27665 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
27666 let use_autoclose = this.use_autoclose;
27667 let use_auto_surround = this.use_auto_surround;
27668 this.set_use_autoclose(false);
27669 this.set_use_auto_surround(false);
27670 this.handle_input(text, window, cx);
27671 this.set_use_autoclose(use_autoclose);
27672 this.set_use_auto_surround(use_auto_surround);
27673
27674 if let Some(new_selected_range) = new_selected_range_utf16 {
27675 let snapshot = this.buffer.read(cx).read(cx);
27676 let new_selected_ranges = marked_ranges
27677 .into_iter()
27678 .map(|marked_range| {
27679 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
27680 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
27681 insertion_start.0 + new_selected_range.start,
27682 ));
27683 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
27684 insertion_start.0 + new_selected_range.end,
27685 ));
27686 snapshot.clip_offset_utf16(new_start, Bias::Left)
27687 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
27688 })
27689 .collect::<Vec<_>>();
27690
27691 drop(snapshot);
27692 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
27693 selections.select_ranges(new_selected_ranges)
27694 });
27695 }
27696 });
27697
27698 self.ime_transaction = self.ime_transaction.or(transaction);
27699 if let Some(transaction) = self.ime_transaction {
27700 self.buffer.update(cx, |buffer, cx| {
27701 buffer.group_until_transaction(transaction, cx);
27702 });
27703 }
27704
27705 if self.text_highlights::<InputComposition>(cx).is_none() {
27706 self.ime_transaction.take();
27707 }
27708 }
27709
27710 fn bounds_for_range(
27711 &mut self,
27712 range_utf16: Range<usize>,
27713 element_bounds: gpui::Bounds<Pixels>,
27714 window: &mut Window,
27715 cx: &mut Context<Self>,
27716 ) -> Option<gpui::Bounds<Pixels>> {
27717 let text_layout_details = self.text_layout_details(window, cx);
27718 let CharacterDimensions {
27719 em_width,
27720 em_advance,
27721 line_height,
27722 } = self.character_dimensions(window, cx);
27723
27724 let snapshot = self.snapshot(window, cx);
27725 let scroll_position = snapshot.scroll_position();
27726 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
27727
27728 let start =
27729 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
27730 let x = Pixels::from(
27731 ScrollOffset::from(
27732 snapshot.x_for_display_point(start, &text_layout_details)
27733 + self.gutter_dimensions.full_width(),
27734 ) - scroll_left,
27735 );
27736 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
27737
27738 Some(Bounds {
27739 origin: element_bounds.origin + point(x, y),
27740 size: size(em_width, line_height),
27741 })
27742 }
27743
27744 fn character_index_for_point(
27745 &mut self,
27746 point: gpui::Point<Pixels>,
27747 _window: &mut Window,
27748 _cx: &mut Context<Self>,
27749 ) -> Option<usize> {
27750 let position_map = self.last_position_map.as_ref()?;
27751 if !position_map.text_hitbox.contains(&point) {
27752 return None;
27753 }
27754 let display_point = position_map.point_for_position(point).previous_valid;
27755 let anchor = position_map
27756 .snapshot
27757 .display_point_to_anchor(display_point, Bias::Left);
27758 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
27759 Some(utf16_offset.0.0)
27760 }
27761
27762 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
27763 self.input_enabled
27764 }
27765}
27766
27767trait SelectionExt {
27768 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
27769 fn spanned_rows(
27770 &self,
27771 include_end_if_at_line_start: bool,
27772 map: &DisplaySnapshot,
27773 ) -> Range<MultiBufferRow>;
27774}
27775
27776impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
27777 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
27778 let start = self
27779 .start
27780 .to_point(map.buffer_snapshot())
27781 .to_display_point(map);
27782 let end = self
27783 .end
27784 .to_point(map.buffer_snapshot())
27785 .to_display_point(map);
27786 if self.reversed {
27787 end..start
27788 } else {
27789 start..end
27790 }
27791 }
27792
27793 fn spanned_rows(
27794 &self,
27795 include_end_if_at_line_start: bool,
27796 map: &DisplaySnapshot,
27797 ) -> Range<MultiBufferRow> {
27798 let start = self.start.to_point(map.buffer_snapshot());
27799 let mut end = self.end.to_point(map.buffer_snapshot());
27800 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
27801 end.row -= 1;
27802 }
27803
27804 let buffer_start = map.prev_line_boundary(start).0;
27805 let buffer_end = map.next_line_boundary(end).0;
27806 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
27807 }
27808}
27809
27810impl<T: InvalidationRegion> InvalidationStack<T> {
27811 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
27812 where
27813 S: Clone + ToOffset,
27814 {
27815 while let Some(region) = self.last() {
27816 let all_selections_inside_invalidation_ranges =
27817 if selections.len() == region.ranges().len() {
27818 selections
27819 .iter()
27820 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
27821 .all(|(selection, invalidation_range)| {
27822 let head = selection.head().to_offset(buffer);
27823 invalidation_range.start <= head && invalidation_range.end >= head
27824 })
27825 } else {
27826 false
27827 };
27828
27829 if all_selections_inside_invalidation_ranges {
27830 break;
27831 } else {
27832 self.pop();
27833 }
27834 }
27835 }
27836}
27837
27838#[derive(Clone)]
27839struct ErasedEditorImpl(Entity<Editor>);
27840
27841impl ui_input::ErasedEditor for ErasedEditorImpl {
27842 fn text(&self, cx: &App) -> String {
27843 self.0.read(cx).text(cx)
27844 }
27845
27846 fn set_text(&self, text: &str, window: &mut Window, cx: &mut App) {
27847 self.0.update(cx, |this, cx| {
27848 this.set_text(text, window, cx);
27849 })
27850 }
27851
27852 fn clear(&self, window: &mut Window, cx: &mut App) {
27853 self.0.update(cx, |this, cx| this.clear(window, cx));
27854 }
27855
27856 fn set_placeholder_text(&self, text: &str, window: &mut Window, cx: &mut App) {
27857 self.0.update(cx, |this, cx| {
27858 this.set_placeholder_text(text, window, cx);
27859 });
27860 }
27861
27862 fn focus_handle(&self, cx: &App) -> FocusHandle {
27863 self.0.read(cx).focus_handle(cx)
27864 }
27865
27866 fn render(&self, _: &mut Window, cx: &App) -> AnyElement {
27867 let settings = ThemeSettings::get_global(cx);
27868 let theme_color = cx.theme().colors();
27869
27870 let text_style = TextStyle {
27871 font_family: settings.ui_font.family.clone(),
27872 font_features: settings.ui_font.features.clone(),
27873 font_size: rems(0.875).into(),
27874 font_weight: settings.buffer_font.weight,
27875 font_style: FontStyle::Normal,
27876 line_height: relative(1.2),
27877 color: theme_color.text,
27878 ..Default::default()
27879 };
27880 let editor_style = EditorStyle {
27881 background: theme_color.ghost_element_background,
27882 local_player: cx.theme().players().local(),
27883 syntax: cx.theme().syntax().clone(),
27884 text: text_style,
27885 ..Default::default()
27886 };
27887 EditorElement::new(&self.0, editor_style).into_any()
27888 }
27889
27890 fn as_any(&self) -> &dyn Any {
27891 &self.0
27892 }
27893
27894 fn move_selection_to_end(&self, window: &mut Window, cx: &mut App) {
27895 self.0.update(cx, |editor, cx| {
27896 let editor_offset = editor.buffer().read(cx).len(cx);
27897 editor.change_selections(
27898 SelectionEffects::scroll(Autoscroll::Next),
27899 window,
27900 cx,
27901 |s| s.select_ranges(Some(editor_offset..editor_offset)),
27902 );
27903 });
27904 }
27905
27906 fn subscribe(
27907 &self,
27908 mut callback: Box<dyn FnMut(ui_input::ErasedEditorEvent, &mut Window, &mut App) + 'static>,
27909 window: &mut Window,
27910 cx: &mut App,
27911 ) -> Subscription {
27912 window.subscribe(&self.0, cx, move |_, event: &EditorEvent, window, cx| {
27913 let event = match event {
27914 EditorEvent::BufferEdited => ui_input::ErasedEditorEvent::BufferEdited,
27915 EditorEvent::Blurred => ui_input::ErasedEditorEvent::Blurred,
27916 _ => return,
27917 };
27918 (callback)(event, window, cx);
27919 })
27920 }
27921
27922 fn set_masked(&self, masked: bool, _window: &mut Window, cx: &mut App) {
27923 self.0.update(cx, |editor, cx| {
27924 editor.set_masked(masked, cx);
27925 });
27926 }
27927}
27928impl<T> Default for InvalidationStack<T> {
27929 fn default() -> Self {
27930 Self(Default::default())
27931 }
27932}
27933
27934impl<T> Deref for InvalidationStack<T> {
27935 type Target = Vec<T>;
27936
27937 fn deref(&self) -> &Self::Target {
27938 &self.0
27939 }
27940}
27941
27942impl<T> DerefMut for InvalidationStack<T> {
27943 fn deref_mut(&mut self) -> &mut Self::Target {
27944 &mut self.0
27945 }
27946}
27947
27948impl InvalidationRegion for SnippetState {
27949 fn ranges(&self) -> &[Range<Anchor>] {
27950 &self.ranges[self.active_index]
27951 }
27952}
27953
27954fn edit_prediction_edit_text(
27955 current_snapshot: &BufferSnapshot,
27956 edits: &[(Range<Anchor>, impl AsRef<str>)],
27957 edit_preview: &EditPreview,
27958 include_deletions: bool,
27959 cx: &App,
27960) -> HighlightedText {
27961 let edits = edits
27962 .iter()
27963 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
27964 .collect::<Vec<_>>();
27965
27966 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
27967}
27968
27969fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
27970 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
27971 // Just show the raw edit text with basic styling
27972 let mut text = String::new();
27973 let mut highlights = Vec::new();
27974
27975 let insertion_highlight_style = HighlightStyle {
27976 color: Some(cx.theme().colors().text),
27977 ..Default::default()
27978 };
27979
27980 for (_, edit_text) in edits {
27981 let start_offset = text.len();
27982 text.push_str(edit_text);
27983 let end_offset = text.len();
27984
27985 if start_offset < end_offset {
27986 highlights.push((start_offset..end_offset, insertion_highlight_style));
27987 }
27988 }
27989
27990 HighlightedText {
27991 text: text.into(),
27992 highlights,
27993 }
27994}
27995
27996pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
27997 match severity {
27998 lsp::DiagnosticSeverity::ERROR => colors.error,
27999 lsp::DiagnosticSeverity::WARNING => colors.warning,
28000 lsp::DiagnosticSeverity::INFORMATION => colors.info,
28001 lsp::DiagnosticSeverity::HINT => colors.info,
28002 _ => colors.ignored,
28003 }
28004}
28005
28006pub fn styled_runs_for_code_label<'a>(
28007 label: &'a CodeLabel,
28008 syntax_theme: &'a theme::SyntaxTheme,
28009 local_player: &'a theme::PlayerColor,
28010) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
28011 let fade_out = HighlightStyle {
28012 fade_out: Some(0.35),
28013 ..Default::default()
28014 };
28015
28016 let mut prev_end = label.filter_range.end;
28017 label
28018 .runs
28019 .iter()
28020 .enumerate()
28021 .flat_map(move |(ix, (range, highlight_id))| {
28022 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
28023 HighlightStyle {
28024 color: Some(local_player.cursor),
28025 ..Default::default()
28026 }
28027 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
28028 HighlightStyle {
28029 background_color: Some(local_player.selection),
28030 ..Default::default()
28031 }
28032 } else if let Some(style) = highlight_id.style(syntax_theme) {
28033 style
28034 } else {
28035 return Default::default();
28036 };
28037 let muted_style = style.highlight(fade_out);
28038
28039 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
28040 if range.start >= label.filter_range.end {
28041 if range.start > prev_end {
28042 runs.push((prev_end..range.start, fade_out));
28043 }
28044 runs.push((range.clone(), muted_style));
28045 } else if range.end <= label.filter_range.end {
28046 runs.push((range.clone(), style));
28047 } else {
28048 runs.push((range.start..label.filter_range.end, style));
28049 runs.push((label.filter_range.end..range.end, muted_style));
28050 }
28051 prev_end = cmp::max(prev_end, range.end);
28052
28053 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
28054 runs.push((prev_end..label.text.len(), fade_out));
28055 }
28056
28057 runs
28058 })
28059}
28060
28061pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
28062 let mut prev_index = 0;
28063 let mut prev_codepoint: Option<char> = None;
28064 text.char_indices()
28065 .chain([(text.len(), '\0')])
28066 .filter_map(move |(index, codepoint)| {
28067 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28068 let is_boundary = index == text.len()
28069 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
28070 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
28071 if is_boundary {
28072 let chunk = &text[prev_index..index];
28073 prev_index = index;
28074 Some(chunk)
28075 } else {
28076 None
28077 }
28078 })
28079}
28080
28081/// Given a string of text immediately before the cursor, iterates over possible
28082/// strings a snippet could match to. More precisely: returns an iterator over
28083/// suffixes of `text` created by splitting at word boundaries (before & after
28084/// every non-word character).
28085///
28086/// Shorter suffixes are returned first.
28087pub(crate) fn snippet_candidate_suffixes(
28088 text: &str,
28089 is_word_char: impl Fn(char) -> bool,
28090) -> impl std::iter::Iterator<Item = &str> {
28091 let mut prev_index = text.len();
28092 let mut prev_codepoint = None;
28093 text.char_indices()
28094 .rev()
28095 .chain([(0, '\0')])
28096 .filter_map(move |(index, codepoint)| {
28097 let prev_index = std::mem::replace(&mut prev_index, index);
28098 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28099 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
28100 None
28101 } else {
28102 let chunk = &text[prev_index..]; // go to end of string
28103 Some(chunk)
28104 }
28105 })
28106}
28107
28108pub trait RangeToAnchorExt: Sized {
28109 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
28110
28111 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
28112 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
28113 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
28114 }
28115}
28116
28117impl<T: ToOffset> RangeToAnchorExt for Range<T> {
28118 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
28119 let start_offset = self.start.to_offset(snapshot);
28120 let end_offset = self.end.to_offset(snapshot);
28121 if start_offset == end_offset {
28122 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
28123 } else {
28124 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
28125 }
28126 }
28127}
28128
28129pub trait RowExt {
28130 fn as_f64(&self) -> f64;
28131
28132 fn next_row(&self) -> Self;
28133
28134 fn previous_row(&self) -> Self;
28135
28136 fn minus(&self, other: Self) -> u32;
28137}
28138
28139impl RowExt for DisplayRow {
28140 fn as_f64(&self) -> f64 {
28141 self.0 as _
28142 }
28143
28144 fn next_row(&self) -> Self {
28145 Self(self.0 + 1)
28146 }
28147
28148 fn previous_row(&self) -> Self {
28149 Self(self.0.saturating_sub(1))
28150 }
28151
28152 fn minus(&self, other: Self) -> u32 {
28153 self.0 - other.0
28154 }
28155}
28156
28157impl RowExt for MultiBufferRow {
28158 fn as_f64(&self) -> f64 {
28159 self.0 as _
28160 }
28161
28162 fn next_row(&self) -> Self {
28163 Self(self.0 + 1)
28164 }
28165
28166 fn previous_row(&self) -> Self {
28167 Self(self.0.saturating_sub(1))
28168 }
28169
28170 fn minus(&self, other: Self) -> u32 {
28171 self.0 - other.0
28172 }
28173}
28174
28175trait RowRangeExt {
28176 type Row;
28177
28178 fn len(&self) -> usize;
28179
28180 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
28181}
28182
28183impl RowRangeExt for Range<MultiBufferRow> {
28184 type Row = MultiBufferRow;
28185
28186 fn len(&self) -> usize {
28187 (self.end.0 - self.start.0) as usize
28188 }
28189
28190 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
28191 (self.start.0..self.end.0).map(MultiBufferRow)
28192 }
28193}
28194
28195impl RowRangeExt for Range<DisplayRow> {
28196 type Row = DisplayRow;
28197
28198 fn len(&self) -> usize {
28199 (self.end.0 - self.start.0) as usize
28200 }
28201
28202 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
28203 (self.start.0..self.end.0).map(DisplayRow)
28204 }
28205}
28206
28207/// If select range has more than one line, we
28208/// just point the cursor to range.start.
28209fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
28210 if range.start.row == range.end.row {
28211 range
28212 } else {
28213 range.start..range.start
28214 }
28215}
28216pub struct KillRing(ClipboardItem);
28217impl Global for KillRing {}
28218
28219const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
28220
28221enum BreakpointPromptEditAction {
28222 Log,
28223 Condition,
28224 HitCondition,
28225}
28226
28227struct BreakpointPromptEditor {
28228 pub(crate) prompt: Entity<Editor>,
28229 editor: WeakEntity<Editor>,
28230 breakpoint_anchor: Anchor,
28231 breakpoint: Breakpoint,
28232 edit_action: BreakpointPromptEditAction,
28233 block_ids: HashSet<CustomBlockId>,
28234 editor_margins: Arc<Mutex<EditorMargins>>,
28235 _subscriptions: Vec<Subscription>,
28236}
28237
28238impl BreakpointPromptEditor {
28239 const MAX_LINES: u8 = 4;
28240
28241 fn new(
28242 editor: WeakEntity<Editor>,
28243 breakpoint_anchor: Anchor,
28244 breakpoint: Breakpoint,
28245 edit_action: BreakpointPromptEditAction,
28246 window: &mut Window,
28247 cx: &mut Context<Self>,
28248 ) -> Self {
28249 let base_text = match edit_action {
28250 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
28251 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
28252 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
28253 }
28254 .map(|msg| msg.to_string())
28255 .unwrap_or_default();
28256
28257 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
28258 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
28259
28260 let prompt = cx.new(|cx| {
28261 let mut prompt = Editor::new(
28262 EditorMode::AutoHeight {
28263 min_lines: 1,
28264 max_lines: Some(Self::MAX_LINES as usize),
28265 },
28266 buffer,
28267 None,
28268 window,
28269 cx,
28270 );
28271 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
28272 prompt.set_show_cursor_when_unfocused(false, cx);
28273 prompt.set_placeholder_text(
28274 match edit_action {
28275 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
28276 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
28277 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
28278 },
28279 window,
28280 cx,
28281 );
28282
28283 prompt
28284 });
28285
28286 Self {
28287 prompt,
28288 editor,
28289 breakpoint_anchor,
28290 breakpoint,
28291 edit_action,
28292 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
28293 block_ids: Default::default(),
28294 _subscriptions: vec![],
28295 }
28296 }
28297
28298 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
28299 self.block_ids.extend(block_ids)
28300 }
28301
28302 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
28303 if let Some(editor) = self.editor.upgrade() {
28304 let message = self
28305 .prompt
28306 .read(cx)
28307 .buffer
28308 .read(cx)
28309 .as_singleton()
28310 .expect("A multi buffer in breakpoint prompt isn't possible")
28311 .read(cx)
28312 .as_rope()
28313 .to_string();
28314
28315 editor.update(cx, |editor, cx| {
28316 editor.edit_breakpoint_at_anchor(
28317 self.breakpoint_anchor,
28318 self.breakpoint.clone(),
28319 match self.edit_action {
28320 BreakpointPromptEditAction::Log => {
28321 BreakpointEditAction::EditLogMessage(message.into())
28322 }
28323 BreakpointPromptEditAction::Condition => {
28324 BreakpointEditAction::EditCondition(message.into())
28325 }
28326 BreakpointPromptEditAction::HitCondition => {
28327 BreakpointEditAction::EditHitCondition(message.into())
28328 }
28329 },
28330 cx,
28331 );
28332
28333 editor.remove_blocks(self.block_ids.clone(), None, cx);
28334 cx.focus_self(window);
28335 });
28336 }
28337 }
28338
28339 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
28340 self.editor
28341 .update(cx, |editor, cx| {
28342 editor.remove_blocks(self.block_ids.clone(), None, cx);
28343 window.focus(&editor.focus_handle, cx);
28344 })
28345 .log_err();
28346 }
28347
28348 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
28349 let settings = ThemeSettings::get_global(cx);
28350 let text_style = TextStyle {
28351 color: if self.prompt.read(cx).read_only(cx) {
28352 cx.theme().colors().text_disabled
28353 } else {
28354 cx.theme().colors().text
28355 },
28356 font_family: settings.buffer_font.family.clone(),
28357 font_fallbacks: settings.buffer_font.fallbacks.clone(),
28358 font_size: settings.buffer_font_size(cx).into(),
28359 font_weight: settings.buffer_font.weight,
28360 line_height: relative(settings.buffer_line_height.value()),
28361 ..Default::default()
28362 };
28363 EditorElement::new(
28364 &self.prompt,
28365 EditorStyle {
28366 background: cx.theme().colors().editor_background,
28367 local_player: cx.theme().players().local(),
28368 text: text_style,
28369 ..Default::default()
28370 },
28371 )
28372 }
28373}
28374
28375impl Render for BreakpointPromptEditor {
28376 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28377 let editor_margins = *self.editor_margins.lock();
28378 let gutter_dimensions = editor_margins.gutter;
28379 h_flex()
28380 .key_context("Editor")
28381 .bg(cx.theme().colors().editor_background)
28382 .border_y_1()
28383 .border_color(cx.theme().status().info_border)
28384 .size_full()
28385 .py(window.line_height() / 2.5)
28386 .on_action(cx.listener(Self::confirm))
28387 .on_action(cx.listener(Self::cancel))
28388 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
28389 .child(div().flex_1().child(self.render_prompt_editor(cx)))
28390 }
28391}
28392
28393impl Focusable for BreakpointPromptEditor {
28394 fn focus_handle(&self, cx: &App) -> FocusHandle {
28395 self.prompt.focus_handle(cx)
28396 }
28397}
28398
28399fn all_edits_insertions_or_deletions(
28400 edits: &Vec<(Range<Anchor>, Arc<str>)>,
28401 snapshot: &MultiBufferSnapshot,
28402) -> bool {
28403 let mut all_insertions = true;
28404 let mut all_deletions = true;
28405
28406 for (range, new_text) in edits.iter() {
28407 let range_is_empty = range.to_offset(snapshot).is_empty();
28408 let text_is_empty = new_text.is_empty();
28409
28410 if range_is_empty != text_is_empty {
28411 if range_is_empty {
28412 all_deletions = false;
28413 } else {
28414 all_insertions = false;
28415 }
28416 } else {
28417 return false;
28418 }
28419
28420 if !all_insertions && !all_deletions {
28421 return false;
28422 }
28423 }
28424 all_insertions || all_deletions
28425}
28426
28427struct MissingEditPredictionKeybindingTooltip;
28428
28429impl Render for MissingEditPredictionKeybindingTooltip {
28430 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28431 ui::tooltip_container(cx, |container, cx| {
28432 container
28433 .flex_shrink_0()
28434 .max_w_80()
28435 .min_h(rems_from_px(124.))
28436 .justify_between()
28437 .child(
28438 v_flex()
28439 .flex_1()
28440 .text_ui_sm(cx)
28441 .child(Label::new("Conflict with Accept Keybinding"))
28442 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
28443 )
28444 .child(
28445 h_flex()
28446 .pb_1()
28447 .gap_1()
28448 .items_end()
28449 .w_full()
28450 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
28451 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
28452 }))
28453 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
28454 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
28455 })),
28456 )
28457 })
28458 }
28459}
28460
28461#[derive(Debug, Clone, Copy, PartialEq)]
28462pub struct LineHighlight {
28463 pub background: Background,
28464 pub border: Option<gpui::Hsla>,
28465 pub include_gutter: bool,
28466 pub type_id: Option<TypeId>,
28467}
28468
28469struct LineManipulationResult {
28470 pub new_text: String,
28471 pub line_count_before: usize,
28472 pub line_count_after: usize,
28473}
28474
28475fn render_diff_hunk_controls(
28476 row: u32,
28477 status: &DiffHunkStatus,
28478 hunk_range: Range<Anchor>,
28479 is_created_file: bool,
28480 line_height: Pixels,
28481 editor: &Entity<Editor>,
28482 _window: &mut Window,
28483 cx: &mut App,
28484) -> AnyElement {
28485 h_flex()
28486 .h(line_height)
28487 .mr_1()
28488 .gap_1()
28489 .px_0p5()
28490 .pb_1()
28491 .border_x_1()
28492 .border_b_1()
28493 .border_color(cx.theme().colors().border_variant)
28494 .rounded_b_lg()
28495 .bg(cx.theme().colors().editor_background)
28496 .gap_1()
28497 .block_mouse_except_scroll()
28498 .shadow_md()
28499 .child(if status.has_secondary_hunk() {
28500 Button::new(("stage", row as u64), "Stage")
28501 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
28502 .tooltip({
28503 let focus_handle = editor.focus_handle(cx);
28504 move |_window, cx| {
28505 Tooltip::for_action_in(
28506 "Stage Hunk",
28507 &::git::ToggleStaged,
28508 &focus_handle,
28509 cx,
28510 )
28511 }
28512 })
28513 .on_click({
28514 let editor = editor.clone();
28515 move |_event, _window, cx| {
28516 editor.update(cx, |editor, cx| {
28517 editor.stage_or_unstage_diff_hunks(
28518 true,
28519 vec![hunk_range.start..hunk_range.start],
28520 cx,
28521 );
28522 });
28523 }
28524 })
28525 } else {
28526 Button::new(("unstage", row as u64), "Unstage")
28527 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
28528 .tooltip({
28529 let focus_handle = editor.focus_handle(cx);
28530 move |_window, cx| {
28531 Tooltip::for_action_in(
28532 "Unstage Hunk",
28533 &::git::ToggleStaged,
28534 &focus_handle,
28535 cx,
28536 )
28537 }
28538 })
28539 .on_click({
28540 let editor = editor.clone();
28541 move |_event, _window, cx| {
28542 editor.update(cx, |editor, cx| {
28543 editor.stage_or_unstage_diff_hunks(
28544 false,
28545 vec![hunk_range.start..hunk_range.start],
28546 cx,
28547 );
28548 });
28549 }
28550 })
28551 })
28552 .child(
28553 Button::new(("restore", row as u64), "Restore")
28554 .tooltip({
28555 let focus_handle = editor.focus_handle(cx);
28556 move |_window, cx| {
28557 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
28558 }
28559 })
28560 .on_click({
28561 let editor = editor.clone();
28562 move |_event, window, cx| {
28563 editor.update(cx, |editor, cx| {
28564 let snapshot = editor.snapshot(window, cx);
28565 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
28566 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
28567 });
28568 }
28569 })
28570 .disabled(is_created_file),
28571 )
28572 .when(
28573 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
28574 |el| {
28575 el.child(
28576 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
28577 .shape(IconButtonShape::Square)
28578 .icon_size(IconSize::Small)
28579 // .disabled(!has_multiple_hunks)
28580 .tooltip({
28581 let focus_handle = editor.focus_handle(cx);
28582 move |_window, cx| {
28583 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
28584 }
28585 })
28586 .on_click({
28587 let editor = editor.clone();
28588 move |_event, window, cx| {
28589 editor.update(cx, |editor, cx| {
28590 let snapshot = editor.snapshot(window, cx);
28591 let position =
28592 hunk_range.end.to_point(&snapshot.buffer_snapshot());
28593 editor.go_to_hunk_before_or_after_position(
28594 &snapshot,
28595 position,
28596 Direction::Next,
28597 window,
28598 cx,
28599 );
28600 editor.expand_selected_diff_hunks(cx);
28601 });
28602 }
28603 }),
28604 )
28605 .child(
28606 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
28607 .shape(IconButtonShape::Square)
28608 .icon_size(IconSize::Small)
28609 // .disabled(!has_multiple_hunks)
28610 .tooltip({
28611 let focus_handle = editor.focus_handle(cx);
28612 move |_window, cx| {
28613 Tooltip::for_action_in(
28614 "Previous Hunk",
28615 &GoToPreviousHunk,
28616 &focus_handle,
28617 cx,
28618 )
28619 }
28620 })
28621 .on_click({
28622 let editor = editor.clone();
28623 move |_event, window, cx| {
28624 editor.update(cx, |editor, cx| {
28625 let snapshot = editor.snapshot(window, cx);
28626 let point =
28627 hunk_range.start.to_point(&snapshot.buffer_snapshot());
28628 editor.go_to_hunk_before_or_after_position(
28629 &snapshot,
28630 point,
28631 Direction::Prev,
28632 window,
28633 cx,
28634 );
28635 editor.expand_selected_diff_hunks(cx);
28636 });
28637 }
28638 }),
28639 )
28640 },
28641 )
28642 .into_any_element()
28643}
28644
28645pub fn multibuffer_context_lines(cx: &App) -> u32 {
28646 EditorSettings::try_get(cx)
28647 .map(|settings| settings.excerpt_context_lines)
28648 .unwrap_or(2)
28649 .min(32)
28650}