1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod element;
22mod git;
23mod highlight_matching_bracket;
24mod hover_links;
25pub mod hover_popover;
26mod indent_guides;
27mod inlay_hint_cache;
28pub mod items;
29mod jsx_tag_auto_close;
30mod linked_editing_ranges;
31mod lsp_colors;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod edit_prediction_tests;
46#[cfg(test)]
47mod editor_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
54pub use edit_prediction::Direction;
55pub use editor_settings::{
56 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
57 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
58};
59pub use element::{
60 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
61};
62pub use git::blame::BlameRenderer;
63pub use hover_popover::hover_markdown_style;
64pub use items::MAX_TAB_TITLE_LEN;
65pub use lsp::CompletionContext;
66pub use lsp_ext::lsp_tasks;
67pub use multi_buffer::{
68 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
69 RowInfo, ToOffset, ToPoint,
70};
71pub use proposed_changes_editor::{
72 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
73};
74pub use text::Bias;
75
76use ::git::{
77 Restore,
78 blame::{BlameEntry, ParsedCommitMessage},
79};
80use aho_corasick::AhoCorasick;
81use anyhow::{Context as _, Result, anyhow};
82use blink_manager::BlinkManager;
83use buffer_diff::DiffHunkStatus;
84use client::{Collaborator, ParticipantIndex, parse_zed_link};
85use clock::{AGENT_REPLICA_ID, ReplicaId};
86use code_context_menus::{
87 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
88 CompletionsMenu, ContextMenuOrigin,
89};
90use collections::{BTreeMap, HashMap, HashSet, VecDeque};
91use convert_case::{Case, Casing};
92use dap::TelemetrySpawnLocation;
93use display_map::*;
94use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
95use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
96use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
97use futures::{
98 FutureExt, StreamExt as _,
99 future::{self, Shared, join},
100 stream::FuturesUnordered,
101};
102use fuzzy::{StringMatch, StringMatchCandidate};
103use git::blame::{GitBlame, GlobalBlameRenderer};
104use gpui::{
105 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
106 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
107 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
108 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
109 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
110 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
111 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
112 div, point, prelude::*, pulsating_between, px, relative, size,
113};
114use highlight_matching_bracket::refresh_matching_bracket_highlights;
115use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
116use hover_popover::{HoverState, hide_hover};
117use indent_guides::ActiveIndentGuidesState;
118use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
119use itertools::{Either, Itertools};
120use language::{
121 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
122 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
123 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
124 IndentSize, Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal,
125 TextObject, TransactionId, TreeSitterOptions, WordsQuery,
126 language_settings::{
127 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
128 all_language_settings, language_settings,
129 },
130 point_from_lsp, point_to_lsp, text_diff_with_options,
131};
132use linked_editing_ranges::refresh_linked_ranges;
133use lsp::{
134 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
135 LanguageServerId,
136};
137use lsp_colors::LspColorData;
138use markdown::Markdown;
139use mouse_context_menu::MouseContextMenu;
140use movement::TextLayoutDetails;
141use multi_buffer::{
142 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
143 ToOffsetUtf16,
144};
145use parking_lot::Mutex;
146use persistence::DB;
147use project::{
148 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
149 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint,
150 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectPath,
151 ProjectTransaction, TaskSourceKind,
152 debugger::{
153 breakpoint_store::{
154 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
155 BreakpointStore, BreakpointStoreEvent,
156 },
157 session::{Session, SessionEvent},
158 },
159 git_store::{GitStoreEvent, RepositoryEvent},
160 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
161 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
162};
163use rand::seq::SliceRandom;
164use rpc::{ErrorCode, ErrorExt, proto::PeerId};
165use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
166use selections_collection::{
167 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
168};
169use serde::{Deserialize, Serialize};
170use settings::{GitGutterSetting, Settings, SettingsLocation, SettingsStore, update_settings_file};
171use smallvec::{SmallVec, smallvec};
172use snippet::Snippet;
173use std::{
174 any::{Any, TypeId},
175 borrow::Cow,
176 cell::{OnceCell, RefCell},
177 cmp::{self, Ordering, Reverse},
178 iter::{self, Peekable},
179 mem,
180 num::NonZeroU32,
181 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
182 path::{Path, PathBuf},
183 rc::Rc,
184 sync::Arc,
185 time::{Duration, Instant},
186};
187use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
188use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
189use theme::{
190 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
191 observe_buffer_font_size_adjustment,
192};
193use ui::{
194 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
195 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
196};
197use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
198use workspace::{
199 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
200 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
201 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
202 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
203 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
204 searchable::SearchEvent,
205};
206
207use crate::{
208 code_context_menus::CompletionsMenuSource,
209 editor_settings::MultiCursorModifier,
210 hover_links::{find_url, find_url_from_range},
211 scroll::{ScrollOffset, ScrollPixelOffset},
212 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
213};
214
215pub const FILE_HEADER_HEIGHT: u32 = 2;
216pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
217const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
218const MAX_LINE_LEN: usize = 1024;
219const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
220const MAX_SELECTION_HISTORY_LEN: usize = 1024;
221pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
222#[doc(hidden)]
223pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
224pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
225
226pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
227pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
228pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
229pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
230
231pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
232pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
233pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
234
235pub type RenderDiffHunkControlsFn = Arc<
236 dyn Fn(
237 u32,
238 &DiffHunkStatus,
239 Range<Anchor>,
240 bool,
241 Pixels,
242 &Entity<Editor>,
243 &mut Window,
244 &mut App,
245 ) -> AnyElement,
246>;
247
248enum ReportEditorEvent {
249 Saved { auto_saved: bool },
250 EditorOpened,
251 Closed,
252}
253
254impl ReportEditorEvent {
255 pub fn event_type(&self) -> &'static str {
256 match self {
257 Self::Saved { .. } => "Editor Saved",
258 Self::EditorOpened => "Editor Opened",
259 Self::Closed => "Editor Closed",
260 }
261 }
262}
263
264struct InlineValueCache {
265 enabled: bool,
266 inlays: Vec<InlayId>,
267 refresh_task: Task<Option<()>>,
268}
269
270impl InlineValueCache {
271 fn new(enabled: bool) -> Self {
272 Self {
273 enabled,
274 inlays: Vec::new(),
275 refresh_task: Task::ready(None),
276 }
277 }
278}
279
280#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
281pub enum InlayId {
282 EditPrediction(u32),
283 DebuggerValue(u32),
284 // LSP
285 Hint(u32),
286 Color(u32),
287}
288
289impl InlayId {
290 fn id(&self) -> u32 {
291 match self {
292 Self::EditPrediction(id) => *id,
293 Self::DebuggerValue(id) => *id,
294 Self::Hint(id) => *id,
295 Self::Color(id) => *id,
296 }
297 }
298}
299
300pub enum ActiveDebugLine {}
301pub enum DebugStackFrameLine {}
302enum DocumentHighlightRead {}
303enum DocumentHighlightWrite {}
304enum InputComposition {}
305pub enum PendingInput {}
306enum SelectedTextHighlight {}
307
308pub enum ConflictsOuter {}
309pub enum ConflictsOurs {}
310pub enum ConflictsTheirs {}
311pub enum ConflictsOursMarker {}
312pub enum ConflictsTheirsMarker {}
313
314#[derive(Debug, Copy, Clone, PartialEq, Eq)]
315pub enum Navigated {
316 Yes,
317 No,
318}
319
320impl Navigated {
321 pub fn from_bool(yes: bool) -> Navigated {
322 if yes { Navigated::Yes } else { Navigated::No }
323 }
324}
325
326#[derive(Debug, Clone, PartialEq, Eq)]
327enum DisplayDiffHunk {
328 Folded {
329 display_row: DisplayRow,
330 },
331 Unfolded {
332 is_created_file: bool,
333 diff_base_byte_range: Range<usize>,
334 display_row_range: Range<DisplayRow>,
335 multi_buffer_range: Range<Anchor>,
336 status: DiffHunkStatus,
337 },
338}
339
340pub enum HideMouseCursorOrigin {
341 TypingAction,
342 MovementAction,
343}
344
345pub fn init_settings(cx: &mut App) {
346 EditorSettings::register(cx);
347}
348
349pub fn init(cx: &mut App) {
350 init_settings(cx);
351
352 cx.set_global(GlobalBlameRenderer(Arc::new(())));
353
354 workspace::register_project_item::<Editor>(cx);
355 workspace::FollowableViewRegistry::register::<Editor>(cx);
356 workspace::register_serializable_item::<Editor>(cx);
357
358 cx.observe_new(
359 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
360 workspace.register_action(Editor::new_file);
361 workspace.register_action(Editor::new_file_vertical);
362 workspace.register_action(Editor::new_file_horizontal);
363 workspace.register_action(Editor::cancel_language_server_work);
364 workspace.register_action(Editor::toggle_focus);
365 },
366 )
367 .detach();
368
369 cx.on_action(move |_: &workspace::NewFile, cx| {
370 let app_state = workspace::AppState::global(cx);
371 if let Some(app_state) = app_state.upgrade() {
372 workspace::open_new(
373 Default::default(),
374 app_state,
375 cx,
376 |workspace, window, cx| {
377 Editor::new_file(workspace, &Default::default(), window, cx)
378 },
379 )
380 .detach();
381 }
382 });
383 cx.on_action(move |_: &workspace::NewWindow, cx| {
384 let app_state = workspace::AppState::global(cx);
385 if let Some(app_state) = app_state.upgrade() {
386 workspace::open_new(
387 Default::default(),
388 app_state,
389 cx,
390 |workspace, window, cx| {
391 cx.activate(true);
392 Editor::new_file(workspace, &Default::default(), window, cx)
393 },
394 )
395 .detach();
396 }
397 });
398}
399
400pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
401 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
402}
403
404pub trait DiagnosticRenderer {
405 fn render_group(
406 &self,
407 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
408 buffer_id: BufferId,
409 snapshot: EditorSnapshot,
410 editor: WeakEntity<Editor>,
411 cx: &mut App,
412 ) -> Vec<BlockProperties<Anchor>>;
413
414 fn render_hover(
415 &self,
416 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
417 range: Range<Point>,
418 buffer_id: BufferId,
419 cx: &mut App,
420 ) -> Option<Entity<markdown::Markdown>>;
421
422 fn open_link(
423 &self,
424 editor: &mut Editor,
425 link: SharedString,
426 window: &mut Window,
427 cx: &mut Context<Editor>,
428 );
429}
430
431pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
432
433impl GlobalDiagnosticRenderer {
434 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
435 cx.try_global::<Self>().map(|g| g.0.clone())
436 }
437}
438
439impl gpui::Global for GlobalDiagnosticRenderer {}
440pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
441 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
442}
443
444pub struct SearchWithinRange;
445
446trait InvalidationRegion {
447 fn ranges(&self) -> &[Range<Anchor>];
448}
449
450#[derive(Clone, Debug, PartialEq)]
451pub enum SelectPhase {
452 Begin {
453 position: DisplayPoint,
454 add: bool,
455 click_count: usize,
456 },
457 BeginColumnar {
458 position: DisplayPoint,
459 reset: bool,
460 mode: ColumnarMode,
461 goal_column: u32,
462 },
463 Extend {
464 position: DisplayPoint,
465 click_count: usize,
466 },
467 Update {
468 position: DisplayPoint,
469 goal_column: u32,
470 scroll_delta: gpui::Point<f32>,
471 },
472 End,
473}
474
475#[derive(Clone, Debug, PartialEq)]
476pub enum ColumnarMode {
477 FromMouse,
478 FromSelection,
479}
480
481#[derive(Clone, Debug)]
482pub enum SelectMode {
483 Character,
484 Word(Range<Anchor>),
485 Line(Range<Anchor>),
486 All,
487}
488
489#[derive(Clone, PartialEq, Eq, Debug)]
490pub enum EditorMode {
491 SingleLine,
492 AutoHeight {
493 min_lines: usize,
494 max_lines: Option<usize>,
495 },
496 Full {
497 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
498 scale_ui_elements_with_buffer_font_size: bool,
499 /// When set to `true`, the editor will render a background for the active line.
500 show_active_line_background: bool,
501 /// When set to `true`, the editor's height will be determined by its content.
502 sized_by_content: bool,
503 },
504 Minimap {
505 parent: WeakEntity<Editor>,
506 },
507}
508
509impl EditorMode {
510 pub fn full() -> Self {
511 Self::Full {
512 scale_ui_elements_with_buffer_font_size: true,
513 show_active_line_background: true,
514 sized_by_content: false,
515 }
516 }
517
518 #[inline]
519 pub fn is_full(&self) -> bool {
520 matches!(self, Self::Full { .. })
521 }
522
523 #[inline]
524 pub fn is_single_line(&self) -> bool {
525 matches!(self, Self::SingleLine { .. })
526 }
527
528 #[inline]
529 fn is_minimap(&self) -> bool {
530 matches!(self, Self::Minimap { .. })
531 }
532}
533
534#[derive(Copy, Clone, Debug)]
535pub enum SoftWrap {
536 /// Prefer not to wrap at all.
537 ///
538 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
539 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
540 GitDiff,
541 /// Prefer a single line generally, unless an overly long line is encountered.
542 None,
543 /// Soft wrap lines that exceed the editor width.
544 EditorWidth,
545 /// Soft wrap lines at the preferred line length.
546 Column(u32),
547 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
548 Bounded(u32),
549}
550
551#[derive(Clone)]
552pub struct EditorStyle {
553 pub background: Hsla,
554 pub border: Hsla,
555 pub local_player: PlayerColor,
556 pub text: TextStyle,
557 pub scrollbar_width: Pixels,
558 pub syntax: Arc<SyntaxTheme>,
559 pub status: StatusColors,
560 pub inlay_hints_style: HighlightStyle,
561 pub edit_prediction_styles: EditPredictionStyles,
562 pub unnecessary_code_fade: f32,
563 pub show_underlines: bool,
564}
565
566impl Default for EditorStyle {
567 fn default() -> Self {
568 Self {
569 background: Hsla::default(),
570 border: Hsla::default(),
571 local_player: PlayerColor::default(),
572 text: TextStyle::default(),
573 scrollbar_width: Pixels::default(),
574 syntax: Default::default(),
575 // HACK: Status colors don't have a real default.
576 // We should look into removing the status colors from the editor
577 // style and retrieve them directly from the theme.
578 status: StatusColors::dark(),
579 inlay_hints_style: HighlightStyle::default(),
580 edit_prediction_styles: EditPredictionStyles {
581 insertion: HighlightStyle::default(),
582 whitespace: HighlightStyle::default(),
583 },
584 unnecessary_code_fade: Default::default(),
585 show_underlines: true,
586 }
587 }
588}
589
590pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
591 let show_background = language_settings::language_settings(None, None, cx)
592 .inlay_hints
593 .show_background;
594
595 let mut style = cx.theme().syntax().get("hint");
596
597 if style.color.is_none() {
598 style.color = Some(cx.theme().status().hint);
599 }
600
601 if !show_background {
602 style.background_color = None;
603 return style;
604 }
605
606 if style.background_color.is_none() {
607 style.background_color = Some(cx.theme().status().hint_background);
608 }
609
610 style
611}
612
613pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
614 EditPredictionStyles {
615 insertion: HighlightStyle {
616 color: Some(cx.theme().status().predictive),
617 ..HighlightStyle::default()
618 },
619 whitespace: HighlightStyle {
620 background_color: Some(cx.theme().status().created_background),
621 ..HighlightStyle::default()
622 },
623 }
624}
625
626type CompletionId = usize;
627
628pub(crate) enum EditDisplayMode {
629 TabAccept,
630 DiffPopover,
631 Inline,
632}
633
634enum EditPrediction {
635 Edit {
636 edits: Vec<(Range<Anchor>, String)>,
637 edit_preview: Option<EditPreview>,
638 display_mode: EditDisplayMode,
639 snapshot: BufferSnapshot,
640 },
641 /// Move to a specific location in the active editor
642 MoveWithin {
643 target: Anchor,
644 snapshot: BufferSnapshot,
645 },
646 /// Move to a specific location in a different editor (not the active one)
647 MoveOutside {
648 target: language::Anchor,
649 snapshot: BufferSnapshot,
650 },
651}
652
653struct EditPredictionState {
654 inlay_ids: Vec<InlayId>,
655 completion: EditPrediction,
656 completion_id: Option<SharedString>,
657 invalidation_range: Option<Range<Anchor>>,
658}
659
660enum EditPredictionSettings {
661 Disabled,
662 Enabled {
663 show_in_menu: bool,
664 preview_requires_modifier: bool,
665 },
666}
667
668enum EditPredictionHighlight {}
669
670#[derive(Debug, Clone)]
671struct InlineDiagnostic {
672 message: SharedString,
673 group_id: usize,
674 is_primary: bool,
675 start: Point,
676 severity: lsp::DiagnosticSeverity,
677}
678
679pub enum MenuEditPredictionsPolicy {
680 Never,
681 ByProvider,
682}
683
684pub enum EditPredictionPreview {
685 /// Modifier is not pressed
686 Inactive { released_too_fast: bool },
687 /// Modifier pressed
688 Active {
689 since: Instant,
690 previous_scroll_position: Option<ScrollAnchor>,
691 },
692}
693
694impl EditPredictionPreview {
695 pub fn released_too_fast(&self) -> bool {
696 match self {
697 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
698 EditPredictionPreview::Active { .. } => false,
699 }
700 }
701
702 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
703 if let EditPredictionPreview::Active {
704 previous_scroll_position,
705 ..
706 } = self
707 {
708 *previous_scroll_position = scroll_position;
709 }
710 }
711}
712
713pub struct ContextMenuOptions {
714 pub min_entries_visible: usize,
715 pub max_entries_visible: usize,
716 pub placement: Option<ContextMenuPlacement>,
717}
718
719#[derive(Debug, Clone, PartialEq, Eq)]
720pub enum ContextMenuPlacement {
721 Above,
722 Below,
723}
724
725#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
726struct EditorActionId(usize);
727
728impl EditorActionId {
729 pub fn post_inc(&mut self) -> Self {
730 let answer = self.0;
731
732 *self = Self(answer + 1);
733
734 Self(answer)
735 }
736}
737
738// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
739// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
740
741type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
742type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
743
744#[derive(Default)]
745struct ScrollbarMarkerState {
746 scrollbar_size: Size<Pixels>,
747 dirty: bool,
748 markers: Arc<[PaintQuad]>,
749 pending_refresh: Option<Task<Result<()>>>,
750}
751
752impl ScrollbarMarkerState {
753 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
754 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
755 }
756}
757
758#[derive(Clone, Copy, PartialEq, Eq)]
759pub enum MinimapVisibility {
760 Disabled,
761 Enabled {
762 /// The configuration currently present in the users settings.
763 setting_configuration: bool,
764 /// Whether to override the currently set visibility from the users setting.
765 toggle_override: bool,
766 },
767}
768
769impl MinimapVisibility {
770 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
771 if mode.is_full() {
772 Self::Enabled {
773 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
774 toggle_override: false,
775 }
776 } else {
777 Self::Disabled
778 }
779 }
780
781 fn hidden(&self) -> Self {
782 match *self {
783 Self::Enabled {
784 setting_configuration,
785 ..
786 } => Self::Enabled {
787 setting_configuration,
788 toggle_override: setting_configuration,
789 },
790 Self::Disabled => Self::Disabled,
791 }
792 }
793
794 fn disabled(&self) -> bool {
795 matches!(*self, Self::Disabled)
796 }
797
798 fn settings_visibility(&self) -> bool {
799 match *self {
800 Self::Enabled {
801 setting_configuration,
802 ..
803 } => setting_configuration,
804 _ => false,
805 }
806 }
807
808 fn visible(&self) -> bool {
809 match *self {
810 Self::Enabled {
811 setting_configuration,
812 toggle_override,
813 } => setting_configuration ^ toggle_override,
814 _ => false,
815 }
816 }
817
818 fn toggle_visibility(&self) -> Self {
819 match *self {
820 Self::Enabled {
821 toggle_override,
822 setting_configuration,
823 } => Self::Enabled {
824 setting_configuration,
825 toggle_override: !toggle_override,
826 },
827 Self::Disabled => Self::Disabled,
828 }
829 }
830}
831
832#[derive(Clone, Debug)]
833struct RunnableTasks {
834 templates: Vec<(TaskSourceKind, TaskTemplate)>,
835 offset: multi_buffer::Anchor,
836 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
837 column: u32,
838 // Values of all named captures, including those starting with '_'
839 extra_variables: HashMap<String, String>,
840 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
841 context_range: Range<BufferOffset>,
842}
843
844impl RunnableTasks {
845 fn resolve<'a>(
846 &'a self,
847 cx: &'a task::TaskContext,
848 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
849 self.templates.iter().filter_map(|(kind, template)| {
850 template
851 .resolve_task(&kind.to_id_base(), cx)
852 .map(|task| (kind.clone(), task))
853 })
854 }
855}
856
857#[derive(Clone)]
858pub struct ResolvedTasks {
859 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
860 position: Anchor,
861}
862
863#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
864struct BufferOffset(usize);
865
866/// Addons allow storing per-editor state in other crates (e.g. Vim)
867pub trait Addon: 'static {
868 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
869
870 fn render_buffer_header_controls(
871 &self,
872 _: &ExcerptInfo,
873 _: &Window,
874 _: &App,
875 ) -> Option<AnyElement> {
876 None
877 }
878
879 fn to_any(&self) -> &dyn std::any::Any;
880
881 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
882 None
883 }
884}
885
886struct ChangeLocation {
887 current: Option<Vec<Anchor>>,
888 original: Vec<Anchor>,
889}
890impl ChangeLocation {
891 fn locations(&self) -> &[Anchor] {
892 self.current.as_ref().unwrap_or(&self.original)
893 }
894}
895
896/// A set of caret positions, registered when the editor was edited.
897pub struct ChangeList {
898 changes: Vec<ChangeLocation>,
899 /// Currently "selected" change.
900 position: Option<usize>,
901}
902
903impl ChangeList {
904 pub fn new() -> Self {
905 Self {
906 changes: Vec::new(),
907 position: None,
908 }
909 }
910
911 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
912 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
913 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
914 if self.changes.is_empty() {
915 return None;
916 }
917
918 let prev = self.position.unwrap_or(self.changes.len());
919 let next = if direction == Direction::Prev {
920 prev.saturating_sub(count)
921 } else {
922 (prev + count).min(self.changes.len() - 1)
923 };
924 self.position = Some(next);
925 self.changes.get(next).map(|change| change.locations())
926 }
927
928 /// Adds a new change to the list, resetting the change list position.
929 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
930 self.position.take();
931 if let Some(last) = self.changes.last_mut()
932 && group
933 {
934 last.current = Some(new_positions)
935 } else {
936 self.changes.push(ChangeLocation {
937 original: new_positions,
938 current: None,
939 });
940 }
941 }
942
943 pub fn last(&self) -> Option<&[Anchor]> {
944 self.changes.last().map(|change| change.locations())
945 }
946
947 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
948 self.changes.last().map(|change| change.original.as_slice())
949 }
950
951 pub fn invert_last_group(&mut self) {
952 if let Some(last) = self.changes.last_mut()
953 && let Some(current) = last.current.as_mut()
954 {
955 mem::swap(&mut last.original, current);
956 }
957 }
958}
959
960#[derive(Clone)]
961struct InlineBlamePopoverState {
962 scroll_handle: ScrollHandle,
963 commit_message: Option<ParsedCommitMessage>,
964 markdown: Entity<Markdown>,
965}
966
967struct InlineBlamePopover {
968 position: gpui::Point<Pixels>,
969 hide_task: Option<Task<()>>,
970 popover_bounds: Option<Bounds<Pixels>>,
971 popover_state: InlineBlamePopoverState,
972 keyboard_grace: bool,
973}
974
975enum SelectionDragState {
976 /// State when no drag related activity is detected.
977 None,
978 /// State when the mouse is down on a selection that is about to be dragged.
979 ReadyToDrag {
980 selection: Selection<Anchor>,
981 click_position: gpui::Point<Pixels>,
982 mouse_down_time: Instant,
983 },
984 /// State when the mouse is dragging the selection in the editor.
985 Dragging {
986 selection: Selection<Anchor>,
987 drop_cursor: Selection<Anchor>,
988 hide_drop_cursor: bool,
989 },
990}
991
992enum ColumnarSelectionState {
993 FromMouse {
994 selection_tail: Anchor,
995 display_point: Option<DisplayPoint>,
996 },
997 FromSelection {
998 selection_tail: Anchor,
999 },
1000}
1001
1002/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1003/// a breakpoint on them.
1004#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1005struct PhantomBreakpointIndicator {
1006 display_row: DisplayRow,
1007 /// There's a small debounce between hovering over the line and showing the indicator.
1008 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1009 is_active: bool,
1010 collides_with_existing_breakpoint: bool,
1011}
1012
1013/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1014///
1015/// See the [module level documentation](self) for more information.
1016pub struct Editor {
1017 focus_handle: FocusHandle,
1018 last_focused_descendant: Option<WeakFocusHandle>,
1019 /// The text buffer being edited
1020 buffer: Entity<MultiBuffer>,
1021 /// Map of how text in the buffer should be displayed.
1022 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1023 pub display_map: Entity<DisplayMap>,
1024 placeholder_display_map: Option<Entity<DisplayMap>>,
1025 pub selections: SelectionsCollection,
1026 pub scroll_manager: ScrollManager,
1027 /// When inline assist editors are linked, they all render cursors because
1028 /// typing enters text into each of them, even the ones that aren't focused.
1029 pub(crate) show_cursor_when_unfocused: bool,
1030 columnar_selection_state: Option<ColumnarSelectionState>,
1031 add_selections_state: Option<AddSelectionsState>,
1032 select_next_state: Option<SelectNextState>,
1033 select_prev_state: Option<SelectNextState>,
1034 selection_history: SelectionHistory,
1035 defer_selection_effects: bool,
1036 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1037 autoclose_regions: Vec<AutocloseRegion>,
1038 snippet_stack: InvalidationStack<SnippetState>,
1039 select_syntax_node_history: SelectSyntaxNodeHistory,
1040 ime_transaction: Option<TransactionId>,
1041 pub diagnostics_max_severity: DiagnosticSeverity,
1042 active_diagnostics: ActiveDiagnostic,
1043 show_inline_diagnostics: bool,
1044 inline_diagnostics_update: Task<()>,
1045 inline_diagnostics_enabled: bool,
1046 diagnostics_enabled: bool,
1047 word_completions_enabled: bool,
1048 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1049 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1050 hard_wrap: Option<usize>,
1051 project: Option<Entity<Project>>,
1052 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1053 completion_provider: Option<Rc<dyn CompletionProvider>>,
1054 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1055 blink_manager: Entity<BlinkManager>,
1056 show_cursor_names: bool,
1057 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1058 pub show_local_selections: bool,
1059 mode: EditorMode,
1060 show_breadcrumbs: bool,
1061 show_gutter: bool,
1062 show_scrollbars: ScrollbarAxes,
1063 minimap_visibility: MinimapVisibility,
1064 offset_content: bool,
1065 disable_expand_excerpt_buttons: bool,
1066 show_line_numbers: Option<bool>,
1067 use_relative_line_numbers: Option<bool>,
1068 show_git_diff_gutter: Option<bool>,
1069 show_code_actions: Option<bool>,
1070 show_runnables: Option<bool>,
1071 show_breakpoints: Option<bool>,
1072 show_wrap_guides: Option<bool>,
1073 show_indent_guides: Option<bool>,
1074 highlight_order: usize,
1075 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1076 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1077 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1078 scrollbar_marker_state: ScrollbarMarkerState,
1079 active_indent_guides_state: ActiveIndentGuidesState,
1080 nav_history: Option<ItemNavHistory>,
1081 context_menu: RefCell<Option<CodeContextMenu>>,
1082 context_menu_options: Option<ContextMenuOptions>,
1083 mouse_context_menu: Option<MouseContextMenu>,
1084 completion_tasks: Vec<(CompletionId, Task<()>)>,
1085 inline_blame_popover: Option<InlineBlamePopover>,
1086 inline_blame_popover_show_task: Option<Task<()>>,
1087 signature_help_state: SignatureHelpState,
1088 auto_signature_help: Option<bool>,
1089 find_all_references_task_sources: Vec<Anchor>,
1090 next_completion_id: CompletionId,
1091 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1092 code_actions_task: Option<Task<Result<()>>>,
1093 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1094 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1095 document_highlights_task: Option<Task<()>>,
1096 linked_editing_range_task: Option<Task<Option<()>>>,
1097 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1098 pending_rename: Option<RenameState>,
1099 searchable: bool,
1100 cursor_shape: CursorShape,
1101 current_line_highlight: Option<CurrentLineHighlight>,
1102 collapse_matches: bool,
1103 autoindent_mode: Option<AutoindentMode>,
1104 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1105 input_enabled: bool,
1106 use_modal_editing: bool,
1107 read_only: bool,
1108 leader_id: Option<CollaboratorId>,
1109 remote_id: Option<ViewId>,
1110 pub hover_state: HoverState,
1111 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1112 gutter_hovered: bool,
1113 hovered_link_state: Option<HoveredLinkState>,
1114 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1115 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1116 active_edit_prediction: Option<EditPredictionState>,
1117 /// Used to prevent flickering as the user types while the menu is open
1118 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1119 edit_prediction_settings: EditPredictionSettings,
1120 edit_predictions_hidden_for_vim_mode: bool,
1121 show_edit_predictions_override: Option<bool>,
1122 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1123 edit_prediction_preview: EditPredictionPreview,
1124 edit_prediction_indent_conflict: bool,
1125 edit_prediction_requires_modifier_in_indent_conflict: bool,
1126 inlay_hint_cache: InlayHintCache,
1127 next_inlay_id: u32,
1128 next_color_inlay_id: u32,
1129 _subscriptions: Vec<Subscription>,
1130 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1131 gutter_dimensions: GutterDimensions,
1132 style: Option<EditorStyle>,
1133 text_style_refinement: Option<TextStyleRefinement>,
1134 next_editor_action_id: EditorActionId,
1135 editor_actions: Rc<
1136 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1137 >,
1138 use_autoclose: bool,
1139 use_auto_surround: bool,
1140 auto_replace_emoji_shortcode: bool,
1141 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1142 show_git_blame_gutter: bool,
1143 show_git_blame_inline: bool,
1144 show_git_blame_inline_delay_task: Option<Task<()>>,
1145 git_blame_inline_enabled: bool,
1146 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1147 serialize_dirty_buffers: bool,
1148 show_selection_menu: Option<bool>,
1149 blame: Option<Entity<GitBlame>>,
1150 blame_subscription: Option<Subscription>,
1151 custom_context_menu: Option<
1152 Box<
1153 dyn 'static
1154 + Fn(
1155 &mut Self,
1156 DisplayPoint,
1157 &mut Window,
1158 &mut Context<Self>,
1159 ) -> Option<Entity<ui::ContextMenu>>,
1160 >,
1161 >,
1162 last_bounds: Option<Bounds<Pixels>>,
1163 last_position_map: Option<Rc<PositionMap>>,
1164 expect_bounds_change: Option<Bounds<Pixels>>,
1165 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1166 tasks_update_task: Option<Task<()>>,
1167 breakpoint_store: Option<Entity<BreakpointStore>>,
1168 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1169 hovered_diff_hunk_row: Option<DisplayRow>,
1170 pull_diagnostics_task: Task<()>,
1171 in_project_search: bool,
1172 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1173 breadcrumb_header: Option<String>,
1174 focused_block: Option<FocusedBlock>,
1175 next_scroll_position: NextScrollCursorCenterTopBottom,
1176 addons: HashMap<TypeId, Box<dyn Addon>>,
1177 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1178 load_diff_task: Option<Shared<Task<()>>>,
1179 /// Whether we are temporarily displaying a diff other than git's
1180 temporary_diff_override: bool,
1181 selection_mark_mode: bool,
1182 toggle_fold_multiple_buffers: Task<()>,
1183 _scroll_cursor_center_top_bottom_task: Task<()>,
1184 serialize_selections: Task<()>,
1185 serialize_folds: Task<()>,
1186 mouse_cursor_hidden: bool,
1187 minimap: Option<Entity<Self>>,
1188 hide_mouse_mode: HideMouseMode,
1189 pub change_list: ChangeList,
1190 inline_value_cache: InlineValueCache,
1191 selection_drag_state: SelectionDragState,
1192 colors: Option<LspColorData>,
1193 post_scroll_update: Task<()>,
1194 refresh_colors_task: Task<()>,
1195 folding_newlines: Task<()>,
1196 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1197}
1198
1199#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1200enum NextScrollCursorCenterTopBottom {
1201 #[default]
1202 Center,
1203 Top,
1204 Bottom,
1205}
1206
1207impl NextScrollCursorCenterTopBottom {
1208 fn next(&self) -> Self {
1209 match self {
1210 Self::Center => Self::Top,
1211 Self::Top => Self::Bottom,
1212 Self::Bottom => Self::Center,
1213 }
1214 }
1215}
1216
1217#[derive(Clone)]
1218pub struct EditorSnapshot {
1219 pub mode: EditorMode,
1220 show_gutter: bool,
1221 show_line_numbers: Option<bool>,
1222 show_git_diff_gutter: Option<bool>,
1223 show_code_actions: Option<bool>,
1224 show_runnables: Option<bool>,
1225 show_breakpoints: Option<bool>,
1226 git_blame_gutter_max_author_length: Option<usize>,
1227 pub display_snapshot: DisplaySnapshot,
1228 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1229 is_focused: bool,
1230 scroll_anchor: ScrollAnchor,
1231 ongoing_scroll: OngoingScroll,
1232 current_line_highlight: CurrentLineHighlight,
1233 gutter_hovered: bool,
1234}
1235
1236#[derive(Default, Debug, Clone, Copy)]
1237pub struct GutterDimensions {
1238 pub left_padding: Pixels,
1239 pub right_padding: Pixels,
1240 pub width: Pixels,
1241 pub margin: Pixels,
1242 pub git_blame_entries_width: Option<Pixels>,
1243}
1244
1245impl GutterDimensions {
1246 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1247 Self {
1248 margin: Self::default_gutter_margin(font_id, font_size, cx),
1249 ..Default::default()
1250 }
1251 }
1252
1253 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1254 -cx.text_system().descent(font_id, font_size)
1255 }
1256 /// The full width of the space taken up by the gutter.
1257 pub fn full_width(&self) -> Pixels {
1258 self.margin + self.width
1259 }
1260
1261 /// The width of the space reserved for the fold indicators,
1262 /// use alongside 'justify_end' and `gutter_width` to
1263 /// right align content with the line numbers
1264 pub fn fold_area_width(&self) -> Pixels {
1265 self.margin + self.right_padding
1266 }
1267}
1268
1269struct CharacterDimensions {
1270 em_width: Pixels,
1271 em_advance: Pixels,
1272 line_height: Pixels,
1273}
1274
1275#[derive(Debug)]
1276pub struct RemoteSelection {
1277 pub replica_id: ReplicaId,
1278 pub selection: Selection<Anchor>,
1279 pub cursor_shape: CursorShape,
1280 pub collaborator_id: CollaboratorId,
1281 pub line_mode: bool,
1282 pub user_name: Option<SharedString>,
1283 pub color: PlayerColor,
1284}
1285
1286#[derive(Clone, Debug)]
1287struct SelectionHistoryEntry {
1288 selections: Arc<[Selection<Anchor>]>,
1289 select_next_state: Option<SelectNextState>,
1290 select_prev_state: Option<SelectNextState>,
1291 add_selections_state: Option<AddSelectionsState>,
1292}
1293
1294#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1295enum SelectionHistoryMode {
1296 Normal,
1297 Undoing,
1298 Redoing,
1299 Skipping,
1300}
1301
1302#[derive(Clone, PartialEq, Eq, Hash)]
1303struct HoveredCursor {
1304 replica_id: u16,
1305 selection_id: usize,
1306}
1307
1308impl Default for SelectionHistoryMode {
1309 fn default() -> Self {
1310 Self::Normal
1311 }
1312}
1313
1314#[derive(Debug)]
1315/// SelectionEffects controls the side-effects of updating the selection.
1316///
1317/// The default behaviour does "what you mostly want":
1318/// - it pushes to the nav history if the cursor moved by >10 lines
1319/// - it re-triggers completion requests
1320/// - it scrolls to fit
1321///
1322/// You might want to modify these behaviours. For example when doing a "jump"
1323/// like go to definition, we always want to add to nav history; but when scrolling
1324/// in vim mode we never do.
1325///
1326/// Similarly, you might want to disable scrolling if you don't want the viewport to
1327/// move.
1328#[derive(Clone)]
1329pub struct SelectionEffects {
1330 nav_history: Option<bool>,
1331 completions: bool,
1332 scroll: Option<Autoscroll>,
1333}
1334
1335impl Default for SelectionEffects {
1336 fn default() -> Self {
1337 Self {
1338 nav_history: None,
1339 completions: true,
1340 scroll: Some(Autoscroll::fit()),
1341 }
1342 }
1343}
1344impl SelectionEffects {
1345 pub fn scroll(scroll: Autoscroll) -> Self {
1346 Self {
1347 scroll: Some(scroll),
1348 ..Default::default()
1349 }
1350 }
1351
1352 pub fn no_scroll() -> Self {
1353 Self {
1354 scroll: None,
1355 ..Default::default()
1356 }
1357 }
1358
1359 pub fn completions(self, completions: bool) -> Self {
1360 Self {
1361 completions,
1362 ..self
1363 }
1364 }
1365
1366 pub fn nav_history(self, nav_history: bool) -> Self {
1367 Self {
1368 nav_history: Some(nav_history),
1369 ..self
1370 }
1371 }
1372}
1373
1374struct DeferredSelectionEffectsState {
1375 changed: bool,
1376 effects: SelectionEffects,
1377 old_cursor_position: Anchor,
1378 history_entry: SelectionHistoryEntry,
1379}
1380
1381#[derive(Default)]
1382struct SelectionHistory {
1383 #[allow(clippy::type_complexity)]
1384 selections_by_transaction:
1385 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1386 mode: SelectionHistoryMode,
1387 undo_stack: VecDeque<SelectionHistoryEntry>,
1388 redo_stack: VecDeque<SelectionHistoryEntry>,
1389}
1390
1391impl SelectionHistory {
1392 #[track_caller]
1393 fn insert_transaction(
1394 &mut self,
1395 transaction_id: TransactionId,
1396 selections: Arc<[Selection<Anchor>]>,
1397 ) {
1398 if selections.is_empty() {
1399 log::error!(
1400 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1401 std::panic::Location::caller()
1402 );
1403 return;
1404 }
1405 self.selections_by_transaction
1406 .insert(transaction_id, (selections, None));
1407 }
1408
1409 #[allow(clippy::type_complexity)]
1410 fn transaction(
1411 &self,
1412 transaction_id: TransactionId,
1413 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1414 self.selections_by_transaction.get(&transaction_id)
1415 }
1416
1417 #[allow(clippy::type_complexity)]
1418 fn transaction_mut(
1419 &mut self,
1420 transaction_id: TransactionId,
1421 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1422 self.selections_by_transaction.get_mut(&transaction_id)
1423 }
1424
1425 fn push(&mut self, entry: SelectionHistoryEntry) {
1426 if !entry.selections.is_empty() {
1427 match self.mode {
1428 SelectionHistoryMode::Normal => {
1429 self.push_undo(entry);
1430 self.redo_stack.clear();
1431 }
1432 SelectionHistoryMode::Undoing => self.push_redo(entry),
1433 SelectionHistoryMode::Redoing => self.push_undo(entry),
1434 SelectionHistoryMode::Skipping => {}
1435 }
1436 }
1437 }
1438
1439 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1440 if self
1441 .undo_stack
1442 .back()
1443 .is_none_or(|e| e.selections != entry.selections)
1444 {
1445 self.undo_stack.push_back(entry);
1446 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1447 self.undo_stack.pop_front();
1448 }
1449 }
1450 }
1451
1452 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1453 if self
1454 .redo_stack
1455 .back()
1456 .is_none_or(|e| e.selections != entry.selections)
1457 {
1458 self.redo_stack.push_back(entry);
1459 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1460 self.redo_stack.pop_front();
1461 }
1462 }
1463 }
1464}
1465
1466#[derive(Clone, Copy)]
1467pub struct RowHighlightOptions {
1468 pub autoscroll: bool,
1469 pub include_gutter: bool,
1470}
1471
1472impl Default for RowHighlightOptions {
1473 fn default() -> Self {
1474 Self {
1475 autoscroll: Default::default(),
1476 include_gutter: true,
1477 }
1478 }
1479}
1480
1481struct RowHighlight {
1482 index: usize,
1483 range: Range<Anchor>,
1484 color: Hsla,
1485 options: RowHighlightOptions,
1486 type_id: TypeId,
1487}
1488
1489#[derive(Clone, Debug)]
1490struct AddSelectionsState {
1491 groups: Vec<AddSelectionsGroup>,
1492}
1493
1494#[derive(Clone, Debug)]
1495struct AddSelectionsGroup {
1496 above: bool,
1497 stack: Vec<usize>,
1498}
1499
1500#[derive(Clone)]
1501struct SelectNextState {
1502 query: AhoCorasick,
1503 wordwise: bool,
1504 done: bool,
1505}
1506
1507impl std::fmt::Debug for SelectNextState {
1508 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1509 f.debug_struct(std::any::type_name::<Self>())
1510 .field("wordwise", &self.wordwise)
1511 .field("done", &self.done)
1512 .finish()
1513 }
1514}
1515
1516#[derive(Debug)]
1517struct AutocloseRegion {
1518 selection_id: usize,
1519 range: Range<Anchor>,
1520 pair: BracketPair,
1521}
1522
1523#[derive(Debug)]
1524struct SnippetState {
1525 ranges: Vec<Vec<Range<Anchor>>>,
1526 active_index: usize,
1527 choices: Vec<Option<Vec<String>>>,
1528}
1529
1530#[doc(hidden)]
1531pub struct RenameState {
1532 pub range: Range<Anchor>,
1533 pub old_name: Arc<str>,
1534 pub editor: Entity<Editor>,
1535 block_id: CustomBlockId,
1536}
1537
1538struct InvalidationStack<T>(Vec<T>);
1539
1540struct RegisteredEditPredictionProvider {
1541 provider: Arc<dyn EditPredictionProviderHandle>,
1542 _subscription: Subscription,
1543}
1544
1545#[derive(Debug, PartialEq, Eq)]
1546pub struct ActiveDiagnosticGroup {
1547 pub active_range: Range<Anchor>,
1548 pub active_message: String,
1549 pub group_id: usize,
1550 pub blocks: HashSet<CustomBlockId>,
1551}
1552
1553#[derive(Debug, PartialEq, Eq)]
1554
1555pub(crate) enum ActiveDiagnostic {
1556 None,
1557 All,
1558 Group(ActiveDiagnosticGroup),
1559}
1560
1561#[derive(Serialize, Deserialize, Clone, Debug)]
1562pub struct ClipboardSelection {
1563 /// The number of bytes in this selection.
1564 pub len: usize,
1565 /// Whether this was a full-line selection.
1566 pub is_entire_line: bool,
1567 /// The indentation of the first line when this content was originally copied.
1568 pub first_line_indent: u32,
1569}
1570
1571// selections, scroll behavior, was newest selection reversed
1572type SelectSyntaxNodeHistoryState = (
1573 Box<[Selection<usize>]>,
1574 SelectSyntaxNodeScrollBehavior,
1575 bool,
1576);
1577
1578#[derive(Default)]
1579struct SelectSyntaxNodeHistory {
1580 stack: Vec<SelectSyntaxNodeHistoryState>,
1581 // disable temporarily to allow changing selections without losing the stack
1582 pub disable_clearing: bool,
1583}
1584
1585impl SelectSyntaxNodeHistory {
1586 pub fn try_clear(&mut self) {
1587 if !self.disable_clearing {
1588 self.stack.clear();
1589 }
1590 }
1591
1592 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1593 self.stack.push(selection);
1594 }
1595
1596 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1597 self.stack.pop()
1598 }
1599}
1600
1601enum SelectSyntaxNodeScrollBehavior {
1602 CursorTop,
1603 FitSelection,
1604 CursorBottom,
1605}
1606
1607#[derive(Debug)]
1608pub(crate) struct NavigationData {
1609 cursor_anchor: Anchor,
1610 cursor_position: Point,
1611 scroll_anchor: ScrollAnchor,
1612 scroll_top_row: u32,
1613}
1614
1615#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1616pub enum GotoDefinitionKind {
1617 Symbol,
1618 Declaration,
1619 Type,
1620 Implementation,
1621}
1622
1623#[derive(Debug, Clone)]
1624enum InlayHintRefreshReason {
1625 ModifiersChanged(bool),
1626 Toggle(bool),
1627 SettingsChange(InlayHintSettings),
1628 NewLinesShown,
1629 BufferEdited(HashSet<Arc<Language>>),
1630 RefreshRequested,
1631 ExcerptsRemoved(Vec<ExcerptId>),
1632}
1633
1634impl InlayHintRefreshReason {
1635 fn description(&self) -> &'static str {
1636 match self {
1637 Self::ModifiersChanged(_) => "modifiers changed",
1638 Self::Toggle(_) => "toggle",
1639 Self::SettingsChange(_) => "settings change",
1640 Self::NewLinesShown => "new lines shown",
1641 Self::BufferEdited(_) => "buffer edited",
1642 Self::RefreshRequested => "refresh requested",
1643 Self::ExcerptsRemoved(_) => "excerpts removed",
1644 }
1645 }
1646}
1647
1648pub enum FormatTarget {
1649 Buffers(HashSet<Entity<Buffer>>),
1650 Ranges(Vec<Range<MultiBufferPoint>>),
1651}
1652
1653pub(crate) struct FocusedBlock {
1654 id: BlockId,
1655 focus_handle: WeakFocusHandle,
1656}
1657
1658#[derive(Clone)]
1659enum JumpData {
1660 MultiBufferRow {
1661 row: MultiBufferRow,
1662 line_offset_from_top: u32,
1663 },
1664 MultiBufferPoint {
1665 excerpt_id: ExcerptId,
1666 position: Point,
1667 anchor: text::Anchor,
1668 line_offset_from_top: u32,
1669 },
1670}
1671
1672pub enum MultibufferSelectionMode {
1673 First,
1674 All,
1675}
1676
1677#[derive(Clone, Copy, Debug, Default)]
1678pub struct RewrapOptions {
1679 pub override_language_settings: bool,
1680 pub preserve_existing_whitespace: bool,
1681}
1682
1683impl Editor {
1684 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1685 let buffer = cx.new(|cx| Buffer::local("", cx));
1686 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1687 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1688 }
1689
1690 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1691 let buffer = cx.new(|cx| Buffer::local("", cx));
1692 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1693 Self::new(EditorMode::full(), buffer, None, window, cx)
1694 }
1695
1696 pub fn auto_height(
1697 min_lines: usize,
1698 max_lines: usize,
1699 window: &mut Window,
1700 cx: &mut Context<Self>,
1701 ) -> Self {
1702 let buffer = cx.new(|cx| Buffer::local("", cx));
1703 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1704 Self::new(
1705 EditorMode::AutoHeight {
1706 min_lines,
1707 max_lines: Some(max_lines),
1708 },
1709 buffer,
1710 None,
1711 window,
1712 cx,
1713 )
1714 }
1715
1716 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1717 /// The editor grows as tall as needed to fit its content.
1718 pub fn auto_height_unbounded(
1719 min_lines: usize,
1720 window: &mut Window,
1721 cx: &mut Context<Self>,
1722 ) -> Self {
1723 let buffer = cx.new(|cx| Buffer::local("", cx));
1724 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1725 Self::new(
1726 EditorMode::AutoHeight {
1727 min_lines,
1728 max_lines: None,
1729 },
1730 buffer,
1731 None,
1732 window,
1733 cx,
1734 )
1735 }
1736
1737 pub fn for_buffer(
1738 buffer: Entity<Buffer>,
1739 project: Option<Entity<Project>>,
1740 window: &mut Window,
1741 cx: &mut Context<Self>,
1742 ) -> Self {
1743 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1744 Self::new(EditorMode::full(), buffer, project, window, cx)
1745 }
1746
1747 pub fn for_multibuffer(
1748 buffer: Entity<MultiBuffer>,
1749 project: Option<Entity<Project>>,
1750 window: &mut Window,
1751 cx: &mut Context<Self>,
1752 ) -> Self {
1753 Self::new(EditorMode::full(), buffer, project, window, cx)
1754 }
1755
1756 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1757 let mut clone = Self::new(
1758 self.mode.clone(),
1759 self.buffer.clone(),
1760 self.project.clone(),
1761 window,
1762 cx,
1763 );
1764 self.display_map.update(cx, |display_map, cx| {
1765 let snapshot = display_map.snapshot(cx);
1766 clone.display_map.update(cx, |display_map, cx| {
1767 display_map.set_state(&snapshot, cx);
1768 });
1769 });
1770 clone.folds_did_change(cx);
1771 clone.selections.clone_state(&self.selections);
1772 clone.scroll_manager.clone_state(&self.scroll_manager);
1773 clone.searchable = self.searchable;
1774 clone.read_only = self.read_only;
1775 clone
1776 }
1777
1778 pub fn new(
1779 mode: EditorMode,
1780 buffer: Entity<MultiBuffer>,
1781 project: Option<Entity<Project>>,
1782 window: &mut Window,
1783 cx: &mut Context<Self>,
1784 ) -> Self {
1785 Editor::new_internal(mode, buffer, project, None, window, cx)
1786 }
1787
1788 fn new_internal(
1789 mode: EditorMode,
1790 multi_buffer: Entity<MultiBuffer>,
1791 project: Option<Entity<Project>>,
1792 display_map: Option<Entity<DisplayMap>>,
1793 window: &mut Window,
1794 cx: &mut Context<Self>,
1795 ) -> Self {
1796 debug_assert!(
1797 display_map.is_none() || mode.is_minimap(),
1798 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1799 );
1800
1801 let full_mode = mode.is_full();
1802 let is_minimap = mode.is_minimap();
1803 let diagnostics_max_severity = if full_mode {
1804 EditorSettings::get_global(cx)
1805 .diagnostics_max_severity
1806 .unwrap_or(DiagnosticSeverity::Hint)
1807 } else {
1808 DiagnosticSeverity::Off
1809 };
1810 let style = window.text_style();
1811 let font_size = style.font_size.to_pixels(window.rem_size());
1812 let editor = cx.entity().downgrade();
1813 let fold_placeholder = FoldPlaceholder {
1814 constrain_width: false,
1815 render: Arc::new(move |fold_id, fold_range, cx| {
1816 let editor = editor.clone();
1817 div()
1818 .id(fold_id)
1819 .bg(cx.theme().colors().ghost_element_background)
1820 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1821 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1822 .rounded_xs()
1823 .size_full()
1824 .cursor_pointer()
1825 .child("⋯")
1826 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1827 .on_click(move |_, _window, cx| {
1828 editor
1829 .update(cx, |editor, cx| {
1830 editor.unfold_ranges(
1831 &[fold_range.start..fold_range.end],
1832 true,
1833 false,
1834 cx,
1835 );
1836 cx.stop_propagation();
1837 })
1838 .ok();
1839 })
1840 .into_any()
1841 }),
1842 merge_adjacent: true,
1843 ..FoldPlaceholder::default()
1844 };
1845 let display_map = display_map.unwrap_or_else(|| {
1846 cx.new(|cx| {
1847 DisplayMap::new(
1848 multi_buffer.clone(),
1849 style.font(),
1850 font_size,
1851 None,
1852 FILE_HEADER_HEIGHT,
1853 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1854 fold_placeholder,
1855 diagnostics_max_severity,
1856 cx,
1857 )
1858 })
1859 });
1860
1861 let selections = SelectionsCollection::new(display_map.clone(), multi_buffer.clone());
1862
1863 let blink_manager = cx.new(|cx| {
1864 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1865 if is_minimap {
1866 blink_manager.disable(cx);
1867 }
1868 blink_manager
1869 });
1870
1871 let soft_wrap_mode_override =
1872 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1873
1874 let mut project_subscriptions = Vec::new();
1875 if full_mode && let Some(project) = project.as_ref() {
1876 project_subscriptions.push(cx.subscribe_in(
1877 project,
1878 window,
1879 |editor, _, event, window, cx| match event {
1880 project::Event::RefreshCodeLens => {
1881 // we always query lens with actions, without storing them, always refreshing them
1882 }
1883 project::Event::RefreshInlayHints => {
1884 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1885 }
1886 project::Event::LanguageServerRemoved(..) => {
1887 if editor.tasks_update_task.is_none() {
1888 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1889 }
1890 editor.registered_buffers.clear();
1891 editor.register_visible_buffers(cx);
1892 }
1893 project::Event::LanguageServerAdded(..) => {
1894 if editor.tasks_update_task.is_none() {
1895 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1896 }
1897 }
1898 project::Event::SnippetEdit(id, snippet_edits) => {
1899 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1900 let focus_handle = editor.focus_handle(cx);
1901 if focus_handle.is_focused(window) {
1902 let snapshot = buffer.read(cx).snapshot();
1903 for (range, snippet) in snippet_edits {
1904 let editor_range =
1905 language::range_from_lsp(*range).to_offset(&snapshot);
1906 editor
1907 .insert_snippet(
1908 &[editor_range],
1909 snippet.clone(),
1910 window,
1911 cx,
1912 )
1913 .ok();
1914 }
1915 }
1916 }
1917 }
1918 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1919 let buffer_id = *buffer_id;
1920 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
1921 let registered = editor.register_buffer(buffer_id, cx);
1922 if registered {
1923 editor.update_lsp_data(Some(buffer_id), window, cx);
1924 editor.refresh_inlay_hints(
1925 InlayHintRefreshReason::RefreshRequested,
1926 cx,
1927 );
1928 refresh_linked_ranges(editor, window, cx);
1929 editor.refresh_code_actions(window, cx);
1930 editor.refresh_document_highlights(cx);
1931 }
1932 }
1933 }
1934
1935 project::Event::EntryRenamed(transaction) => {
1936 let Some(workspace) = editor.workspace() else {
1937 return;
1938 };
1939 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1940 else {
1941 return;
1942 };
1943 if active_editor.entity_id() == cx.entity_id() {
1944 let edited_buffers_already_open = {
1945 let other_editors: Vec<Entity<Editor>> = workspace
1946 .read(cx)
1947 .panes()
1948 .iter()
1949 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1950 .filter(|editor| editor.entity_id() != cx.entity_id())
1951 .collect();
1952
1953 transaction.0.keys().all(|buffer| {
1954 other_editors.iter().any(|editor| {
1955 let multi_buffer = editor.read(cx).buffer();
1956 multi_buffer.read(cx).is_singleton()
1957 && multi_buffer.read(cx).as_singleton().map_or(
1958 false,
1959 |singleton| {
1960 singleton.entity_id() == buffer.entity_id()
1961 },
1962 )
1963 })
1964 })
1965 };
1966
1967 if !edited_buffers_already_open {
1968 let workspace = workspace.downgrade();
1969 let transaction = transaction.clone();
1970 cx.defer_in(window, move |_, window, cx| {
1971 cx.spawn_in(window, async move |editor, cx| {
1972 Self::open_project_transaction(
1973 &editor,
1974 workspace,
1975 transaction,
1976 "Rename".to_string(),
1977 cx,
1978 )
1979 .await
1980 .ok()
1981 })
1982 .detach();
1983 });
1984 }
1985 }
1986 }
1987
1988 _ => {}
1989 },
1990 ));
1991 if let Some(task_inventory) = project
1992 .read(cx)
1993 .task_store()
1994 .read(cx)
1995 .task_inventory()
1996 .cloned()
1997 {
1998 project_subscriptions.push(cx.observe_in(
1999 &task_inventory,
2000 window,
2001 |editor, _, window, cx| {
2002 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2003 },
2004 ));
2005 };
2006
2007 project_subscriptions.push(cx.subscribe_in(
2008 &project.read(cx).breakpoint_store(),
2009 window,
2010 |editor, _, event, window, cx| match event {
2011 BreakpointStoreEvent::ClearDebugLines => {
2012 editor.clear_row_highlights::<ActiveDebugLine>();
2013 editor.refresh_inline_values(cx);
2014 }
2015 BreakpointStoreEvent::SetDebugLine => {
2016 if editor.go_to_active_debug_line(window, cx) {
2017 cx.stop_propagation();
2018 }
2019
2020 editor.refresh_inline_values(cx);
2021 }
2022 _ => {}
2023 },
2024 ));
2025 let git_store = project.read(cx).git_store().clone();
2026 let project = project.clone();
2027 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2028 if let GitStoreEvent::RepositoryUpdated(
2029 _,
2030 RepositoryEvent::Updated {
2031 new_instance: true, ..
2032 },
2033 _,
2034 ) = event
2035 {
2036 this.load_diff_task = Some(
2037 update_uncommitted_diff_for_buffer(
2038 cx.entity(),
2039 &project,
2040 this.buffer.read(cx).all_buffers(),
2041 this.buffer.clone(),
2042 cx,
2043 )
2044 .shared(),
2045 );
2046 }
2047 }));
2048 }
2049
2050 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2051
2052 let inlay_hint_settings =
2053 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2054 let focus_handle = cx.focus_handle();
2055 if !is_minimap {
2056 cx.on_focus(&focus_handle, window, Self::handle_focus)
2057 .detach();
2058 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2059 .detach();
2060 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2061 .detach();
2062 cx.on_blur(&focus_handle, window, Self::handle_blur)
2063 .detach();
2064 cx.observe_pending_input(window, Self::observe_pending_input)
2065 .detach();
2066 }
2067
2068 let show_indent_guides =
2069 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2070 Some(false)
2071 } else {
2072 None
2073 };
2074
2075 let breakpoint_store = match (&mode, project.as_ref()) {
2076 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2077 _ => None,
2078 };
2079
2080 let mut code_action_providers = Vec::new();
2081 let mut load_uncommitted_diff = None;
2082 if let Some(project) = project.clone() {
2083 load_uncommitted_diff = Some(
2084 update_uncommitted_diff_for_buffer(
2085 cx.entity(),
2086 &project,
2087 multi_buffer.read(cx).all_buffers(),
2088 multi_buffer.clone(),
2089 cx,
2090 )
2091 .shared(),
2092 );
2093 code_action_providers.push(Rc::new(project) as Rc<_>);
2094 }
2095
2096 let mut editor = Self {
2097 focus_handle,
2098 show_cursor_when_unfocused: false,
2099 last_focused_descendant: None,
2100 buffer: multi_buffer.clone(),
2101 display_map: display_map.clone(),
2102 placeholder_display_map: None,
2103 selections,
2104 scroll_manager: ScrollManager::new(cx),
2105 columnar_selection_state: None,
2106 add_selections_state: None,
2107 select_next_state: None,
2108 select_prev_state: None,
2109 selection_history: SelectionHistory::default(),
2110 defer_selection_effects: false,
2111 deferred_selection_effects_state: None,
2112 autoclose_regions: Vec::new(),
2113 snippet_stack: InvalidationStack::default(),
2114 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2115 ime_transaction: None,
2116 active_diagnostics: ActiveDiagnostic::None,
2117 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2118 inline_diagnostics_update: Task::ready(()),
2119 inline_diagnostics: Vec::new(),
2120 soft_wrap_mode_override,
2121 diagnostics_max_severity,
2122 hard_wrap: None,
2123 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2124 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2125 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2126 project,
2127 blink_manager: blink_manager.clone(),
2128 show_local_selections: true,
2129 show_scrollbars: ScrollbarAxes {
2130 horizontal: full_mode,
2131 vertical: full_mode,
2132 },
2133 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2134 offset_content: !matches!(mode, EditorMode::SingleLine),
2135 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2136 show_gutter: full_mode,
2137 show_line_numbers: (!full_mode).then_some(false),
2138 use_relative_line_numbers: None,
2139 disable_expand_excerpt_buttons: !full_mode,
2140 show_git_diff_gutter: None,
2141 show_code_actions: None,
2142 show_runnables: None,
2143 show_breakpoints: None,
2144 show_wrap_guides: None,
2145 show_indent_guides,
2146 highlight_order: 0,
2147 highlighted_rows: HashMap::default(),
2148 background_highlights: HashMap::default(),
2149 gutter_highlights: HashMap::default(),
2150 scrollbar_marker_state: ScrollbarMarkerState::default(),
2151 active_indent_guides_state: ActiveIndentGuidesState::default(),
2152 nav_history: None,
2153 context_menu: RefCell::new(None),
2154 context_menu_options: None,
2155 mouse_context_menu: None,
2156 completion_tasks: Vec::new(),
2157 inline_blame_popover: None,
2158 inline_blame_popover_show_task: None,
2159 signature_help_state: SignatureHelpState::default(),
2160 auto_signature_help: None,
2161 find_all_references_task_sources: Vec::new(),
2162 next_completion_id: 0,
2163 next_inlay_id: 0,
2164 code_action_providers,
2165 available_code_actions: None,
2166 code_actions_task: None,
2167 quick_selection_highlight_task: None,
2168 debounced_selection_highlight_task: None,
2169 document_highlights_task: None,
2170 linked_editing_range_task: None,
2171 pending_rename: None,
2172 searchable: !is_minimap,
2173 cursor_shape: EditorSettings::get_global(cx)
2174 .cursor_shape
2175 .unwrap_or_default(),
2176 current_line_highlight: None,
2177 autoindent_mode: Some(AutoindentMode::EachLine),
2178 collapse_matches: false,
2179 workspace: None,
2180 input_enabled: !is_minimap,
2181 use_modal_editing: full_mode,
2182 read_only: is_minimap,
2183 use_autoclose: true,
2184 use_auto_surround: true,
2185 auto_replace_emoji_shortcode: false,
2186 jsx_tag_auto_close_enabled_in_any_buffer: false,
2187 leader_id: None,
2188 remote_id: None,
2189 hover_state: HoverState::default(),
2190 pending_mouse_down: None,
2191 hovered_link_state: None,
2192 edit_prediction_provider: None,
2193 active_edit_prediction: None,
2194 stale_edit_prediction_in_menu: None,
2195 edit_prediction_preview: EditPredictionPreview::Inactive {
2196 released_too_fast: false,
2197 },
2198 inline_diagnostics_enabled: full_mode,
2199 diagnostics_enabled: full_mode,
2200 word_completions_enabled: full_mode,
2201 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2202 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2203 gutter_hovered: false,
2204 pixel_position_of_newest_cursor: None,
2205 last_bounds: None,
2206 last_position_map: None,
2207 expect_bounds_change: None,
2208 gutter_dimensions: GutterDimensions::default(),
2209 style: None,
2210 show_cursor_names: false,
2211 hovered_cursors: HashMap::default(),
2212 next_editor_action_id: EditorActionId::default(),
2213 editor_actions: Rc::default(),
2214 edit_predictions_hidden_for_vim_mode: false,
2215 show_edit_predictions_override: None,
2216 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2217 edit_prediction_settings: EditPredictionSettings::Disabled,
2218 edit_prediction_indent_conflict: false,
2219 edit_prediction_requires_modifier_in_indent_conflict: true,
2220 custom_context_menu: None,
2221 show_git_blame_gutter: false,
2222 show_git_blame_inline: false,
2223 show_selection_menu: None,
2224 show_git_blame_inline_delay_task: None,
2225 git_blame_inline_enabled: full_mode
2226 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2227 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2228 serialize_dirty_buffers: !is_minimap
2229 && ProjectSettings::get_global(cx)
2230 .session
2231 .restore_unsaved_buffers,
2232 blame: None,
2233 blame_subscription: None,
2234 tasks: BTreeMap::default(),
2235
2236 breakpoint_store,
2237 gutter_breakpoint_indicator: (None, None),
2238 hovered_diff_hunk_row: None,
2239 _subscriptions: (!is_minimap)
2240 .then(|| {
2241 vec![
2242 cx.observe(&multi_buffer, Self::on_buffer_changed),
2243 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2244 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2245 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2246 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2247 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2248 cx.observe_window_activation(window, |editor, window, cx| {
2249 let active = window.is_window_active();
2250 editor.blink_manager.update(cx, |blink_manager, cx| {
2251 if active {
2252 blink_manager.enable(cx);
2253 } else {
2254 blink_manager.disable(cx);
2255 }
2256 });
2257 if active {
2258 editor.show_mouse_cursor(cx);
2259 }
2260 }),
2261 ]
2262 })
2263 .unwrap_or_default(),
2264 tasks_update_task: None,
2265 pull_diagnostics_task: Task::ready(()),
2266 colors: None,
2267 refresh_colors_task: Task::ready(()),
2268 next_color_inlay_id: 0,
2269 post_scroll_update: Task::ready(()),
2270 linked_edit_ranges: Default::default(),
2271 in_project_search: false,
2272 previous_search_ranges: None,
2273 breadcrumb_header: None,
2274 focused_block: None,
2275 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2276 addons: HashMap::default(),
2277 registered_buffers: HashMap::default(),
2278 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2279 selection_mark_mode: false,
2280 toggle_fold_multiple_buffers: Task::ready(()),
2281 serialize_selections: Task::ready(()),
2282 serialize_folds: Task::ready(()),
2283 text_style_refinement: None,
2284 load_diff_task: load_uncommitted_diff,
2285 temporary_diff_override: false,
2286 mouse_cursor_hidden: false,
2287 minimap: None,
2288 hide_mouse_mode: EditorSettings::get_global(cx)
2289 .hide_mouse
2290 .unwrap_or_default(),
2291 change_list: ChangeList::new(),
2292 mode,
2293 selection_drag_state: SelectionDragState::None,
2294 folding_newlines: Task::ready(()),
2295 lookup_key: None,
2296 };
2297
2298 if is_minimap {
2299 return editor;
2300 }
2301
2302 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2303 editor
2304 ._subscriptions
2305 .push(cx.observe(breakpoints, |_, _, cx| {
2306 cx.notify();
2307 }));
2308 }
2309 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2310 editor._subscriptions.extend(project_subscriptions);
2311
2312 editor._subscriptions.push(cx.subscribe_in(
2313 &cx.entity(),
2314 window,
2315 |editor, _, e: &EditorEvent, window, cx| match e {
2316 EditorEvent::ScrollPositionChanged { local, .. } => {
2317 if *local {
2318 let new_anchor = editor.scroll_manager.anchor();
2319 let snapshot = editor.snapshot(window, cx);
2320 editor.update_restoration_data(cx, move |data| {
2321 data.scroll_position = (
2322 new_anchor.top_row(snapshot.buffer_snapshot()),
2323 new_anchor.offset,
2324 );
2325 });
2326 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2327 editor.inline_blame_popover.take();
2328 }
2329 }
2330 EditorEvent::Edited { .. } => {
2331 if !vim_enabled(cx) {
2332 let display_map = editor.display_snapshot(cx);
2333 let selections = editor.selections.all_adjusted_display(&display_map);
2334 let pop_state = editor
2335 .change_list
2336 .last()
2337 .map(|previous| {
2338 previous.len() == selections.len()
2339 && previous.iter().enumerate().all(|(ix, p)| {
2340 p.to_display_point(&display_map).row()
2341 == selections[ix].head().row()
2342 })
2343 })
2344 .unwrap_or(false);
2345 let new_positions = selections
2346 .into_iter()
2347 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2348 .collect();
2349 editor
2350 .change_list
2351 .push_to_change_list(pop_state, new_positions);
2352 }
2353 }
2354 _ => (),
2355 },
2356 ));
2357
2358 if let Some(dap_store) = editor
2359 .project
2360 .as_ref()
2361 .map(|project| project.read(cx).dap_store())
2362 {
2363 let weak_editor = cx.weak_entity();
2364
2365 editor
2366 ._subscriptions
2367 .push(
2368 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2369 let session_entity = cx.entity();
2370 weak_editor
2371 .update(cx, |editor, cx| {
2372 editor._subscriptions.push(
2373 cx.subscribe(&session_entity, Self::on_debug_session_event),
2374 );
2375 })
2376 .ok();
2377 }),
2378 );
2379
2380 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2381 editor
2382 ._subscriptions
2383 .push(cx.subscribe(&session, Self::on_debug_session_event));
2384 }
2385 }
2386
2387 // skip adding the initial selection to selection history
2388 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2389 editor.end_selection(window, cx);
2390 editor.selection_history.mode = SelectionHistoryMode::Normal;
2391
2392 editor.scroll_manager.show_scrollbars(window, cx);
2393 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2394
2395 if full_mode {
2396 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2397 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2398
2399 if editor.git_blame_inline_enabled {
2400 editor.start_git_blame_inline(false, window, cx);
2401 }
2402
2403 editor.go_to_active_debug_line(window, cx);
2404
2405 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2406 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2407 }
2408
2409 editor.minimap =
2410 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2411 editor.colors = Some(LspColorData::new(cx));
2412 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2413 }
2414
2415 editor
2416 }
2417
2418 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2419 self.selections.display_map(cx)
2420 }
2421
2422 pub fn deploy_mouse_context_menu(
2423 &mut self,
2424 position: gpui::Point<Pixels>,
2425 context_menu: Entity<ContextMenu>,
2426 window: &mut Window,
2427 cx: &mut Context<Self>,
2428 ) {
2429 self.mouse_context_menu = Some(MouseContextMenu::new(
2430 self,
2431 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2432 context_menu,
2433 window,
2434 cx,
2435 ));
2436 }
2437
2438 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2439 self.mouse_context_menu
2440 .as_ref()
2441 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2442 }
2443
2444 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2445 if self
2446 .selections
2447 .pending_anchor()
2448 .is_some_and(|pending_selection| {
2449 let snapshot = self.buffer().read(cx).snapshot(cx);
2450 pending_selection.range().includes(range, &snapshot)
2451 })
2452 {
2453 return true;
2454 }
2455
2456 self.selections
2457 .disjoint_in_range::<usize>(range.clone(), &self.display_snapshot(cx))
2458 .into_iter()
2459 .any(|selection| {
2460 // This is needed to cover a corner case, if we just check for an existing
2461 // selection in the fold range, having a cursor at the start of the fold
2462 // marks it as selected. Non-empty selections don't cause this.
2463 let length = selection.end - selection.start;
2464 length > 0
2465 })
2466 }
2467
2468 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2469 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2470 }
2471
2472 fn key_context_internal(
2473 &self,
2474 has_active_edit_prediction: bool,
2475 window: &Window,
2476 cx: &App,
2477 ) -> KeyContext {
2478 let mut key_context = KeyContext::new_with_defaults();
2479 key_context.add("Editor");
2480 let mode = match self.mode {
2481 EditorMode::SingleLine => "single_line",
2482 EditorMode::AutoHeight { .. } => "auto_height",
2483 EditorMode::Minimap { .. } => "minimap",
2484 EditorMode::Full { .. } => "full",
2485 };
2486
2487 if EditorSettings::jupyter_enabled(cx) {
2488 key_context.add("jupyter");
2489 }
2490
2491 key_context.set("mode", mode);
2492 if self.pending_rename.is_some() {
2493 key_context.add("renaming");
2494 }
2495
2496 match self.context_menu.borrow().as_ref() {
2497 Some(CodeContextMenu::Completions(menu)) => {
2498 if menu.visible() {
2499 key_context.add("menu");
2500 key_context.add("showing_completions");
2501 }
2502 }
2503 Some(CodeContextMenu::CodeActions(menu)) => {
2504 if menu.visible() {
2505 key_context.add("menu");
2506 key_context.add("showing_code_actions")
2507 }
2508 }
2509 None => {}
2510 }
2511
2512 if self.signature_help_state.has_multiple_signatures() {
2513 key_context.add("showing_signature_help");
2514 }
2515
2516 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2517 if !self.focus_handle(cx).contains_focused(window, cx)
2518 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2519 {
2520 for addon in self.addons.values() {
2521 addon.extend_key_context(&mut key_context, cx)
2522 }
2523 }
2524
2525 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2526 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2527 Some(
2528 file.full_path(cx)
2529 .extension()?
2530 .to_string_lossy()
2531 .into_owned(),
2532 )
2533 }) {
2534 key_context.set("extension", extension);
2535 }
2536 } else {
2537 key_context.add("multibuffer");
2538 }
2539
2540 if has_active_edit_prediction {
2541 if self.edit_prediction_in_conflict() {
2542 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2543 } else {
2544 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2545 key_context.add("copilot_suggestion");
2546 }
2547 }
2548
2549 if self.selection_mark_mode {
2550 key_context.add("selection_mode");
2551 }
2552
2553 key_context
2554 }
2555
2556 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2557 self.last_bounds.as_ref()
2558 }
2559
2560 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2561 if self.mouse_cursor_hidden {
2562 self.mouse_cursor_hidden = false;
2563 cx.notify();
2564 }
2565 }
2566
2567 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2568 let hide_mouse_cursor = match origin {
2569 HideMouseCursorOrigin::TypingAction => {
2570 matches!(
2571 self.hide_mouse_mode,
2572 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2573 )
2574 }
2575 HideMouseCursorOrigin::MovementAction => {
2576 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2577 }
2578 };
2579 if self.mouse_cursor_hidden != hide_mouse_cursor {
2580 self.mouse_cursor_hidden = hide_mouse_cursor;
2581 cx.notify();
2582 }
2583 }
2584
2585 pub fn edit_prediction_in_conflict(&self) -> bool {
2586 if !self.show_edit_predictions_in_menu() {
2587 return false;
2588 }
2589
2590 let showing_completions = self
2591 .context_menu
2592 .borrow()
2593 .as_ref()
2594 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2595
2596 showing_completions
2597 || self.edit_prediction_requires_modifier()
2598 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2599 // bindings to insert tab characters.
2600 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2601 }
2602
2603 pub fn accept_edit_prediction_keybind(
2604 &self,
2605 accept_partial: bool,
2606 window: &Window,
2607 cx: &App,
2608 ) -> AcceptEditPredictionBinding {
2609 let key_context = self.key_context_internal(true, window, cx);
2610 let in_conflict = self.edit_prediction_in_conflict();
2611
2612 let bindings = if accept_partial {
2613 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2614 } else {
2615 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2616 };
2617
2618 // TODO: if the binding contains multiple keystrokes, display all of them, not
2619 // just the first one.
2620 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2621 !in_conflict
2622 || binding
2623 .keystrokes()
2624 .first()
2625 .is_some_and(|keystroke| keystroke.modifiers().modified())
2626 }))
2627 }
2628
2629 pub fn new_file(
2630 workspace: &mut Workspace,
2631 _: &workspace::NewFile,
2632 window: &mut Window,
2633 cx: &mut Context<Workspace>,
2634 ) {
2635 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2636 "Failed to create buffer",
2637 window,
2638 cx,
2639 |e, _, _| match e.error_code() {
2640 ErrorCode::RemoteUpgradeRequired => Some(format!(
2641 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2642 e.error_tag("required").unwrap_or("the latest version")
2643 )),
2644 _ => None,
2645 },
2646 );
2647 }
2648
2649 pub fn new_in_workspace(
2650 workspace: &mut Workspace,
2651 window: &mut Window,
2652 cx: &mut Context<Workspace>,
2653 ) -> Task<Result<Entity<Editor>>> {
2654 let project = workspace.project().clone();
2655 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2656
2657 cx.spawn_in(window, async move |workspace, cx| {
2658 let buffer = create.await?;
2659 workspace.update_in(cx, |workspace, window, cx| {
2660 let editor =
2661 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2662 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2663 editor
2664 })
2665 })
2666 }
2667
2668 fn new_file_vertical(
2669 workspace: &mut Workspace,
2670 _: &workspace::NewFileSplitVertical,
2671 window: &mut Window,
2672 cx: &mut Context<Workspace>,
2673 ) {
2674 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2675 }
2676
2677 fn new_file_horizontal(
2678 workspace: &mut Workspace,
2679 _: &workspace::NewFileSplitHorizontal,
2680 window: &mut Window,
2681 cx: &mut Context<Workspace>,
2682 ) {
2683 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2684 }
2685
2686 fn new_file_in_direction(
2687 workspace: &mut Workspace,
2688 direction: SplitDirection,
2689 window: &mut Window,
2690 cx: &mut Context<Workspace>,
2691 ) {
2692 let project = workspace.project().clone();
2693 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2694
2695 cx.spawn_in(window, async move |workspace, cx| {
2696 let buffer = create.await?;
2697 workspace.update_in(cx, move |workspace, window, cx| {
2698 workspace.split_item(
2699 direction,
2700 Box::new(
2701 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2702 ),
2703 window,
2704 cx,
2705 )
2706 })?;
2707 anyhow::Ok(())
2708 })
2709 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2710 match e.error_code() {
2711 ErrorCode::RemoteUpgradeRequired => Some(format!(
2712 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2713 e.error_tag("required").unwrap_or("the latest version")
2714 )),
2715 _ => None,
2716 }
2717 });
2718 }
2719
2720 pub fn leader_id(&self) -> Option<CollaboratorId> {
2721 self.leader_id
2722 }
2723
2724 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2725 &self.buffer
2726 }
2727
2728 pub fn project(&self) -> Option<&Entity<Project>> {
2729 self.project.as_ref()
2730 }
2731
2732 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2733 self.workspace.as_ref()?.0.upgrade()
2734 }
2735
2736 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2737 self.buffer().read(cx).title(cx)
2738 }
2739
2740 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2741 let git_blame_gutter_max_author_length = self
2742 .render_git_blame_gutter(cx)
2743 .then(|| {
2744 if let Some(blame) = self.blame.as_ref() {
2745 let max_author_length =
2746 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2747 Some(max_author_length)
2748 } else {
2749 None
2750 }
2751 })
2752 .flatten();
2753
2754 EditorSnapshot {
2755 mode: self.mode.clone(),
2756 show_gutter: self.show_gutter,
2757 show_line_numbers: self.show_line_numbers,
2758 show_git_diff_gutter: self.show_git_diff_gutter,
2759 show_code_actions: self.show_code_actions,
2760 show_runnables: self.show_runnables,
2761 show_breakpoints: self.show_breakpoints,
2762 git_blame_gutter_max_author_length,
2763 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2764 placeholder_display_snapshot: self
2765 .placeholder_display_map
2766 .as_ref()
2767 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2768 scroll_anchor: self.scroll_manager.anchor(),
2769 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2770 is_focused: self.focus_handle.is_focused(window),
2771 current_line_highlight: self
2772 .current_line_highlight
2773 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2774 gutter_hovered: self.gutter_hovered,
2775 }
2776 }
2777
2778 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2779 self.buffer.read(cx).language_at(point, cx)
2780 }
2781
2782 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2783 self.buffer.read(cx).read(cx).file_at(point).cloned()
2784 }
2785
2786 pub fn active_excerpt(
2787 &self,
2788 cx: &App,
2789 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2790 self.buffer
2791 .read(cx)
2792 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2793 }
2794
2795 pub fn mode(&self) -> &EditorMode {
2796 &self.mode
2797 }
2798
2799 pub fn set_mode(&mut self, mode: EditorMode) {
2800 self.mode = mode;
2801 }
2802
2803 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2804 self.collaboration_hub.as_deref()
2805 }
2806
2807 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2808 self.collaboration_hub = Some(hub);
2809 }
2810
2811 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2812 self.in_project_search = in_project_search;
2813 }
2814
2815 pub fn set_custom_context_menu(
2816 &mut self,
2817 f: impl 'static
2818 + Fn(
2819 &mut Self,
2820 DisplayPoint,
2821 &mut Window,
2822 &mut Context<Self>,
2823 ) -> Option<Entity<ui::ContextMenu>>,
2824 ) {
2825 self.custom_context_menu = Some(Box::new(f))
2826 }
2827
2828 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2829 self.completion_provider = provider;
2830 }
2831
2832 #[cfg(any(test, feature = "test-support"))]
2833 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2834 self.completion_provider.clone()
2835 }
2836
2837 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2838 self.semantics_provider.clone()
2839 }
2840
2841 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2842 self.semantics_provider = provider;
2843 }
2844
2845 pub fn set_edit_prediction_provider<T>(
2846 &mut self,
2847 provider: Option<Entity<T>>,
2848 window: &mut Window,
2849 cx: &mut Context<Self>,
2850 ) where
2851 T: EditPredictionProvider,
2852 {
2853 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2854 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2855 if this.focus_handle.is_focused(window) {
2856 this.update_visible_edit_prediction(window, cx);
2857 }
2858 }),
2859 provider: Arc::new(provider),
2860 });
2861 self.update_edit_prediction_settings(cx);
2862 self.refresh_edit_prediction(false, false, window, cx);
2863 }
2864
2865 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2866 self.placeholder_display_map
2867 .as_ref()
2868 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2869 }
2870
2871 pub fn set_placeholder_text(
2872 &mut self,
2873 placeholder_text: &str,
2874 window: &mut Window,
2875 cx: &mut Context<Self>,
2876 ) {
2877 let multibuffer = cx
2878 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2879
2880 let style = window.text_style();
2881
2882 self.placeholder_display_map = Some(cx.new(|cx| {
2883 DisplayMap::new(
2884 multibuffer,
2885 style.font(),
2886 style.font_size.to_pixels(window.rem_size()),
2887 None,
2888 FILE_HEADER_HEIGHT,
2889 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2890 Default::default(),
2891 DiagnosticSeverity::Off,
2892 cx,
2893 )
2894 }));
2895 cx.notify();
2896 }
2897
2898 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2899 self.cursor_shape = cursor_shape;
2900
2901 // Disrupt blink for immediate user feedback that the cursor shape has changed
2902 self.blink_manager.update(cx, BlinkManager::show_cursor);
2903
2904 cx.notify();
2905 }
2906
2907 pub fn set_current_line_highlight(
2908 &mut self,
2909 current_line_highlight: Option<CurrentLineHighlight>,
2910 ) {
2911 self.current_line_highlight = current_line_highlight;
2912 }
2913
2914 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2915 self.collapse_matches = collapse_matches;
2916 }
2917
2918 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2919 if self.collapse_matches {
2920 return range.start..range.start;
2921 }
2922 range.clone()
2923 }
2924
2925 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2926 if self.display_map.read(cx).clip_at_line_ends != clip {
2927 self.display_map
2928 .update(cx, |map, _| map.clip_at_line_ends = clip);
2929 }
2930 }
2931
2932 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2933 self.input_enabled = input_enabled;
2934 }
2935
2936 pub fn set_edit_predictions_hidden_for_vim_mode(
2937 &mut self,
2938 hidden: bool,
2939 window: &mut Window,
2940 cx: &mut Context<Self>,
2941 ) {
2942 if hidden != self.edit_predictions_hidden_for_vim_mode {
2943 self.edit_predictions_hidden_for_vim_mode = hidden;
2944 if hidden {
2945 self.update_visible_edit_prediction(window, cx);
2946 } else {
2947 self.refresh_edit_prediction(true, false, window, cx);
2948 }
2949 }
2950 }
2951
2952 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2953 self.menu_edit_predictions_policy = value;
2954 }
2955
2956 pub fn set_autoindent(&mut self, autoindent: bool) {
2957 if autoindent {
2958 self.autoindent_mode = Some(AutoindentMode::EachLine);
2959 } else {
2960 self.autoindent_mode = None;
2961 }
2962 }
2963
2964 pub fn read_only(&self, cx: &App) -> bool {
2965 self.read_only || self.buffer.read(cx).read_only()
2966 }
2967
2968 pub fn set_read_only(&mut self, read_only: bool) {
2969 self.read_only = read_only;
2970 }
2971
2972 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2973 self.use_autoclose = autoclose;
2974 }
2975
2976 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2977 self.use_auto_surround = auto_surround;
2978 }
2979
2980 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2981 self.auto_replace_emoji_shortcode = auto_replace;
2982 }
2983
2984 pub fn toggle_edit_predictions(
2985 &mut self,
2986 _: &ToggleEditPrediction,
2987 window: &mut Window,
2988 cx: &mut Context<Self>,
2989 ) {
2990 if self.show_edit_predictions_override.is_some() {
2991 self.set_show_edit_predictions(None, window, cx);
2992 } else {
2993 let show_edit_predictions = !self.edit_predictions_enabled();
2994 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2995 }
2996 }
2997
2998 pub fn set_show_edit_predictions(
2999 &mut self,
3000 show_edit_predictions: Option<bool>,
3001 window: &mut Window,
3002 cx: &mut Context<Self>,
3003 ) {
3004 self.show_edit_predictions_override = show_edit_predictions;
3005 self.update_edit_prediction_settings(cx);
3006
3007 if let Some(false) = show_edit_predictions {
3008 self.discard_edit_prediction(false, cx);
3009 } else {
3010 self.refresh_edit_prediction(false, true, window, cx);
3011 }
3012 }
3013
3014 fn edit_predictions_disabled_in_scope(
3015 &self,
3016 buffer: &Entity<Buffer>,
3017 buffer_position: language::Anchor,
3018 cx: &App,
3019 ) -> bool {
3020 let snapshot = buffer.read(cx).snapshot();
3021 let settings = snapshot.settings_at(buffer_position, cx);
3022
3023 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3024 return false;
3025 };
3026
3027 scope.override_name().is_some_and(|scope_name| {
3028 settings
3029 .edit_predictions_disabled_in
3030 .iter()
3031 .any(|s| s == scope_name)
3032 })
3033 }
3034
3035 pub fn set_use_modal_editing(&mut self, to: bool) {
3036 self.use_modal_editing = to;
3037 }
3038
3039 pub fn use_modal_editing(&self) -> bool {
3040 self.use_modal_editing
3041 }
3042
3043 fn selections_did_change(
3044 &mut self,
3045 local: bool,
3046 old_cursor_position: &Anchor,
3047 effects: SelectionEffects,
3048 window: &mut Window,
3049 cx: &mut Context<Self>,
3050 ) {
3051 window.invalidate_character_coordinates();
3052
3053 // Copy selections to primary selection buffer
3054 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3055 if local {
3056 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3057 let buffer_handle = self.buffer.read(cx).read(cx);
3058
3059 let mut text = String::new();
3060 for (index, selection) in selections.iter().enumerate() {
3061 let text_for_selection = buffer_handle
3062 .text_for_range(selection.start..selection.end)
3063 .collect::<String>();
3064
3065 text.push_str(&text_for_selection);
3066 if index != selections.len() - 1 {
3067 text.push('\n');
3068 }
3069 }
3070
3071 if !text.is_empty() {
3072 cx.write_to_primary(ClipboardItem::new_string(text));
3073 }
3074 }
3075
3076 let selection_anchors = self.selections.disjoint_anchors_arc();
3077
3078 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3079 self.buffer.update(cx, |buffer, cx| {
3080 buffer.set_active_selections(
3081 &selection_anchors,
3082 self.selections.line_mode(),
3083 self.cursor_shape,
3084 cx,
3085 )
3086 });
3087 }
3088 let display_map = self
3089 .display_map
3090 .update(cx, |display_map, cx| display_map.snapshot(cx));
3091 let buffer = display_map.buffer_snapshot();
3092 if self.selections.count() == 1 {
3093 self.add_selections_state = None;
3094 }
3095 self.select_next_state = None;
3096 self.select_prev_state = None;
3097 self.select_syntax_node_history.try_clear();
3098 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3099 self.snippet_stack.invalidate(&selection_anchors, buffer);
3100 self.take_rename(false, window, cx);
3101
3102 let newest_selection = self.selections.newest_anchor();
3103 let new_cursor_position = newest_selection.head();
3104 let selection_start = newest_selection.start;
3105
3106 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3107 self.push_to_nav_history(
3108 *old_cursor_position,
3109 Some(new_cursor_position.to_point(buffer)),
3110 false,
3111 effects.nav_history == Some(true),
3112 cx,
3113 );
3114 }
3115
3116 if local {
3117 if let Some(buffer_id) = new_cursor_position.buffer_id {
3118 self.register_buffer(buffer_id, cx);
3119 }
3120
3121 let mut context_menu = self.context_menu.borrow_mut();
3122 let completion_menu = match context_menu.as_ref() {
3123 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3124 Some(CodeContextMenu::CodeActions(_)) => {
3125 *context_menu = None;
3126 None
3127 }
3128 None => None,
3129 };
3130 let completion_position = completion_menu.map(|menu| menu.initial_position);
3131 drop(context_menu);
3132
3133 if effects.completions
3134 && let Some(completion_position) = completion_position
3135 {
3136 let start_offset = selection_start.to_offset(buffer);
3137 let position_matches = start_offset == completion_position.to_offset(buffer);
3138 let continue_showing = if position_matches {
3139 if self.snippet_stack.is_empty() {
3140 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3141 == Some(CharKind::Word)
3142 } else {
3143 // Snippet choices can be shown even when the cursor is in whitespace.
3144 // Dismissing the menu with actions like backspace is handled by
3145 // invalidation regions.
3146 true
3147 }
3148 } else {
3149 false
3150 };
3151
3152 if continue_showing {
3153 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3154 } else {
3155 self.hide_context_menu(window, cx);
3156 }
3157 }
3158
3159 hide_hover(self, cx);
3160
3161 if old_cursor_position.to_display_point(&display_map).row()
3162 != new_cursor_position.to_display_point(&display_map).row()
3163 {
3164 self.available_code_actions.take();
3165 }
3166 self.refresh_code_actions(window, cx);
3167 self.refresh_document_highlights(cx);
3168 refresh_linked_ranges(self, window, cx);
3169
3170 self.refresh_selected_text_highlights(false, window, cx);
3171 refresh_matching_bracket_highlights(self, cx);
3172 self.update_visible_edit_prediction(window, cx);
3173 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3174 self.inline_blame_popover.take();
3175 if self.git_blame_inline_enabled {
3176 self.start_inline_blame_timer(window, cx);
3177 }
3178 }
3179
3180 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3181 cx.emit(EditorEvent::SelectionsChanged { local });
3182
3183 let selections = &self.selections.disjoint_anchors_arc();
3184 if selections.len() == 1 {
3185 cx.emit(SearchEvent::ActiveMatchChanged)
3186 }
3187 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3188 let inmemory_selections = selections
3189 .iter()
3190 .map(|s| {
3191 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3192 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3193 })
3194 .collect();
3195 self.update_restoration_data(cx, |data| {
3196 data.selections = inmemory_selections;
3197 });
3198
3199 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3200 && let Some(workspace_id) =
3201 self.workspace.as_ref().and_then(|workspace| workspace.1)
3202 {
3203 let snapshot = self.buffer().read(cx).snapshot(cx);
3204 let selections = selections.clone();
3205 let background_executor = cx.background_executor().clone();
3206 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3207 self.serialize_selections = cx.background_spawn(async move {
3208 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3209 let db_selections = selections
3210 .iter()
3211 .map(|selection| {
3212 (
3213 selection.start.to_offset(&snapshot),
3214 selection.end.to_offset(&snapshot),
3215 )
3216 })
3217 .collect();
3218
3219 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3220 .await
3221 .with_context(|| {
3222 format!(
3223 "persisting editor selections for editor {editor_id}, \
3224 workspace {workspace_id:?}"
3225 )
3226 })
3227 .log_err();
3228 });
3229 }
3230 }
3231
3232 cx.notify();
3233 }
3234
3235 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3236 use text::ToOffset as _;
3237 use text::ToPoint as _;
3238
3239 if self.mode.is_minimap()
3240 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3241 {
3242 return;
3243 }
3244
3245 if !self.buffer().read(cx).is_singleton() {
3246 return;
3247 }
3248
3249 let display_snapshot = self
3250 .display_map
3251 .update(cx, |display_map, cx| display_map.snapshot(cx));
3252 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3253 return;
3254 };
3255 let inmemory_folds = display_snapshot
3256 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3257 .map(|fold| {
3258 fold.range.start.text_anchor.to_point(&snapshot)
3259 ..fold.range.end.text_anchor.to_point(&snapshot)
3260 })
3261 .collect();
3262 self.update_restoration_data(cx, |data| {
3263 data.folds = inmemory_folds;
3264 });
3265
3266 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3267 return;
3268 };
3269 let background_executor = cx.background_executor().clone();
3270 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3271 let db_folds = display_snapshot
3272 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3273 .map(|fold| {
3274 (
3275 fold.range.start.text_anchor.to_offset(&snapshot),
3276 fold.range.end.text_anchor.to_offset(&snapshot),
3277 )
3278 })
3279 .collect();
3280 self.serialize_folds = cx.background_spawn(async move {
3281 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3282 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3283 .await
3284 .with_context(|| {
3285 format!(
3286 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3287 )
3288 })
3289 .log_err();
3290 });
3291 }
3292
3293 pub fn sync_selections(
3294 &mut self,
3295 other: Entity<Editor>,
3296 cx: &mut Context<Self>,
3297 ) -> gpui::Subscription {
3298 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3299 if !other_selections.is_empty() {
3300 self.selections.change_with(cx, |selections| {
3301 selections.select_anchors(other_selections);
3302 });
3303 }
3304
3305 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3306 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3307 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3308 if other_selections.is_empty() {
3309 return;
3310 }
3311 this.selections.change_with(cx, |selections| {
3312 selections.select_anchors(other_selections);
3313 });
3314 }
3315 });
3316
3317 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3318 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3319 let these_selections = this.selections.disjoint_anchors().to_vec();
3320 if these_selections.is_empty() {
3321 return;
3322 }
3323 other.update(cx, |other_editor, cx| {
3324 other_editor.selections.change_with(cx, |selections| {
3325 selections.select_anchors(these_selections);
3326 })
3327 });
3328 }
3329 });
3330
3331 Subscription::join(other_subscription, this_subscription)
3332 }
3333
3334 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3335 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3336 /// effects of selection change occur at the end of the transaction.
3337 pub fn change_selections<R>(
3338 &mut self,
3339 effects: SelectionEffects,
3340 window: &mut Window,
3341 cx: &mut Context<Self>,
3342 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3343 ) -> R {
3344 if let Some(state) = &mut self.deferred_selection_effects_state {
3345 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3346 state.effects.completions = effects.completions;
3347 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3348 let (changed, result) = self.selections.change_with(cx, change);
3349 state.changed |= changed;
3350 return result;
3351 }
3352 let mut state = DeferredSelectionEffectsState {
3353 changed: false,
3354 effects,
3355 old_cursor_position: self.selections.newest_anchor().head(),
3356 history_entry: SelectionHistoryEntry {
3357 selections: self.selections.disjoint_anchors_arc(),
3358 select_next_state: self.select_next_state.clone(),
3359 select_prev_state: self.select_prev_state.clone(),
3360 add_selections_state: self.add_selections_state.clone(),
3361 },
3362 };
3363 let (changed, result) = self.selections.change_with(cx, change);
3364 state.changed = state.changed || changed;
3365 if self.defer_selection_effects {
3366 self.deferred_selection_effects_state = Some(state);
3367 } else {
3368 self.apply_selection_effects(state, window, cx);
3369 }
3370 result
3371 }
3372
3373 /// Defers the effects of selection change, so that the effects of multiple calls to
3374 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3375 /// to selection history and the state of popovers based on selection position aren't
3376 /// erroneously updated.
3377 pub fn with_selection_effects_deferred<R>(
3378 &mut self,
3379 window: &mut Window,
3380 cx: &mut Context<Self>,
3381 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3382 ) -> R {
3383 let already_deferred = self.defer_selection_effects;
3384 self.defer_selection_effects = true;
3385 let result = update(self, window, cx);
3386 if !already_deferred {
3387 self.defer_selection_effects = false;
3388 if let Some(state) = self.deferred_selection_effects_state.take() {
3389 self.apply_selection_effects(state, window, cx);
3390 }
3391 }
3392 result
3393 }
3394
3395 fn apply_selection_effects(
3396 &mut self,
3397 state: DeferredSelectionEffectsState,
3398 window: &mut Window,
3399 cx: &mut Context<Self>,
3400 ) {
3401 if state.changed {
3402 self.selection_history.push(state.history_entry);
3403
3404 if let Some(autoscroll) = state.effects.scroll {
3405 self.request_autoscroll(autoscroll, cx);
3406 }
3407
3408 let old_cursor_position = &state.old_cursor_position;
3409
3410 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3411
3412 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3413 self.show_signature_help(&ShowSignatureHelp, window, cx);
3414 }
3415 }
3416 }
3417
3418 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3419 where
3420 I: IntoIterator<Item = (Range<S>, T)>,
3421 S: ToOffset,
3422 T: Into<Arc<str>>,
3423 {
3424 if self.read_only(cx) {
3425 return;
3426 }
3427
3428 self.buffer
3429 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3430 }
3431
3432 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3433 where
3434 I: IntoIterator<Item = (Range<S>, T)>,
3435 S: ToOffset,
3436 T: Into<Arc<str>>,
3437 {
3438 if self.read_only(cx) {
3439 return;
3440 }
3441
3442 self.buffer.update(cx, |buffer, cx| {
3443 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3444 });
3445 }
3446
3447 pub fn edit_with_block_indent<I, S, T>(
3448 &mut self,
3449 edits: I,
3450 original_indent_columns: Vec<Option<u32>>,
3451 cx: &mut Context<Self>,
3452 ) where
3453 I: IntoIterator<Item = (Range<S>, T)>,
3454 S: ToOffset,
3455 T: Into<Arc<str>>,
3456 {
3457 if self.read_only(cx) {
3458 return;
3459 }
3460
3461 self.buffer.update(cx, |buffer, cx| {
3462 buffer.edit(
3463 edits,
3464 Some(AutoindentMode::Block {
3465 original_indent_columns,
3466 }),
3467 cx,
3468 )
3469 });
3470 }
3471
3472 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3473 self.hide_context_menu(window, cx);
3474
3475 match phase {
3476 SelectPhase::Begin {
3477 position,
3478 add,
3479 click_count,
3480 } => self.begin_selection(position, add, click_count, window, cx),
3481 SelectPhase::BeginColumnar {
3482 position,
3483 goal_column,
3484 reset,
3485 mode,
3486 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3487 SelectPhase::Extend {
3488 position,
3489 click_count,
3490 } => self.extend_selection(position, click_count, window, cx),
3491 SelectPhase::Update {
3492 position,
3493 goal_column,
3494 scroll_delta,
3495 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3496 SelectPhase::End => self.end_selection(window, cx),
3497 }
3498 }
3499
3500 fn extend_selection(
3501 &mut self,
3502 position: DisplayPoint,
3503 click_count: usize,
3504 window: &mut Window,
3505 cx: &mut Context<Self>,
3506 ) {
3507 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3508 let tail = self.selections.newest::<usize>(&display_map).tail();
3509 let click_count = click_count.max(match self.selections.select_mode() {
3510 SelectMode::Character => 1,
3511 SelectMode::Word(_) => 2,
3512 SelectMode::Line(_) => 3,
3513 SelectMode::All => 4,
3514 });
3515 self.begin_selection(position, false, click_count, window, cx);
3516
3517 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3518
3519 let current_selection = match self.selections.select_mode() {
3520 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3521 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3522 };
3523
3524 let mut pending_selection = self
3525 .selections
3526 .pending_anchor()
3527 .cloned()
3528 .expect("extend_selection not called with pending selection");
3529
3530 if pending_selection
3531 .start
3532 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3533 == Ordering::Greater
3534 {
3535 pending_selection.start = current_selection.start;
3536 }
3537 if pending_selection
3538 .end
3539 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3540 == Ordering::Less
3541 {
3542 pending_selection.end = current_selection.end;
3543 pending_selection.reversed = true;
3544 }
3545
3546 let mut pending_mode = self.selections.pending_mode().unwrap();
3547 match &mut pending_mode {
3548 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3549 _ => {}
3550 }
3551
3552 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3553 SelectionEffects::scroll(Autoscroll::fit())
3554 } else {
3555 SelectionEffects::no_scroll()
3556 };
3557
3558 self.change_selections(effects, window, cx, |s| {
3559 s.set_pending(pending_selection.clone(), pending_mode);
3560 s.set_is_extending(true);
3561 });
3562 }
3563
3564 fn begin_selection(
3565 &mut self,
3566 position: DisplayPoint,
3567 add: bool,
3568 click_count: usize,
3569 window: &mut Window,
3570 cx: &mut Context<Self>,
3571 ) {
3572 if !self.focus_handle.is_focused(window) {
3573 self.last_focused_descendant = None;
3574 window.focus(&self.focus_handle);
3575 }
3576
3577 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3578 let buffer = display_map.buffer_snapshot();
3579 let position = display_map.clip_point(position, Bias::Left);
3580
3581 let start;
3582 let end;
3583 let mode;
3584 let mut auto_scroll;
3585 match click_count {
3586 1 => {
3587 start = buffer.anchor_before(position.to_point(&display_map));
3588 end = start;
3589 mode = SelectMode::Character;
3590 auto_scroll = true;
3591 }
3592 2 => {
3593 let position = display_map
3594 .clip_point(position, Bias::Left)
3595 .to_offset(&display_map, Bias::Left);
3596 let (range, _) = buffer.surrounding_word(position, None);
3597 start = buffer.anchor_before(range.start);
3598 end = buffer.anchor_before(range.end);
3599 mode = SelectMode::Word(start..end);
3600 auto_scroll = true;
3601 }
3602 3 => {
3603 let position = display_map
3604 .clip_point(position, Bias::Left)
3605 .to_point(&display_map);
3606 let line_start = display_map.prev_line_boundary(position).0;
3607 let next_line_start = buffer.clip_point(
3608 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3609 Bias::Left,
3610 );
3611 start = buffer.anchor_before(line_start);
3612 end = buffer.anchor_before(next_line_start);
3613 mode = SelectMode::Line(start..end);
3614 auto_scroll = true;
3615 }
3616 _ => {
3617 start = buffer.anchor_before(0);
3618 end = buffer.anchor_before(buffer.len());
3619 mode = SelectMode::All;
3620 auto_scroll = false;
3621 }
3622 }
3623 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3624
3625 let point_to_delete: Option<usize> = {
3626 let selected_points: Vec<Selection<Point>> =
3627 self.selections.disjoint_in_range(start..end, &display_map);
3628
3629 if !add || click_count > 1 {
3630 None
3631 } else if !selected_points.is_empty() {
3632 Some(selected_points[0].id)
3633 } else {
3634 let clicked_point_already_selected =
3635 self.selections.disjoint_anchors().iter().find(|selection| {
3636 selection.start.to_point(buffer) == start.to_point(buffer)
3637 || selection.end.to_point(buffer) == end.to_point(buffer)
3638 });
3639
3640 clicked_point_already_selected.map(|selection| selection.id)
3641 }
3642 };
3643
3644 let selections_count = self.selections.count();
3645 let effects = if auto_scroll {
3646 SelectionEffects::default()
3647 } else {
3648 SelectionEffects::no_scroll()
3649 };
3650
3651 self.change_selections(effects, window, cx, |s| {
3652 if let Some(point_to_delete) = point_to_delete {
3653 s.delete(point_to_delete);
3654
3655 if selections_count == 1 {
3656 s.set_pending_anchor_range(start..end, mode);
3657 }
3658 } else {
3659 if !add {
3660 s.clear_disjoint();
3661 }
3662
3663 s.set_pending_anchor_range(start..end, mode);
3664 }
3665 });
3666 }
3667
3668 fn begin_columnar_selection(
3669 &mut self,
3670 position: DisplayPoint,
3671 goal_column: u32,
3672 reset: bool,
3673 mode: ColumnarMode,
3674 window: &mut Window,
3675 cx: &mut Context<Self>,
3676 ) {
3677 if !self.focus_handle.is_focused(window) {
3678 self.last_focused_descendant = None;
3679 window.focus(&self.focus_handle);
3680 }
3681
3682 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3683
3684 if reset {
3685 let pointer_position = display_map
3686 .buffer_snapshot()
3687 .anchor_before(position.to_point(&display_map));
3688
3689 self.change_selections(
3690 SelectionEffects::scroll(Autoscroll::newest()),
3691 window,
3692 cx,
3693 |s| {
3694 s.clear_disjoint();
3695 s.set_pending_anchor_range(
3696 pointer_position..pointer_position,
3697 SelectMode::Character,
3698 );
3699 },
3700 );
3701 };
3702
3703 let tail = self.selections.newest::<Point>(&display_map).tail();
3704 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3705 self.columnar_selection_state = match mode {
3706 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3707 selection_tail: selection_anchor,
3708 display_point: if reset {
3709 if position.column() != goal_column {
3710 Some(DisplayPoint::new(position.row(), goal_column))
3711 } else {
3712 None
3713 }
3714 } else {
3715 None
3716 },
3717 }),
3718 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3719 selection_tail: selection_anchor,
3720 }),
3721 };
3722
3723 if !reset {
3724 self.select_columns(position, goal_column, &display_map, window, cx);
3725 }
3726 }
3727
3728 fn update_selection(
3729 &mut self,
3730 position: DisplayPoint,
3731 goal_column: u32,
3732 scroll_delta: gpui::Point<f32>,
3733 window: &mut Window,
3734 cx: &mut Context<Self>,
3735 ) {
3736 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3737
3738 if self.columnar_selection_state.is_some() {
3739 self.select_columns(position, goal_column, &display_map, window, cx);
3740 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3741 let buffer = display_map.buffer_snapshot();
3742 let head;
3743 let tail;
3744 let mode = self.selections.pending_mode().unwrap();
3745 match &mode {
3746 SelectMode::Character => {
3747 head = position.to_point(&display_map);
3748 tail = pending.tail().to_point(buffer);
3749 }
3750 SelectMode::Word(original_range) => {
3751 let offset = display_map
3752 .clip_point(position, Bias::Left)
3753 .to_offset(&display_map, Bias::Left);
3754 let original_range = original_range.to_offset(buffer);
3755
3756 let head_offset = if buffer.is_inside_word(offset, None)
3757 || original_range.contains(&offset)
3758 {
3759 let (word_range, _) = buffer.surrounding_word(offset, None);
3760 if word_range.start < original_range.start {
3761 word_range.start
3762 } else {
3763 word_range.end
3764 }
3765 } else {
3766 offset
3767 };
3768
3769 head = head_offset.to_point(buffer);
3770 if head_offset <= original_range.start {
3771 tail = original_range.end.to_point(buffer);
3772 } else {
3773 tail = original_range.start.to_point(buffer);
3774 }
3775 }
3776 SelectMode::Line(original_range) => {
3777 let original_range = original_range.to_point(display_map.buffer_snapshot());
3778
3779 let position = display_map
3780 .clip_point(position, Bias::Left)
3781 .to_point(&display_map);
3782 let line_start = display_map.prev_line_boundary(position).0;
3783 let next_line_start = buffer.clip_point(
3784 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3785 Bias::Left,
3786 );
3787
3788 if line_start < original_range.start {
3789 head = line_start
3790 } else {
3791 head = next_line_start
3792 }
3793
3794 if head <= original_range.start {
3795 tail = original_range.end;
3796 } else {
3797 tail = original_range.start;
3798 }
3799 }
3800 SelectMode::All => {
3801 return;
3802 }
3803 };
3804
3805 if head < tail {
3806 pending.start = buffer.anchor_before(head);
3807 pending.end = buffer.anchor_before(tail);
3808 pending.reversed = true;
3809 } else {
3810 pending.start = buffer.anchor_before(tail);
3811 pending.end = buffer.anchor_before(head);
3812 pending.reversed = false;
3813 }
3814
3815 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3816 s.set_pending(pending.clone(), mode);
3817 });
3818 } else {
3819 log::error!("update_selection dispatched with no pending selection");
3820 return;
3821 }
3822
3823 self.apply_scroll_delta(scroll_delta, window, cx);
3824 cx.notify();
3825 }
3826
3827 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3828 self.columnar_selection_state.take();
3829 if let Some(pending_mode) = self.selections.pending_mode() {
3830 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3831 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3832 s.select(selections);
3833 s.clear_pending();
3834 if s.is_extending() {
3835 s.set_is_extending(false);
3836 } else {
3837 s.set_select_mode(pending_mode);
3838 }
3839 });
3840 }
3841 }
3842
3843 fn select_columns(
3844 &mut self,
3845 head: DisplayPoint,
3846 goal_column: u32,
3847 display_map: &DisplaySnapshot,
3848 window: &mut Window,
3849 cx: &mut Context<Self>,
3850 ) {
3851 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3852 return;
3853 };
3854
3855 let tail = match columnar_state {
3856 ColumnarSelectionState::FromMouse {
3857 selection_tail,
3858 display_point,
3859 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3860 ColumnarSelectionState::FromSelection { selection_tail } => {
3861 selection_tail.to_display_point(display_map)
3862 }
3863 };
3864
3865 let start_row = cmp::min(tail.row(), head.row());
3866 let end_row = cmp::max(tail.row(), head.row());
3867 let start_column = cmp::min(tail.column(), goal_column);
3868 let end_column = cmp::max(tail.column(), goal_column);
3869 let reversed = start_column < tail.column();
3870
3871 let selection_ranges = (start_row.0..=end_row.0)
3872 .map(DisplayRow)
3873 .filter_map(|row| {
3874 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3875 || start_column <= display_map.line_len(row))
3876 && !display_map.is_block_line(row)
3877 {
3878 let start = display_map
3879 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3880 .to_point(display_map);
3881 let end = display_map
3882 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3883 .to_point(display_map);
3884 if reversed {
3885 Some(end..start)
3886 } else {
3887 Some(start..end)
3888 }
3889 } else {
3890 None
3891 }
3892 })
3893 .collect::<Vec<_>>();
3894 if selection_ranges.is_empty() {
3895 return;
3896 }
3897
3898 let ranges = match columnar_state {
3899 ColumnarSelectionState::FromMouse { .. } => {
3900 let mut non_empty_ranges = selection_ranges
3901 .iter()
3902 .filter(|selection_range| selection_range.start != selection_range.end)
3903 .peekable();
3904 if non_empty_ranges.peek().is_some() {
3905 non_empty_ranges.cloned().collect()
3906 } else {
3907 selection_ranges
3908 }
3909 }
3910 _ => selection_ranges,
3911 };
3912
3913 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3914 s.select_ranges(ranges);
3915 });
3916 cx.notify();
3917 }
3918
3919 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
3920 self.selections
3921 .all_adjusted(snapshot)
3922 .iter()
3923 .any(|selection| !selection.is_empty())
3924 }
3925
3926 pub fn has_pending_nonempty_selection(&self) -> bool {
3927 let pending_nonempty_selection = match self.selections.pending_anchor() {
3928 Some(Selection { start, end, .. }) => start != end,
3929 None => false,
3930 };
3931
3932 pending_nonempty_selection
3933 || (self.columnar_selection_state.is_some()
3934 && self.selections.disjoint_anchors().len() > 1)
3935 }
3936
3937 pub fn has_pending_selection(&self) -> bool {
3938 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3939 }
3940
3941 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3942 self.selection_mark_mode = false;
3943 self.selection_drag_state = SelectionDragState::None;
3944
3945 if self.clear_expanded_diff_hunks(cx) {
3946 cx.notify();
3947 return;
3948 }
3949 if self.dismiss_menus_and_popups(true, window, cx) {
3950 return;
3951 }
3952
3953 if self.mode.is_full()
3954 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3955 {
3956 return;
3957 }
3958
3959 cx.propagate();
3960 }
3961
3962 pub fn dismiss_menus_and_popups(
3963 &mut self,
3964 is_user_requested: bool,
3965 window: &mut Window,
3966 cx: &mut Context<Self>,
3967 ) -> bool {
3968 if self.take_rename(false, window, cx).is_some() {
3969 return true;
3970 }
3971
3972 if hide_hover(self, cx) {
3973 return true;
3974 }
3975
3976 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3977 return true;
3978 }
3979
3980 if self.hide_context_menu(window, cx).is_some() {
3981 return true;
3982 }
3983
3984 if self.mouse_context_menu.take().is_some() {
3985 return true;
3986 }
3987
3988 if is_user_requested && self.discard_edit_prediction(true, cx) {
3989 return true;
3990 }
3991
3992 if self.snippet_stack.pop().is_some() {
3993 return true;
3994 }
3995
3996 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3997 self.dismiss_diagnostics(cx);
3998 return true;
3999 }
4000
4001 false
4002 }
4003
4004 fn linked_editing_ranges_for(
4005 &self,
4006 selection: Range<text::Anchor>,
4007 cx: &App,
4008 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4009 if self.linked_edit_ranges.is_empty() {
4010 return None;
4011 }
4012 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4013 selection.end.buffer_id.and_then(|end_buffer_id| {
4014 if selection.start.buffer_id != Some(end_buffer_id) {
4015 return None;
4016 }
4017 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4018 let snapshot = buffer.read(cx).snapshot();
4019 self.linked_edit_ranges
4020 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4021 .map(|ranges| (ranges, snapshot, buffer))
4022 })?;
4023 use text::ToOffset as TO;
4024 // find offset from the start of current range to current cursor position
4025 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4026
4027 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4028 let start_difference = start_offset - start_byte_offset;
4029 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4030 let end_difference = end_offset - start_byte_offset;
4031 // Current range has associated linked ranges.
4032 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4033 for range in linked_ranges.iter() {
4034 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4035 let end_offset = start_offset + end_difference;
4036 let start_offset = start_offset + start_difference;
4037 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4038 continue;
4039 }
4040 if self.selections.disjoint_anchor_ranges().any(|s| {
4041 if s.start.buffer_id != selection.start.buffer_id
4042 || s.end.buffer_id != selection.end.buffer_id
4043 {
4044 return false;
4045 }
4046 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4047 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4048 }) {
4049 continue;
4050 }
4051 let start = buffer_snapshot.anchor_after(start_offset);
4052 let end = buffer_snapshot.anchor_after(end_offset);
4053 linked_edits
4054 .entry(buffer.clone())
4055 .or_default()
4056 .push(start..end);
4057 }
4058 Some(linked_edits)
4059 }
4060
4061 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4062 let text: Arc<str> = text.into();
4063
4064 if self.read_only(cx) {
4065 return;
4066 }
4067
4068 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4069
4070 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4071 let mut bracket_inserted = false;
4072 let mut edits = Vec::new();
4073 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4074 let mut new_selections = Vec::with_capacity(selections.len());
4075 let mut new_autoclose_regions = Vec::new();
4076 let snapshot = self.buffer.read(cx).read(cx);
4077 let mut clear_linked_edit_ranges = false;
4078
4079 for (selection, autoclose_region) in
4080 self.selections_with_autoclose_regions(selections, &snapshot)
4081 {
4082 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4083 // Determine if the inserted text matches the opening or closing
4084 // bracket of any of this language's bracket pairs.
4085 let mut bracket_pair = None;
4086 let mut is_bracket_pair_start = false;
4087 let mut is_bracket_pair_end = false;
4088 if !text.is_empty() {
4089 let mut bracket_pair_matching_end = None;
4090 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4091 // and they are removing the character that triggered IME popup.
4092 for (pair, enabled) in scope.brackets() {
4093 if !pair.close && !pair.surround {
4094 continue;
4095 }
4096
4097 if enabled && pair.start.ends_with(text.as_ref()) {
4098 let prefix_len = pair.start.len() - text.len();
4099 let preceding_text_matches_prefix = prefix_len == 0
4100 || (selection.start.column >= (prefix_len as u32)
4101 && snapshot.contains_str_at(
4102 Point::new(
4103 selection.start.row,
4104 selection.start.column - (prefix_len as u32),
4105 ),
4106 &pair.start[..prefix_len],
4107 ));
4108 if preceding_text_matches_prefix {
4109 bracket_pair = Some(pair.clone());
4110 is_bracket_pair_start = true;
4111 break;
4112 }
4113 }
4114 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4115 {
4116 // take first bracket pair matching end, but don't break in case a later bracket
4117 // pair matches start
4118 bracket_pair_matching_end = Some(pair.clone());
4119 }
4120 }
4121 if let Some(end) = bracket_pair_matching_end
4122 && bracket_pair.is_none()
4123 {
4124 bracket_pair = Some(end);
4125 is_bracket_pair_end = true;
4126 }
4127 }
4128
4129 if let Some(bracket_pair) = bracket_pair {
4130 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4131 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4132 let auto_surround =
4133 self.use_auto_surround && snapshot_settings.use_auto_surround;
4134 if selection.is_empty() {
4135 if is_bracket_pair_start {
4136 // If the inserted text is a suffix of an opening bracket and the
4137 // selection is preceded by the rest of the opening bracket, then
4138 // insert the closing bracket.
4139 let following_text_allows_autoclose = snapshot
4140 .chars_at(selection.start)
4141 .next()
4142 .is_none_or(|c| scope.should_autoclose_before(c));
4143
4144 let preceding_text_allows_autoclose = selection.start.column == 0
4145 || snapshot
4146 .reversed_chars_at(selection.start)
4147 .next()
4148 .is_none_or(|c| {
4149 bracket_pair.start != bracket_pair.end
4150 || !snapshot
4151 .char_classifier_at(selection.start)
4152 .is_word(c)
4153 });
4154
4155 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4156 && bracket_pair.start.len() == 1
4157 {
4158 let target = bracket_pair.start.chars().next().unwrap();
4159 let current_line_count = snapshot
4160 .reversed_chars_at(selection.start)
4161 .take_while(|&c| c != '\n')
4162 .filter(|&c| c == target)
4163 .count();
4164 current_line_count % 2 == 1
4165 } else {
4166 false
4167 };
4168
4169 if autoclose
4170 && bracket_pair.close
4171 && following_text_allows_autoclose
4172 && preceding_text_allows_autoclose
4173 && !is_closing_quote
4174 {
4175 let anchor = snapshot.anchor_before(selection.end);
4176 new_selections.push((selection.map(|_| anchor), text.len()));
4177 new_autoclose_regions.push((
4178 anchor,
4179 text.len(),
4180 selection.id,
4181 bracket_pair.clone(),
4182 ));
4183 edits.push((
4184 selection.range(),
4185 format!("{}{}", text, bracket_pair.end).into(),
4186 ));
4187 bracket_inserted = true;
4188 continue;
4189 }
4190 }
4191
4192 if let Some(region) = autoclose_region {
4193 // If the selection is followed by an auto-inserted closing bracket,
4194 // then don't insert that closing bracket again; just move the selection
4195 // past the closing bracket.
4196 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4197 && text.as_ref() == region.pair.end.as_str()
4198 && snapshot.contains_str_at(region.range.end, text.as_ref());
4199 if should_skip {
4200 let anchor = snapshot.anchor_after(selection.end);
4201 new_selections
4202 .push((selection.map(|_| anchor), region.pair.end.len()));
4203 continue;
4204 }
4205 }
4206
4207 let always_treat_brackets_as_autoclosed = snapshot
4208 .language_settings_at(selection.start, cx)
4209 .always_treat_brackets_as_autoclosed;
4210 if always_treat_brackets_as_autoclosed
4211 && is_bracket_pair_end
4212 && snapshot.contains_str_at(selection.end, text.as_ref())
4213 {
4214 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4215 // and the inserted text is a closing bracket and the selection is followed
4216 // by the closing bracket then move the selection past the closing bracket.
4217 let anchor = snapshot.anchor_after(selection.end);
4218 new_selections.push((selection.map(|_| anchor), text.len()));
4219 continue;
4220 }
4221 }
4222 // If an opening bracket is 1 character long and is typed while
4223 // text is selected, then surround that text with the bracket pair.
4224 else if auto_surround
4225 && bracket_pair.surround
4226 && is_bracket_pair_start
4227 && bracket_pair.start.chars().count() == 1
4228 {
4229 edits.push((selection.start..selection.start, text.clone()));
4230 edits.push((
4231 selection.end..selection.end,
4232 bracket_pair.end.as_str().into(),
4233 ));
4234 bracket_inserted = true;
4235 new_selections.push((
4236 Selection {
4237 id: selection.id,
4238 start: snapshot.anchor_after(selection.start),
4239 end: snapshot.anchor_before(selection.end),
4240 reversed: selection.reversed,
4241 goal: selection.goal,
4242 },
4243 0,
4244 ));
4245 continue;
4246 }
4247 }
4248 }
4249
4250 if self.auto_replace_emoji_shortcode
4251 && selection.is_empty()
4252 && text.as_ref().ends_with(':')
4253 && let Some(possible_emoji_short_code) =
4254 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4255 && !possible_emoji_short_code.is_empty()
4256 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4257 {
4258 let emoji_shortcode_start = Point::new(
4259 selection.start.row,
4260 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4261 );
4262
4263 // Remove shortcode from buffer
4264 edits.push((
4265 emoji_shortcode_start..selection.start,
4266 "".to_string().into(),
4267 ));
4268 new_selections.push((
4269 Selection {
4270 id: selection.id,
4271 start: snapshot.anchor_after(emoji_shortcode_start),
4272 end: snapshot.anchor_before(selection.start),
4273 reversed: selection.reversed,
4274 goal: selection.goal,
4275 },
4276 0,
4277 ));
4278
4279 // Insert emoji
4280 let selection_start_anchor = snapshot.anchor_after(selection.start);
4281 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4282 edits.push((selection.start..selection.end, emoji.to_string().into()));
4283
4284 continue;
4285 }
4286
4287 // If not handling any auto-close operation, then just replace the selected
4288 // text with the given input and move the selection to the end of the
4289 // newly inserted text.
4290 let anchor = snapshot.anchor_after(selection.end);
4291 if !self.linked_edit_ranges.is_empty() {
4292 let start_anchor = snapshot.anchor_before(selection.start);
4293
4294 let is_word_char = text.chars().next().is_none_or(|char| {
4295 let classifier = snapshot
4296 .char_classifier_at(start_anchor.to_offset(&snapshot))
4297 .scope_context(Some(CharScopeContext::LinkedEdit));
4298 classifier.is_word(char)
4299 });
4300
4301 if is_word_char {
4302 if let Some(ranges) = self
4303 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4304 {
4305 for (buffer, edits) in ranges {
4306 linked_edits
4307 .entry(buffer.clone())
4308 .or_default()
4309 .extend(edits.into_iter().map(|range| (range, text.clone())));
4310 }
4311 }
4312 } else {
4313 clear_linked_edit_ranges = true;
4314 }
4315 }
4316
4317 new_selections.push((selection.map(|_| anchor), 0));
4318 edits.push((selection.start..selection.end, text.clone()));
4319 }
4320
4321 drop(snapshot);
4322
4323 self.transact(window, cx, |this, window, cx| {
4324 if clear_linked_edit_ranges {
4325 this.linked_edit_ranges.clear();
4326 }
4327 let initial_buffer_versions =
4328 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4329
4330 this.buffer.update(cx, |buffer, cx| {
4331 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4332 });
4333 for (buffer, edits) in linked_edits {
4334 buffer.update(cx, |buffer, cx| {
4335 let snapshot = buffer.snapshot();
4336 let edits = edits
4337 .into_iter()
4338 .map(|(range, text)| {
4339 use text::ToPoint as TP;
4340 let end_point = TP::to_point(&range.end, &snapshot);
4341 let start_point = TP::to_point(&range.start, &snapshot);
4342 (start_point..end_point, text)
4343 })
4344 .sorted_by_key(|(range, _)| range.start);
4345 buffer.edit(edits, None, cx);
4346 })
4347 }
4348 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4349 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4350 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4351 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4352 .zip(new_selection_deltas)
4353 .map(|(selection, delta)| Selection {
4354 id: selection.id,
4355 start: selection.start + delta,
4356 end: selection.end + delta,
4357 reversed: selection.reversed,
4358 goal: SelectionGoal::None,
4359 })
4360 .collect::<Vec<_>>();
4361
4362 let mut i = 0;
4363 for (position, delta, selection_id, pair) in new_autoclose_regions {
4364 let position = position.to_offset(map.buffer_snapshot()) + delta;
4365 let start = map.buffer_snapshot().anchor_before(position);
4366 let end = map.buffer_snapshot().anchor_after(position);
4367 while let Some(existing_state) = this.autoclose_regions.get(i) {
4368 match existing_state
4369 .range
4370 .start
4371 .cmp(&start, map.buffer_snapshot())
4372 {
4373 Ordering::Less => i += 1,
4374 Ordering::Greater => break,
4375 Ordering::Equal => {
4376 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4377 Ordering::Less => i += 1,
4378 Ordering::Equal => break,
4379 Ordering::Greater => break,
4380 }
4381 }
4382 }
4383 }
4384 this.autoclose_regions.insert(
4385 i,
4386 AutocloseRegion {
4387 selection_id,
4388 range: start..end,
4389 pair,
4390 },
4391 );
4392 }
4393
4394 let had_active_edit_prediction = this.has_active_edit_prediction();
4395 this.change_selections(
4396 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4397 window,
4398 cx,
4399 |s| s.select(new_selections),
4400 );
4401
4402 if !bracket_inserted
4403 && let Some(on_type_format_task) =
4404 this.trigger_on_type_formatting(text.to_string(), window, cx)
4405 {
4406 on_type_format_task.detach_and_log_err(cx);
4407 }
4408
4409 let editor_settings = EditorSettings::get_global(cx);
4410 if bracket_inserted
4411 && (editor_settings.auto_signature_help
4412 || editor_settings.show_signature_help_after_edits)
4413 {
4414 this.show_signature_help(&ShowSignatureHelp, window, cx);
4415 }
4416
4417 let trigger_in_words =
4418 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4419 if this.hard_wrap.is_some() {
4420 let latest: Range<Point> = this.selections.newest(&map).range();
4421 if latest.is_empty()
4422 && this
4423 .buffer()
4424 .read(cx)
4425 .snapshot(cx)
4426 .line_len(MultiBufferRow(latest.start.row))
4427 == latest.start.column
4428 {
4429 this.rewrap_impl(
4430 RewrapOptions {
4431 override_language_settings: true,
4432 preserve_existing_whitespace: true,
4433 },
4434 cx,
4435 )
4436 }
4437 }
4438 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4439 refresh_linked_ranges(this, window, cx);
4440 this.refresh_edit_prediction(true, false, window, cx);
4441 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4442 });
4443 }
4444
4445 fn find_possible_emoji_shortcode_at_position(
4446 snapshot: &MultiBufferSnapshot,
4447 position: Point,
4448 ) -> Option<String> {
4449 let mut chars = Vec::new();
4450 let mut found_colon = false;
4451 for char in snapshot.reversed_chars_at(position).take(100) {
4452 // Found a possible emoji shortcode in the middle of the buffer
4453 if found_colon {
4454 if char.is_whitespace() {
4455 chars.reverse();
4456 return Some(chars.iter().collect());
4457 }
4458 // If the previous character is not a whitespace, we are in the middle of a word
4459 // and we only want to complete the shortcode if the word is made up of other emojis
4460 let mut containing_word = String::new();
4461 for ch in snapshot
4462 .reversed_chars_at(position)
4463 .skip(chars.len() + 1)
4464 .take(100)
4465 {
4466 if ch.is_whitespace() {
4467 break;
4468 }
4469 containing_word.push(ch);
4470 }
4471 let containing_word = containing_word.chars().rev().collect::<String>();
4472 if util::word_consists_of_emojis(containing_word.as_str()) {
4473 chars.reverse();
4474 return Some(chars.iter().collect());
4475 }
4476 }
4477
4478 if char.is_whitespace() || !char.is_ascii() {
4479 return None;
4480 }
4481 if char == ':' {
4482 found_colon = true;
4483 } else {
4484 chars.push(char);
4485 }
4486 }
4487 // Found a possible emoji shortcode at the beginning of the buffer
4488 chars.reverse();
4489 Some(chars.iter().collect())
4490 }
4491
4492 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4493 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4494 self.transact(window, cx, |this, window, cx| {
4495 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4496 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
4497 let multi_buffer = this.buffer.read(cx);
4498 let buffer = multi_buffer.snapshot(cx);
4499 selections
4500 .iter()
4501 .map(|selection| {
4502 let start_point = selection.start.to_point(&buffer);
4503 let mut existing_indent =
4504 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4505 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4506 let start = selection.start;
4507 let end = selection.end;
4508 let selection_is_empty = start == end;
4509 let language_scope = buffer.language_scope_at(start);
4510 let (
4511 comment_delimiter,
4512 doc_delimiter,
4513 insert_extra_newline,
4514 indent_on_newline,
4515 indent_on_extra_newline,
4516 ) = if let Some(language) = &language_scope {
4517 let mut insert_extra_newline =
4518 insert_extra_newline_brackets(&buffer, start..end, language)
4519 || insert_extra_newline_tree_sitter(&buffer, start..end);
4520
4521 // Comment extension on newline is allowed only for cursor selections
4522 let comment_delimiter = maybe!({
4523 if !selection_is_empty {
4524 return None;
4525 }
4526
4527 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4528 return None;
4529 }
4530
4531 let delimiters = language.line_comment_prefixes();
4532 let max_len_of_delimiter =
4533 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4534 let (snapshot, range) =
4535 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4536
4537 let num_of_whitespaces = snapshot
4538 .chars_for_range(range.clone())
4539 .take_while(|c| c.is_whitespace())
4540 .count();
4541 let comment_candidate = snapshot
4542 .chars_for_range(range.clone())
4543 .skip(num_of_whitespaces)
4544 .take(max_len_of_delimiter)
4545 .collect::<String>();
4546 let (delimiter, trimmed_len) = delimiters
4547 .iter()
4548 .filter_map(|delimiter| {
4549 let prefix = delimiter.trim_end();
4550 if comment_candidate.starts_with(prefix) {
4551 Some((delimiter, prefix.len()))
4552 } else {
4553 None
4554 }
4555 })
4556 .max_by_key(|(_, len)| *len)?;
4557
4558 if let Some(BlockCommentConfig {
4559 start: block_start, ..
4560 }) = language.block_comment()
4561 {
4562 let block_start_trimmed = block_start.trim_end();
4563 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4564 let line_content = snapshot
4565 .chars_for_range(range)
4566 .skip(num_of_whitespaces)
4567 .take(block_start_trimmed.len())
4568 .collect::<String>();
4569
4570 if line_content.starts_with(block_start_trimmed) {
4571 return None;
4572 }
4573 }
4574 }
4575
4576 let cursor_is_placed_after_comment_marker =
4577 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4578 if cursor_is_placed_after_comment_marker {
4579 Some(delimiter.clone())
4580 } else {
4581 None
4582 }
4583 });
4584
4585 let mut indent_on_newline = IndentSize::spaces(0);
4586 let mut indent_on_extra_newline = IndentSize::spaces(0);
4587
4588 let doc_delimiter = maybe!({
4589 if !selection_is_empty {
4590 return None;
4591 }
4592
4593 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4594 return None;
4595 }
4596
4597 let BlockCommentConfig {
4598 start: start_tag,
4599 end: end_tag,
4600 prefix: delimiter,
4601 tab_size: len,
4602 } = language.documentation_comment()?;
4603 let is_within_block_comment = buffer
4604 .language_scope_at(start_point)
4605 .is_some_and(|scope| scope.override_name() == Some("comment"));
4606 if !is_within_block_comment {
4607 return None;
4608 }
4609
4610 let (snapshot, range) =
4611 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4612
4613 let num_of_whitespaces = snapshot
4614 .chars_for_range(range.clone())
4615 .take_while(|c| c.is_whitespace())
4616 .count();
4617
4618 // 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.
4619 let column = start_point.column;
4620 let cursor_is_after_start_tag = {
4621 let start_tag_len = start_tag.len();
4622 let start_tag_line = snapshot
4623 .chars_for_range(range.clone())
4624 .skip(num_of_whitespaces)
4625 .take(start_tag_len)
4626 .collect::<String>();
4627 if start_tag_line.starts_with(start_tag.as_ref()) {
4628 num_of_whitespaces + start_tag_len <= column as usize
4629 } else {
4630 false
4631 }
4632 };
4633
4634 let cursor_is_after_delimiter = {
4635 let delimiter_trim = delimiter.trim_end();
4636 let delimiter_line = snapshot
4637 .chars_for_range(range.clone())
4638 .skip(num_of_whitespaces)
4639 .take(delimiter_trim.len())
4640 .collect::<String>();
4641 if delimiter_line.starts_with(delimiter_trim) {
4642 num_of_whitespaces + delimiter_trim.len() <= column as usize
4643 } else {
4644 false
4645 }
4646 };
4647
4648 let cursor_is_before_end_tag_if_exists = {
4649 let mut char_position = 0u32;
4650 let mut end_tag_offset = None;
4651
4652 'outer: for chunk in snapshot.text_for_range(range) {
4653 if let Some(byte_pos) = chunk.find(&**end_tag) {
4654 let chars_before_match =
4655 chunk[..byte_pos].chars().count() as u32;
4656 end_tag_offset =
4657 Some(char_position + chars_before_match);
4658 break 'outer;
4659 }
4660 char_position += chunk.chars().count() as u32;
4661 }
4662
4663 if let Some(end_tag_offset) = end_tag_offset {
4664 let cursor_is_before_end_tag = column <= end_tag_offset;
4665 if cursor_is_after_start_tag {
4666 if cursor_is_before_end_tag {
4667 insert_extra_newline = true;
4668 }
4669 let cursor_is_at_start_of_end_tag =
4670 column == end_tag_offset;
4671 if cursor_is_at_start_of_end_tag {
4672 indent_on_extra_newline.len = *len;
4673 }
4674 }
4675 cursor_is_before_end_tag
4676 } else {
4677 true
4678 }
4679 };
4680
4681 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4682 && cursor_is_before_end_tag_if_exists
4683 {
4684 if cursor_is_after_start_tag {
4685 indent_on_newline.len = *len;
4686 }
4687 Some(delimiter.clone())
4688 } else {
4689 None
4690 }
4691 });
4692
4693 (
4694 comment_delimiter,
4695 doc_delimiter,
4696 insert_extra_newline,
4697 indent_on_newline,
4698 indent_on_extra_newline,
4699 )
4700 } else {
4701 (
4702 None,
4703 None,
4704 false,
4705 IndentSize::default(),
4706 IndentSize::default(),
4707 )
4708 };
4709
4710 let prevent_auto_indent = doc_delimiter.is_some();
4711 let delimiter = comment_delimiter.or(doc_delimiter);
4712
4713 let capacity_for_delimiter =
4714 delimiter.as_deref().map(str::len).unwrap_or_default();
4715 let mut new_text = String::with_capacity(
4716 1 + capacity_for_delimiter
4717 + existing_indent.len as usize
4718 + indent_on_newline.len as usize
4719 + indent_on_extra_newline.len as usize,
4720 );
4721 new_text.push('\n');
4722 new_text.extend(existing_indent.chars());
4723 new_text.extend(indent_on_newline.chars());
4724
4725 if let Some(delimiter) = &delimiter {
4726 new_text.push_str(delimiter);
4727 }
4728
4729 if insert_extra_newline {
4730 new_text.push('\n');
4731 new_text.extend(existing_indent.chars());
4732 new_text.extend(indent_on_extra_newline.chars());
4733 }
4734
4735 let anchor = buffer.anchor_after(end);
4736 let new_selection = selection.map(|_| anchor);
4737 (
4738 ((start..end, new_text), prevent_auto_indent),
4739 (insert_extra_newline, new_selection),
4740 )
4741 })
4742 .unzip()
4743 };
4744
4745 let mut auto_indent_edits = Vec::new();
4746 let mut edits = Vec::new();
4747 for (edit, prevent_auto_indent) in edits_with_flags {
4748 if prevent_auto_indent {
4749 edits.push(edit);
4750 } else {
4751 auto_indent_edits.push(edit);
4752 }
4753 }
4754 if !edits.is_empty() {
4755 this.edit(edits, cx);
4756 }
4757 if !auto_indent_edits.is_empty() {
4758 this.edit_with_autoindent(auto_indent_edits, cx);
4759 }
4760
4761 let buffer = this.buffer.read(cx).snapshot(cx);
4762 let new_selections = selection_info
4763 .into_iter()
4764 .map(|(extra_newline_inserted, new_selection)| {
4765 let mut cursor = new_selection.end.to_point(&buffer);
4766 if extra_newline_inserted {
4767 cursor.row -= 1;
4768 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4769 }
4770 new_selection.map(|_| cursor)
4771 })
4772 .collect();
4773
4774 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4775 this.refresh_edit_prediction(true, false, window, cx);
4776 });
4777 }
4778
4779 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4780 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4781
4782 let buffer = self.buffer.read(cx);
4783 let snapshot = buffer.snapshot(cx);
4784
4785 let mut edits = Vec::new();
4786 let mut rows = Vec::new();
4787
4788 for (rows_inserted, selection) in self
4789 .selections
4790 .all_adjusted(&self.display_snapshot(cx))
4791 .into_iter()
4792 .enumerate()
4793 {
4794 let cursor = selection.head();
4795 let row = cursor.row;
4796
4797 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4798
4799 let newline = "\n".to_string();
4800 edits.push((start_of_line..start_of_line, newline));
4801
4802 rows.push(row + rows_inserted as u32);
4803 }
4804
4805 self.transact(window, cx, |editor, window, cx| {
4806 editor.edit(edits, cx);
4807
4808 editor.change_selections(Default::default(), window, cx, |s| {
4809 let mut index = 0;
4810 s.move_cursors_with(|map, _, _| {
4811 let row = rows[index];
4812 index += 1;
4813
4814 let point = Point::new(row, 0);
4815 let boundary = map.next_line_boundary(point).1;
4816 let clipped = map.clip_point(boundary, Bias::Left);
4817
4818 (clipped, SelectionGoal::None)
4819 });
4820 });
4821
4822 let mut indent_edits = Vec::new();
4823 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4824 for row in rows {
4825 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4826 for (row, indent) in indents {
4827 if indent.len == 0 {
4828 continue;
4829 }
4830
4831 let text = match indent.kind {
4832 IndentKind::Space => " ".repeat(indent.len as usize),
4833 IndentKind::Tab => "\t".repeat(indent.len as usize),
4834 };
4835 let point = Point::new(row.0, 0);
4836 indent_edits.push((point..point, text));
4837 }
4838 }
4839 editor.edit(indent_edits, cx);
4840 });
4841 }
4842
4843 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4844 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4845
4846 let buffer = self.buffer.read(cx);
4847 let snapshot = buffer.snapshot(cx);
4848
4849 let mut edits = Vec::new();
4850 let mut rows = Vec::new();
4851 let mut rows_inserted = 0;
4852
4853 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
4854 let cursor = selection.head();
4855 let row = cursor.row;
4856
4857 let point = Point::new(row + 1, 0);
4858 let start_of_line = snapshot.clip_point(point, Bias::Left);
4859
4860 let newline = "\n".to_string();
4861 edits.push((start_of_line..start_of_line, newline));
4862
4863 rows_inserted += 1;
4864 rows.push(row + rows_inserted);
4865 }
4866
4867 self.transact(window, cx, |editor, window, cx| {
4868 editor.edit(edits, cx);
4869
4870 editor.change_selections(Default::default(), window, cx, |s| {
4871 let mut index = 0;
4872 s.move_cursors_with(|map, _, _| {
4873 let row = rows[index];
4874 index += 1;
4875
4876 let point = Point::new(row, 0);
4877 let boundary = map.next_line_boundary(point).1;
4878 let clipped = map.clip_point(boundary, Bias::Left);
4879
4880 (clipped, SelectionGoal::None)
4881 });
4882 });
4883
4884 let mut indent_edits = Vec::new();
4885 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4886 for row in rows {
4887 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4888 for (row, indent) in indents {
4889 if indent.len == 0 {
4890 continue;
4891 }
4892
4893 let text = match indent.kind {
4894 IndentKind::Space => " ".repeat(indent.len as usize),
4895 IndentKind::Tab => "\t".repeat(indent.len as usize),
4896 };
4897 let point = Point::new(row.0, 0);
4898 indent_edits.push((point..point, text));
4899 }
4900 }
4901 editor.edit(indent_edits, cx);
4902 });
4903 }
4904
4905 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4906 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4907 original_indent_columns: Vec::new(),
4908 });
4909 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4910 }
4911
4912 fn insert_with_autoindent_mode(
4913 &mut self,
4914 text: &str,
4915 autoindent_mode: Option<AutoindentMode>,
4916 window: &mut Window,
4917 cx: &mut Context<Self>,
4918 ) {
4919 if self.read_only(cx) {
4920 return;
4921 }
4922
4923 let text: Arc<str> = text.into();
4924 self.transact(window, cx, |this, window, cx| {
4925 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
4926 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4927 let anchors = {
4928 let snapshot = buffer.read(cx);
4929 old_selections
4930 .iter()
4931 .map(|s| {
4932 let anchor = snapshot.anchor_after(s.head());
4933 s.map(|_| anchor)
4934 })
4935 .collect::<Vec<_>>()
4936 };
4937 buffer.edit(
4938 old_selections
4939 .iter()
4940 .map(|s| (s.start..s.end, text.clone())),
4941 autoindent_mode,
4942 cx,
4943 );
4944 anchors
4945 });
4946
4947 this.change_selections(Default::default(), window, cx, |s| {
4948 s.select_anchors(selection_anchors);
4949 });
4950
4951 cx.notify();
4952 });
4953 }
4954
4955 fn trigger_completion_on_input(
4956 &mut self,
4957 text: &str,
4958 trigger_in_words: bool,
4959 window: &mut Window,
4960 cx: &mut Context<Self>,
4961 ) {
4962 let completions_source = self
4963 .context_menu
4964 .borrow()
4965 .as_ref()
4966 .and_then(|menu| match menu {
4967 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4968 CodeContextMenu::CodeActions(_) => None,
4969 });
4970
4971 match completions_source {
4972 Some(CompletionsMenuSource::Words { .. }) => {
4973 self.open_or_update_completions_menu(
4974 Some(CompletionsMenuSource::Words {
4975 ignore_threshold: false,
4976 }),
4977 None,
4978 window,
4979 cx,
4980 );
4981 }
4982 Some(CompletionsMenuSource::Normal)
4983 | Some(CompletionsMenuSource::SnippetChoices)
4984 | None
4985 if self.is_completion_trigger(
4986 text,
4987 trigger_in_words,
4988 completions_source.is_some(),
4989 cx,
4990 ) =>
4991 {
4992 self.show_completions(
4993 &ShowCompletions {
4994 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4995 },
4996 window,
4997 cx,
4998 )
4999 }
5000 _ => {
5001 self.hide_context_menu(window, cx);
5002 }
5003 }
5004 }
5005
5006 fn is_completion_trigger(
5007 &self,
5008 text: &str,
5009 trigger_in_words: bool,
5010 menu_is_open: bool,
5011 cx: &mut Context<Self>,
5012 ) -> bool {
5013 let position = self.selections.newest_anchor().head();
5014 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
5015 return false;
5016 };
5017
5018 if let Some(completion_provider) = &self.completion_provider {
5019 completion_provider.is_completion_trigger(
5020 &buffer,
5021 position.text_anchor,
5022 text,
5023 trigger_in_words,
5024 menu_is_open,
5025 cx,
5026 )
5027 } else {
5028 false
5029 }
5030 }
5031
5032 /// If any empty selections is touching the start of its innermost containing autoclose
5033 /// region, expand it to select the brackets.
5034 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5035 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5036 let buffer = self.buffer.read(cx).read(cx);
5037 let new_selections = self
5038 .selections_with_autoclose_regions(selections, &buffer)
5039 .map(|(mut selection, region)| {
5040 if !selection.is_empty() {
5041 return selection;
5042 }
5043
5044 if let Some(region) = region {
5045 let mut range = region.range.to_offset(&buffer);
5046 if selection.start == range.start && range.start >= region.pair.start.len() {
5047 range.start -= region.pair.start.len();
5048 if buffer.contains_str_at(range.start, ®ion.pair.start)
5049 && buffer.contains_str_at(range.end, ®ion.pair.end)
5050 {
5051 range.end += region.pair.end.len();
5052 selection.start = range.start;
5053 selection.end = range.end;
5054
5055 return selection;
5056 }
5057 }
5058 }
5059
5060 let always_treat_brackets_as_autoclosed = buffer
5061 .language_settings_at(selection.start, cx)
5062 .always_treat_brackets_as_autoclosed;
5063
5064 if !always_treat_brackets_as_autoclosed {
5065 return selection;
5066 }
5067
5068 if let Some(scope) = buffer.language_scope_at(selection.start) {
5069 for (pair, enabled) in scope.brackets() {
5070 if !enabled || !pair.close {
5071 continue;
5072 }
5073
5074 if buffer.contains_str_at(selection.start, &pair.end) {
5075 let pair_start_len = pair.start.len();
5076 if buffer.contains_str_at(
5077 selection.start.saturating_sub(pair_start_len),
5078 &pair.start,
5079 ) {
5080 selection.start -= pair_start_len;
5081 selection.end += pair.end.len();
5082
5083 return selection;
5084 }
5085 }
5086 }
5087 }
5088
5089 selection
5090 })
5091 .collect();
5092
5093 drop(buffer);
5094 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5095 selections.select(new_selections)
5096 });
5097 }
5098
5099 /// Iterate the given selections, and for each one, find the smallest surrounding
5100 /// autoclose region. This uses the ordering of the selections and the autoclose
5101 /// regions to avoid repeated comparisons.
5102 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5103 &'a self,
5104 selections: impl IntoIterator<Item = Selection<D>>,
5105 buffer: &'a MultiBufferSnapshot,
5106 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5107 let mut i = 0;
5108 let mut regions = self.autoclose_regions.as_slice();
5109 selections.into_iter().map(move |selection| {
5110 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5111
5112 let mut enclosing = None;
5113 while let Some(pair_state) = regions.get(i) {
5114 if pair_state.range.end.to_offset(buffer) < range.start {
5115 regions = ®ions[i + 1..];
5116 i = 0;
5117 } else if pair_state.range.start.to_offset(buffer) > range.end {
5118 break;
5119 } else {
5120 if pair_state.selection_id == selection.id {
5121 enclosing = Some(pair_state);
5122 }
5123 i += 1;
5124 }
5125 }
5126
5127 (selection, enclosing)
5128 })
5129 }
5130
5131 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5132 fn invalidate_autoclose_regions(
5133 &mut self,
5134 mut selections: &[Selection<Anchor>],
5135 buffer: &MultiBufferSnapshot,
5136 ) {
5137 self.autoclose_regions.retain(|state| {
5138 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5139 return false;
5140 }
5141
5142 let mut i = 0;
5143 while let Some(selection) = selections.get(i) {
5144 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5145 selections = &selections[1..];
5146 continue;
5147 }
5148 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5149 break;
5150 }
5151 if selection.id == state.selection_id {
5152 return true;
5153 } else {
5154 i += 1;
5155 }
5156 }
5157 false
5158 });
5159 }
5160
5161 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5162 let offset = position.to_offset(buffer);
5163 let (word_range, kind) =
5164 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5165 if offset > word_range.start && kind == Some(CharKind::Word) {
5166 Some(
5167 buffer
5168 .text_for_range(word_range.start..offset)
5169 .collect::<String>(),
5170 )
5171 } else {
5172 None
5173 }
5174 }
5175
5176 pub fn toggle_inline_values(
5177 &mut self,
5178 _: &ToggleInlineValues,
5179 _: &mut Window,
5180 cx: &mut Context<Self>,
5181 ) {
5182 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5183
5184 self.refresh_inline_values(cx);
5185 }
5186
5187 pub fn toggle_inlay_hints(
5188 &mut self,
5189 _: &ToggleInlayHints,
5190 _: &mut Window,
5191 cx: &mut Context<Self>,
5192 ) {
5193 self.refresh_inlay_hints(
5194 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5195 cx,
5196 );
5197 }
5198
5199 pub fn inlay_hints_enabled(&self) -> bool {
5200 self.inlay_hint_cache.enabled
5201 }
5202
5203 pub fn inline_values_enabled(&self) -> bool {
5204 self.inline_value_cache.enabled
5205 }
5206
5207 #[cfg(any(test, feature = "test-support"))]
5208 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5209 self.display_map
5210 .read(cx)
5211 .current_inlays()
5212 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5213 .cloned()
5214 .collect()
5215 }
5216
5217 #[cfg(any(test, feature = "test-support"))]
5218 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5219 self.display_map
5220 .read(cx)
5221 .current_inlays()
5222 .cloned()
5223 .collect()
5224 }
5225
5226 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5227 if self.semantics_provider.is_none() || !self.mode.is_full() {
5228 return;
5229 }
5230
5231 let reason_description = reason.description();
5232 let ignore_debounce = matches!(
5233 reason,
5234 InlayHintRefreshReason::SettingsChange(_)
5235 | InlayHintRefreshReason::Toggle(_)
5236 | InlayHintRefreshReason::ExcerptsRemoved(_)
5237 | InlayHintRefreshReason::ModifiersChanged(_)
5238 );
5239 let (invalidate_cache, required_languages) = match reason {
5240 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5241 match self.inlay_hint_cache.modifiers_override(enabled) {
5242 Some(enabled) => {
5243 if enabled {
5244 (InvalidationStrategy::RefreshRequested, None)
5245 } else {
5246 self.clear_inlay_hints(cx);
5247 return;
5248 }
5249 }
5250 None => return,
5251 }
5252 }
5253 InlayHintRefreshReason::Toggle(enabled) => {
5254 if self.inlay_hint_cache.toggle(enabled) {
5255 if enabled {
5256 (InvalidationStrategy::RefreshRequested, None)
5257 } else {
5258 self.clear_inlay_hints(cx);
5259 return;
5260 }
5261 } else {
5262 return;
5263 }
5264 }
5265 InlayHintRefreshReason::SettingsChange(new_settings) => {
5266 match self.inlay_hint_cache.update_settings(
5267 &self.buffer,
5268 new_settings,
5269 self.visible_inlay_hints(cx).cloned().collect::<Vec<_>>(),
5270 cx,
5271 ) {
5272 ControlFlow::Break(Some(InlaySplice {
5273 to_remove,
5274 to_insert,
5275 })) => {
5276 self.splice_inlays(&to_remove, to_insert, cx);
5277 return;
5278 }
5279 ControlFlow::Break(None) => return,
5280 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5281 }
5282 }
5283 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5284 if let Some(InlaySplice {
5285 to_remove,
5286 to_insert,
5287 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5288 {
5289 self.splice_inlays(&to_remove, to_insert, cx);
5290 }
5291 self.display_map.update(cx, |display_map, _| {
5292 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5293 });
5294 return;
5295 }
5296 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5297 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5298 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5299 }
5300 InlayHintRefreshReason::RefreshRequested => {
5301 (InvalidationStrategy::RefreshRequested, None)
5302 }
5303 };
5304
5305 let mut visible_excerpts = self.visible_excerpts(required_languages.as_ref(), cx);
5306 visible_excerpts.retain(|_, (buffer, _, _)| {
5307 self.registered_buffers
5308 .contains_key(&buffer.read(cx).remote_id())
5309 });
5310
5311 if let Some(InlaySplice {
5312 to_remove,
5313 to_insert,
5314 }) = self.inlay_hint_cache.spawn_hint_refresh(
5315 reason_description,
5316 visible_excerpts,
5317 invalidate_cache,
5318 ignore_debounce,
5319 cx,
5320 ) {
5321 self.splice_inlays(&to_remove, to_insert, cx);
5322 }
5323 }
5324
5325 pub fn clear_inlay_hints(&self, cx: &mut Context<Editor>) {
5326 self.splice_inlays(
5327 &self
5328 .visible_inlay_hints(cx)
5329 .map(|inlay| inlay.id)
5330 .collect::<Vec<_>>(),
5331 Vec::new(),
5332 cx,
5333 );
5334 }
5335
5336 fn visible_inlay_hints<'a>(
5337 &'a self,
5338 cx: &'a Context<Editor>,
5339 ) -> impl Iterator<Item = &'a Inlay> {
5340 self.display_map
5341 .read(cx)
5342 .current_inlays()
5343 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5344 }
5345
5346 pub fn visible_excerpts(
5347 &self,
5348 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5349 cx: &mut Context<Editor>,
5350 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5351 let Some(project) = self.project() else {
5352 return HashMap::default();
5353 };
5354 let project = project.read(cx);
5355 let multi_buffer = self.buffer().read(cx);
5356 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5357 let multi_buffer_visible_start = self
5358 .scroll_manager
5359 .anchor()
5360 .anchor
5361 .to_point(&multi_buffer_snapshot);
5362 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5363 multi_buffer_visible_start
5364 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5365 Bias::Left,
5366 );
5367 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5368 multi_buffer_snapshot
5369 .range_to_buffer_ranges(multi_buffer_visible_range)
5370 .into_iter()
5371 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5372 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5373 let buffer_file = project::File::from_dyn(buffer.file())?;
5374 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5375 let worktree_entry = buffer_worktree
5376 .read(cx)
5377 .entry_for_id(buffer_file.project_entry_id()?)?;
5378 if worktree_entry.is_ignored {
5379 return None;
5380 }
5381
5382 let language = buffer.language()?;
5383 if let Some(restrict_to_languages) = restrict_to_languages
5384 && !restrict_to_languages.contains(language)
5385 {
5386 return None;
5387 }
5388 Some((
5389 excerpt_id,
5390 (
5391 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5392 buffer.version().clone(),
5393 excerpt_visible_range,
5394 ),
5395 ))
5396 })
5397 .collect()
5398 }
5399
5400 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5401 TextLayoutDetails {
5402 text_system: window.text_system().clone(),
5403 editor_style: self.style.clone().unwrap(),
5404 rem_size: window.rem_size(),
5405 scroll_anchor: self.scroll_manager.anchor(),
5406 visible_rows: self.visible_line_count(),
5407 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5408 }
5409 }
5410
5411 pub fn splice_inlays(
5412 &self,
5413 to_remove: &[InlayId],
5414 to_insert: Vec<Inlay>,
5415 cx: &mut Context<Self>,
5416 ) {
5417 self.display_map.update(cx, |display_map, cx| {
5418 display_map.splice_inlays(to_remove, to_insert, cx)
5419 });
5420 cx.notify();
5421 }
5422
5423 fn trigger_on_type_formatting(
5424 &self,
5425 input: String,
5426 window: &mut Window,
5427 cx: &mut Context<Self>,
5428 ) -> Option<Task<Result<()>>> {
5429 if input.len() != 1 {
5430 return None;
5431 }
5432
5433 let project = self.project()?;
5434 let position = self.selections.newest_anchor().head();
5435 let (buffer, buffer_position) = self
5436 .buffer
5437 .read(cx)
5438 .text_anchor_for_position(position, cx)?;
5439
5440 let settings = language_settings::language_settings(
5441 buffer
5442 .read(cx)
5443 .language_at(buffer_position)
5444 .map(|l| l.name()),
5445 buffer.read(cx).file(),
5446 cx,
5447 );
5448 if !settings.use_on_type_format {
5449 return None;
5450 }
5451
5452 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5453 // hence we do LSP request & edit on host side only — add formats to host's history.
5454 let push_to_lsp_host_history = true;
5455 // If this is not the host, append its history with new edits.
5456 let push_to_client_history = project.read(cx).is_via_collab();
5457
5458 let on_type_formatting = project.update(cx, |project, cx| {
5459 project.on_type_format(
5460 buffer.clone(),
5461 buffer_position,
5462 input,
5463 push_to_lsp_host_history,
5464 cx,
5465 )
5466 });
5467 Some(cx.spawn_in(window, async move |editor, cx| {
5468 if let Some(transaction) = on_type_formatting.await? {
5469 if push_to_client_history {
5470 buffer
5471 .update(cx, |buffer, _| {
5472 buffer.push_transaction(transaction, Instant::now());
5473 buffer.finalize_last_transaction();
5474 })
5475 .ok();
5476 }
5477 editor.update(cx, |editor, cx| {
5478 editor.refresh_document_highlights(cx);
5479 })?;
5480 }
5481 Ok(())
5482 }))
5483 }
5484
5485 pub fn show_word_completions(
5486 &mut self,
5487 _: &ShowWordCompletions,
5488 window: &mut Window,
5489 cx: &mut Context<Self>,
5490 ) {
5491 self.open_or_update_completions_menu(
5492 Some(CompletionsMenuSource::Words {
5493 ignore_threshold: true,
5494 }),
5495 None,
5496 window,
5497 cx,
5498 );
5499 }
5500
5501 pub fn show_completions(
5502 &mut self,
5503 options: &ShowCompletions,
5504 window: &mut Window,
5505 cx: &mut Context<Self>,
5506 ) {
5507 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5508 }
5509
5510 fn open_or_update_completions_menu(
5511 &mut self,
5512 requested_source: Option<CompletionsMenuSource>,
5513 trigger: Option<&str>,
5514 window: &mut Window,
5515 cx: &mut Context<Self>,
5516 ) {
5517 if self.pending_rename.is_some() {
5518 return;
5519 }
5520
5521 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5522
5523 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5524 // inserted and selected. To handle that case, the start of the selection is used so that
5525 // the menu starts with all choices.
5526 let position = self
5527 .selections
5528 .newest_anchor()
5529 .start
5530 .bias_right(&multibuffer_snapshot);
5531 if position.diff_base_anchor.is_some() {
5532 return;
5533 }
5534 let buffer_position = multibuffer_snapshot.anchor_before(position);
5535 let Some(buffer) = buffer_position
5536 .buffer_id
5537 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5538 else {
5539 return;
5540 };
5541 let buffer_snapshot = buffer.read(cx).snapshot();
5542
5543 let query: Option<Arc<String>> =
5544 Self::completion_query(&multibuffer_snapshot, buffer_position)
5545 .map(|query| query.into());
5546
5547 drop(multibuffer_snapshot);
5548
5549 // Hide the current completions menu when query is empty. Without this, cached
5550 // completions from before the trigger char may be reused (#32774).
5551 if query.is_none() {
5552 let menu_is_open = matches!(
5553 self.context_menu.borrow().as_ref(),
5554 Some(CodeContextMenu::Completions(_))
5555 );
5556 if menu_is_open {
5557 self.hide_context_menu(window, cx);
5558 }
5559 }
5560
5561 let mut ignore_word_threshold = false;
5562 let provider = match requested_source {
5563 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5564 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5565 ignore_word_threshold = ignore_threshold;
5566 None
5567 }
5568 Some(CompletionsMenuSource::SnippetChoices) => {
5569 log::error!("bug: SnippetChoices requested_source is not handled");
5570 None
5571 }
5572 };
5573
5574 let sort_completions = provider
5575 .as_ref()
5576 .is_some_and(|provider| provider.sort_completions());
5577
5578 let filter_completions = provider
5579 .as_ref()
5580 .is_none_or(|provider| provider.filter_completions());
5581
5582 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5583 if filter_completions {
5584 menu.filter(query.clone(), provider.clone(), window, cx);
5585 }
5586 // When `is_incomplete` is false, no need to re-query completions when the current query
5587 // is a suffix of the initial query.
5588 if !menu.is_incomplete {
5589 // If the new query is a suffix of the old query (typing more characters) and
5590 // the previous result was complete, the existing completions can be filtered.
5591 //
5592 // Note that this is always true for snippet completions.
5593 let query_matches = match (&menu.initial_query, &query) {
5594 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5595 (None, _) => true,
5596 _ => false,
5597 };
5598 if query_matches {
5599 let position_matches = if menu.initial_position == position {
5600 true
5601 } else {
5602 let snapshot = self.buffer.read(cx).read(cx);
5603 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5604 };
5605 if position_matches {
5606 return;
5607 }
5608 }
5609 }
5610 };
5611
5612 let trigger_kind = match trigger {
5613 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5614 CompletionTriggerKind::TRIGGER_CHARACTER
5615 }
5616 _ => CompletionTriggerKind::INVOKED,
5617 };
5618 let completion_context = CompletionContext {
5619 trigger_character: trigger.and_then(|trigger| {
5620 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5621 Some(String::from(trigger))
5622 } else {
5623 None
5624 }
5625 }),
5626 trigger_kind,
5627 };
5628
5629 let Anchor {
5630 excerpt_id: buffer_excerpt_id,
5631 text_anchor: buffer_position,
5632 ..
5633 } = buffer_position;
5634
5635 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5636 buffer_snapshot.surrounding_word(buffer_position, None)
5637 {
5638 let word_to_exclude = buffer_snapshot
5639 .text_for_range(word_range.clone())
5640 .collect::<String>();
5641 (
5642 buffer_snapshot.anchor_before(word_range.start)
5643 ..buffer_snapshot.anchor_after(buffer_position),
5644 Some(word_to_exclude),
5645 )
5646 } else {
5647 (buffer_position..buffer_position, None)
5648 };
5649
5650 let language = buffer_snapshot
5651 .language_at(buffer_position)
5652 .map(|language| language.name());
5653
5654 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5655 .completions
5656 .clone();
5657
5658 let show_completion_documentation = buffer_snapshot
5659 .settings_at(buffer_position, cx)
5660 .show_completion_documentation;
5661
5662 // The document can be large, so stay in reasonable bounds when searching for words,
5663 // otherwise completion pop-up might be slow to appear.
5664 const WORD_LOOKUP_ROWS: u32 = 5_000;
5665 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5666 let min_word_search = buffer_snapshot.clip_point(
5667 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5668 Bias::Left,
5669 );
5670 let max_word_search = buffer_snapshot.clip_point(
5671 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5672 Bias::Right,
5673 );
5674 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5675 ..buffer_snapshot.point_to_offset(max_word_search);
5676
5677 let skip_digits = query
5678 .as_ref()
5679 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5680
5681 let omit_word_completions = !self.word_completions_enabled
5682 || (!ignore_word_threshold
5683 && match &query {
5684 Some(query) => query.chars().count() < completion_settings.words_min_length,
5685 None => completion_settings.words_min_length != 0,
5686 });
5687
5688 let (mut words, provider_responses) = match &provider {
5689 Some(provider) => {
5690 let provider_responses = provider.completions(
5691 buffer_excerpt_id,
5692 &buffer,
5693 buffer_position,
5694 completion_context,
5695 window,
5696 cx,
5697 );
5698
5699 let words = match (omit_word_completions, completion_settings.words) {
5700 (true, _) | (_, WordsCompletionMode::Disabled) => {
5701 Task::ready(BTreeMap::default())
5702 }
5703 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5704 .background_spawn(async move {
5705 buffer_snapshot.words_in_range(WordsQuery {
5706 fuzzy_contents: None,
5707 range: word_search_range,
5708 skip_digits,
5709 })
5710 }),
5711 };
5712
5713 (words, provider_responses)
5714 }
5715 None => {
5716 let words = if omit_word_completions {
5717 Task::ready(BTreeMap::default())
5718 } else {
5719 cx.background_spawn(async move {
5720 buffer_snapshot.words_in_range(WordsQuery {
5721 fuzzy_contents: None,
5722 range: word_search_range,
5723 skip_digits,
5724 })
5725 })
5726 };
5727 (words, Task::ready(Ok(Vec::new())))
5728 }
5729 };
5730
5731 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5732
5733 let id = post_inc(&mut self.next_completion_id);
5734 let task = cx.spawn_in(window, async move |editor, cx| {
5735 let Ok(()) = editor.update(cx, |this, _| {
5736 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5737 }) else {
5738 return;
5739 };
5740
5741 // TODO: Ideally completions from different sources would be selectively re-queried, so
5742 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5743 let mut completions = Vec::new();
5744 let mut is_incomplete = false;
5745 let mut display_options: Option<CompletionDisplayOptions> = None;
5746 if let Some(provider_responses) = provider_responses.await.log_err()
5747 && !provider_responses.is_empty()
5748 {
5749 for response in provider_responses {
5750 completions.extend(response.completions);
5751 is_incomplete = is_incomplete || response.is_incomplete;
5752 match display_options.as_mut() {
5753 None => {
5754 display_options = Some(response.display_options);
5755 }
5756 Some(options) => options.merge(&response.display_options),
5757 }
5758 }
5759 if completion_settings.words == WordsCompletionMode::Fallback {
5760 words = Task::ready(BTreeMap::default());
5761 }
5762 }
5763 let display_options = display_options.unwrap_or_default();
5764
5765 let mut words = words.await;
5766 if let Some(word_to_exclude) = &word_to_exclude {
5767 words.remove(word_to_exclude);
5768 }
5769 for lsp_completion in &completions {
5770 words.remove(&lsp_completion.new_text);
5771 }
5772 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5773 replace_range: word_replace_range.clone(),
5774 new_text: word.clone(),
5775 label: CodeLabel::plain(word, None),
5776 icon_path: None,
5777 documentation: None,
5778 source: CompletionSource::BufferWord {
5779 word_range,
5780 resolved: false,
5781 },
5782 insert_text_mode: Some(InsertTextMode::AS_IS),
5783 confirm: None,
5784 }));
5785
5786 let menu = if completions.is_empty() {
5787 None
5788 } else {
5789 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5790 let languages = editor
5791 .workspace
5792 .as_ref()
5793 .and_then(|(workspace, _)| workspace.upgrade())
5794 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5795 let menu = CompletionsMenu::new(
5796 id,
5797 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5798 sort_completions,
5799 show_completion_documentation,
5800 position,
5801 query.clone(),
5802 is_incomplete,
5803 buffer.clone(),
5804 completions.into(),
5805 display_options,
5806 snippet_sort_order,
5807 languages,
5808 language,
5809 cx,
5810 );
5811
5812 let query = if filter_completions { query } else { None };
5813 let matches_task = if let Some(query) = query {
5814 menu.do_async_filtering(query, cx)
5815 } else {
5816 Task::ready(menu.unfiltered_matches())
5817 };
5818 (menu, matches_task)
5819 }) else {
5820 return;
5821 };
5822
5823 let matches = matches_task.await;
5824
5825 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5826 // Newer menu already set, so exit.
5827 if let Some(CodeContextMenu::Completions(prev_menu)) =
5828 editor.context_menu.borrow().as_ref()
5829 && prev_menu.id > id
5830 {
5831 return;
5832 };
5833
5834 // Only valid to take prev_menu because it the new menu is immediately set
5835 // below, or the menu is hidden.
5836 if let Some(CodeContextMenu::Completions(prev_menu)) =
5837 editor.context_menu.borrow_mut().take()
5838 {
5839 let position_matches =
5840 if prev_menu.initial_position == menu.initial_position {
5841 true
5842 } else {
5843 let snapshot = editor.buffer.read(cx).read(cx);
5844 prev_menu.initial_position.to_offset(&snapshot)
5845 == menu.initial_position.to_offset(&snapshot)
5846 };
5847 if position_matches {
5848 // Preserve markdown cache before `set_filter_results` because it will
5849 // try to populate the documentation cache.
5850 menu.preserve_markdown_cache(prev_menu);
5851 }
5852 };
5853
5854 menu.set_filter_results(matches, provider, window, cx);
5855 }) else {
5856 return;
5857 };
5858
5859 menu.visible().then_some(menu)
5860 };
5861
5862 editor
5863 .update_in(cx, |editor, window, cx| {
5864 if editor.focus_handle.is_focused(window)
5865 && let Some(menu) = menu
5866 {
5867 *editor.context_menu.borrow_mut() =
5868 Some(CodeContextMenu::Completions(menu));
5869
5870 crate::hover_popover::hide_hover(editor, cx);
5871 if editor.show_edit_predictions_in_menu() {
5872 editor.update_visible_edit_prediction(window, cx);
5873 } else {
5874 editor.discard_edit_prediction(false, cx);
5875 }
5876
5877 cx.notify();
5878 return;
5879 }
5880
5881 if editor.completion_tasks.len() <= 1 {
5882 // If there are no more completion tasks and the last menu was empty, we should hide it.
5883 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5884 // If it was already hidden and we don't show edit predictions in the menu,
5885 // we should also show the edit prediction when available.
5886 if was_hidden && editor.show_edit_predictions_in_menu() {
5887 editor.update_visible_edit_prediction(window, cx);
5888 }
5889 }
5890 })
5891 .ok();
5892 });
5893
5894 self.completion_tasks.push((id, task));
5895 }
5896
5897 #[cfg(feature = "test-support")]
5898 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5899 let menu = self.context_menu.borrow();
5900 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5901 let completions = menu.completions.borrow();
5902 Some(completions.to_vec())
5903 } else {
5904 None
5905 }
5906 }
5907
5908 pub fn with_completions_menu_matching_id<R>(
5909 &self,
5910 id: CompletionId,
5911 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5912 ) -> R {
5913 let mut context_menu = self.context_menu.borrow_mut();
5914 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5915 return f(None);
5916 };
5917 if completions_menu.id != id {
5918 return f(None);
5919 }
5920 f(Some(completions_menu))
5921 }
5922
5923 pub fn confirm_completion(
5924 &mut self,
5925 action: &ConfirmCompletion,
5926 window: &mut Window,
5927 cx: &mut Context<Self>,
5928 ) -> Option<Task<Result<()>>> {
5929 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5930 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5931 }
5932
5933 pub fn confirm_completion_insert(
5934 &mut self,
5935 _: &ConfirmCompletionInsert,
5936 window: &mut Window,
5937 cx: &mut Context<Self>,
5938 ) -> Option<Task<Result<()>>> {
5939 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5940 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5941 }
5942
5943 pub fn confirm_completion_replace(
5944 &mut self,
5945 _: &ConfirmCompletionReplace,
5946 window: &mut Window,
5947 cx: &mut Context<Self>,
5948 ) -> Option<Task<Result<()>>> {
5949 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5950 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5951 }
5952
5953 pub fn compose_completion(
5954 &mut self,
5955 action: &ComposeCompletion,
5956 window: &mut Window,
5957 cx: &mut Context<Self>,
5958 ) -> Option<Task<Result<()>>> {
5959 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5960 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5961 }
5962
5963 fn do_completion(
5964 &mut self,
5965 item_ix: Option<usize>,
5966 intent: CompletionIntent,
5967 window: &mut Window,
5968 cx: &mut Context<Editor>,
5969 ) -> Option<Task<Result<()>>> {
5970 use language::ToOffset as _;
5971
5972 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5973 else {
5974 return None;
5975 };
5976
5977 let candidate_id = {
5978 let entries = completions_menu.entries.borrow();
5979 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5980 if self.show_edit_predictions_in_menu() {
5981 self.discard_edit_prediction(true, cx);
5982 }
5983 mat.candidate_id
5984 };
5985
5986 let completion = completions_menu
5987 .completions
5988 .borrow()
5989 .get(candidate_id)?
5990 .clone();
5991 cx.stop_propagation();
5992
5993 let buffer_handle = completions_menu.buffer.clone();
5994
5995 let CompletionEdit {
5996 new_text,
5997 snippet,
5998 replace_range,
5999 } = process_completion_for_edit(
6000 &completion,
6001 intent,
6002 &buffer_handle,
6003 &completions_menu.initial_position.text_anchor,
6004 cx,
6005 );
6006
6007 let buffer = buffer_handle.read(cx);
6008 let snapshot = self.buffer.read(cx).snapshot(cx);
6009 let newest_anchor = self.selections.newest_anchor();
6010 let replace_range_multibuffer = {
6011 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6012 excerpt.map_range_from_buffer(replace_range.clone())
6013 };
6014 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6015 return None;
6016 }
6017
6018 let old_text = buffer
6019 .text_for_range(replace_range.clone())
6020 .collect::<String>();
6021 let lookbehind = newest_anchor
6022 .start
6023 .text_anchor
6024 .to_offset(buffer)
6025 .saturating_sub(replace_range.start);
6026 let lookahead = replace_range
6027 .end
6028 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6029 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6030 let suffix = &old_text[lookbehind.min(old_text.len())..];
6031
6032 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
6033 let mut ranges = Vec::new();
6034 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6035
6036 for selection in &selections {
6037 let range = if selection.id == newest_anchor.id {
6038 replace_range_multibuffer.clone()
6039 } else {
6040 let mut range = selection.range();
6041
6042 // if prefix is present, don't duplicate it
6043 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
6044 range.start = range.start.saturating_sub(lookbehind);
6045
6046 // if suffix is also present, mimic the newest cursor and replace it
6047 if selection.id != newest_anchor.id
6048 && snapshot.contains_str_at(range.end, suffix)
6049 {
6050 range.end += lookahead;
6051 }
6052 }
6053 range
6054 };
6055
6056 ranges.push(range.clone());
6057
6058 if !self.linked_edit_ranges.is_empty() {
6059 let start_anchor = snapshot.anchor_before(range.start);
6060 let end_anchor = snapshot.anchor_after(range.end);
6061 if let Some(ranges) = self
6062 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6063 {
6064 for (buffer, edits) in ranges {
6065 linked_edits
6066 .entry(buffer.clone())
6067 .or_default()
6068 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6069 }
6070 }
6071 }
6072 }
6073
6074 let common_prefix_len = old_text
6075 .chars()
6076 .zip(new_text.chars())
6077 .take_while(|(a, b)| a == b)
6078 .map(|(a, _)| a.len_utf8())
6079 .sum::<usize>();
6080
6081 cx.emit(EditorEvent::InputHandled {
6082 utf16_range_to_replace: None,
6083 text: new_text[common_prefix_len..].into(),
6084 });
6085
6086 self.transact(window, cx, |editor, window, cx| {
6087 if let Some(mut snippet) = snippet {
6088 snippet.text = new_text.to_string();
6089 editor
6090 .insert_snippet(&ranges, snippet, window, cx)
6091 .log_err();
6092 } else {
6093 editor.buffer.update(cx, |multi_buffer, cx| {
6094 let auto_indent = match completion.insert_text_mode {
6095 Some(InsertTextMode::AS_IS) => None,
6096 _ => editor.autoindent_mode.clone(),
6097 };
6098 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6099 multi_buffer.edit(edits, auto_indent, cx);
6100 });
6101 }
6102 for (buffer, edits) in linked_edits {
6103 buffer.update(cx, |buffer, cx| {
6104 let snapshot = buffer.snapshot();
6105 let edits = edits
6106 .into_iter()
6107 .map(|(range, text)| {
6108 use text::ToPoint as TP;
6109 let end_point = TP::to_point(&range.end, &snapshot);
6110 let start_point = TP::to_point(&range.start, &snapshot);
6111 (start_point..end_point, text)
6112 })
6113 .sorted_by_key(|(range, _)| range.start);
6114 buffer.edit(edits, None, cx);
6115 })
6116 }
6117
6118 editor.refresh_edit_prediction(true, false, window, cx);
6119 });
6120 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6121
6122 let show_new_completions_on_confirm = completion
6123 .confirm
6124 .as_ref()
6125 .is_some_and(|confirm| confirm(intent, window, cx));
6126 if show_new_completions_on_confirm {
6127 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6128 }
6129
6130 let provider = self.completion_provider.as_ref()?;
6131 drop(completion);
6132 let apply_edits = provider.apply_additional_edits_for_completion(
6133 buffer_handle,
6134 completions_menu.completions.clone(),
6135 candidate_id,
6136 true,
6137 cx,
6138 );
6139
6140 let editor_settings = EditorSettings::get_global(cx);
6141 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6142 // After the code completion is finished, users often want to know what signatures are needed.
6143 // so we should automatically call signature_help
6144 self.show_signature_help(&ShowSignatureHelp, window, cx);
6145 }
6146
6147 Some(cx.foreground_executor().spawn(async move {
6148 apply_edits.await?;
6149 Ok(())
6150 }))
6151 }
6152
6153 pub fn toggle_code_actions(
6154 &mut self,
6155 action: &ToggleCodeActions,
6156 window: &mut Window,
6157 cx: &mut Context<Self>,
6158 ) {
6159 let quick_launch = action.quick_launch;
6160 let mut context_menu = self.context_menu.borrow_mut();
6161 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6162 if code_actions.deployed_from == action.deployed_from {
6163 // Toggle if we're selecting the same one
6164 *context_menu = None;
6165 cx.notify();
6166 return;
6167 } else {
6168 // Otherwise, clear it and start a new one
6169 *context_menu = None;
6170 cx.notify();
6171 }
6172 }
6173 drop(context_menu);
6174 let snapshot = self.snapshot(window, cx);
6175 let deployed_from = action.deployed_from.clone();
6176 let action = action.clone();
6177 self.completion_tasks.clear();
6178 self.discard_edit_prediction(false, cx);
6179
6180 let multibuffer_point = match &action.deployed_from {
6181 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6182 DisplayPoint::new(*row, 0).to_point(&snapshot)
6183 }
6184 _ => self
6185 .selections
6186 .newest::<Point>(&snapshot.display_snapshot)
6187 .head(),
6188 };
6189 let Some((buffer, buffer_row)) = snapshot
6190 .buffer_snapshot()
6191 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6192 .and_then(|(buffer_snapshot, range)| {
6193 self.buffer()
6194 .read(cx)
6195 .buffer(buffer_snapshot.remote_id())
6196 .map(|buffer| (buffer, range.start.row))
6197 })
6198 else {
6199 return;
6200 };
6201 let buffer_id = buffer.read(cx).remote_id();
6202 let tasks = self
6203 .tasks
6204 .get(&(buffer_id, buffer_row))
6205 .map(|t| Arc::new(t.to_owned()));
6206
6207 if !self.focus_handle.is_focused(window) {
6208 return;
6209 }
6210 let project = self.project.clone();
6211
6212 let code_actions_task = match deployed_from {
6213 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6214 _ => self.code_actions(buffer_row, window, cx),
6215 };
6216
6217 let runnable_task = match deployed_from {
6218 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6219 _ => {
6220 let mut task_context_task = Task::ready(None);
6221 if let Some(tasks) = &tasks
6222 && let Some(project) = project
6223 {
6224 task_context_task =
6225 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6226 }
6227
6228 cx.spawn_in(window, {
6229 let buffer = buffer.clone();
6230 async move |editor, cx| {
6231 let task_context = task_context_task.await;
6232
6233 let resolved_tasks =
6234 tasks
6235 .zip(task_context.clone())
6236 .map(|(tasks, task_context)| ResolvedTasks {
6237 templates: tasks.resolve(&task_context).collect(),
6238 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6239 multibuffer_point.row,
6240 tasks.column,
6241 )),
6242 });
6243 let debug_scenarios = editor
6244 .update(cx, |editor, cx| {
6245 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6246 })?
6247 .await;
6248 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6249 }
6250 })
6251 }
6252 };
6253
6254 cx.spawn_in(window, async move |editor, cx| {
6255 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6256 let code_actions = code_actions_task.await;
6257 let spawn_straight_away = quick_launch
6258 && resolved_tasks
6259 .as_ref()
6260 .is_some_and(|tasks| tasks.templates.len() == 1)
6261 && code_actions
6262 .as_ref()
6263 .is_none_or(|actions| actions.is_empty())
6264 && debug_scenarios.is_empty();
6265
6266 editor.update_in(cx, |editor, window, cx| {
6267 crate::hover_popover::hide_hover(editor, cx);
6268 let actions = CodeActionContents::new(
6269 resolved_tasks,
6270 code_actions,
6271 debug_scenarios,
6272 task_context.unwrap_or_default(),
6273 );
6274
6275 // Don't show the menu if there are no actions available
6276 if actions.is_empty() {
6277 cx.notify();
6278 return Task::ready(Ok(()));
6279 }
6280
6281 *editor.context_menu.borrow_mut() =
6282 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6283 buffer,
6284 actions,
6285 selected_item: Default::default(),
6286 scroll_handle: UniformListScrollHandle::default(),
6287 deployed_from,
6288 }));
6289 cx.notify();
6290 if spawn_straight_away
6291 && let Some(task) = editor.confirm_code_action(
6292 &ConfirmCodeAction { item_ix: Some(0) },
6293 window,
6294 cx,
6295 )
6296 {
6297 return task;
6298 }
6299
6300 Task::ready(Ok(()))
6301 })
6302 })
6303 .detach_and_log_err(cx);
6304 }
6305
6306 fn debug_scenarios(
6307 &mut self,
6308 resolved_tasks: &Option<ResolvedTasks>,
6309 buffer: &Entity<Buffer>,
6310 cx: &mut App,
6311 ) -> Task<Vec<task::DebugScenario>> {
6312 maybe!({
6313 let project = self.project()?;
6314 let dap_store = project.read(cx).dap_store();
6315 let mut scenarios = vec![];
6316 let resolved_tasks = resolved_tasks.as_ref()?;
6317 let buffer = buffer.read(cx);
6318 let language = buffer.language()?;
6319 let file = buffer.file();
6320 let debug_adapter = language_settings(language.name().into(), file, cx)
6321 .debuggers
6322 .first()
6323 .map(SharedString::from)
6324 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6325
6326 dap_store.update(cx, |dap_store, cx| {
6327 for (_, task) in &resolved_tasks.templates {
6328 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6329 task.original_task().clone(),
6330 debug_adapter.clone().into(),
6331 task.display_label().to_owned().into(),
6332 cx,
6333 );
6334 scenarios.push(maybe_scenario);
6335 }
6336 });
6337 Some(cx.background_spawn(async move {
6338 futures::future::join_all(scenarios)
6339 .await
6340 .into_iter()
6341 .flatten()
6342 .collect::<Vec<_>>()
6343 }))
6344 })
6345 .unwrap_or_else(|| Task::ready(vec![]))
6346 }
6347
6348 fn code_actions(
6349 &mut self,
6350 buffer_row: u32,
6351 window: &mut Window,
6352 cx: &mut Context<Self>,
6353 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6354 let mut task = self.code_actions_task.take();
6355 cx.spawn_in(window, async move |editor, cx| {
6356 while let Some(prev_task) = task {
6357 prev_task.await.log_err();
6358 task = editor
6359 .update(cx, |this, _| this.code_actions_task.take())
6360 .ok()?;
6361 }
6362
6363 editor
6364 .update(cx, |editor, cx| {
6365 editor
6366 .available_code_actions
6367 .clone()
6368 .and_then(|(location, code_actions)| {
6369 let snapshot = location.buffer.read(cx).snapshot();
6370 let point_range = location.range.to_point(&snapshot);
6371 let point_range = point_range.start.row..=point_range.end.row;
6372 if point_range.contains(&buffer_row) {
6373 Some(code_actions)
6374 } else {
6375 None
6376 }
6377 })
6378 })
6379 .ok()
6380 .flatten()
6381 })
6382 }
6383
6384 pub fn confirm_code_action(
6385 &mut self,
6386 action: &ConfirmCodeAction,
6387 window: &mut Window,
6388 cx: &mut Context<Self>,
6389 ) -> Option<Task<Result<()>>> {
6390 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6391
6392 let actions_menu =
6393 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6394 menu
6395 } else {
6396 return None;
6397 };
6398
6399 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6400 let action = actions_menu.actions.get(action_ix)?;
6401 let title = action.label();
6402 let buffer = actions_menu.buffer;
6403 let workspace = self.workspace()?;
6404
6405 match action {
6406 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6407 workspace.update(cx, |workspace, cx| {
6408 workspace.schedule_resolved_task(
6409 task_source_kind,
6410 resolved_task,
6411 false,
6412 window,
6413 cx,
6414 );
6415
6416 Some(Task::ready(Ok(())))
6417 })
6418 }
6419 CodeActionsItem::CodeAction {
6420 excerpt_id,
6421 action,
6422 provider,
6423 } => {
6424 let apply_code_action =
6425 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6426 let workspace = workspace.downgrade();
6427 Some(cx.spawn_in(window, async move |editor, cx| {
6428 let project_transaction = apply_code_action.await?;
6429 Self::open_project_transaction(
6430 &editor,
6431 workspace,
6432 project_transaction,
6433 title,
6434 cx,
6435 )
6436 .await
6437 }))
6438 }
6439 CodeActionsItem::DebugScenario(scenario) => {
6440 let context = actions_menu.actions.context;
6441
6442 workspace.update(cx, |workspace, cx| {
6443 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6444 workspace.start_debug_session(
6445 scenario,
6446 context,
6447 Some(buffer),
6448 None,
6449 window,
6450 cx,
6451 );
6452 });
6453 Some(Task::ready(Ok(())))
6454 }
6455 }
6456 }
6457
6458 pub async fn open_project_transaction(
6459 editor: &WeakEntity<Editor>,
6460 workspace: WeakEntity<Workspace>,
6461 transaction: ProjectTransaction,
6462 title: String,
6463 cx: &mut AsyncWindowContext,
6464 ) -> Result<()> {
6465 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6466 cx.update(|_, cx| {
6467 entries.sort_unstable_by_key(|(buffer, _)| {
6468 buffer.read(cx).file().map(|f| f.path().clone())
6469 });
6470 })?;
6471 if entries.is_empty() {
6472 return Ok(());
6473 }
6474
6475 // If the project transaction's edits are all contained within this editor, then
6476 // avoid opening a new editor to display them.
6477
6478 if let [(buffer, transaction)] = &*entries {
6479 let excerpt = editor.update(cx, |editor, cx| {
6480 editor
6481 .buffer()
6482 .read(cx)
6483 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6484 })?;
6485 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6486 && excerpted_buffer == *buffer
6487 {
6488 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6489 let excerpt_range = excerpt_range.to_offset(buffer);
6490 buffer
6491 .edited_ranges_for_transaction::<usize>(transaction)
6492 .all(|range| {
6493 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6494 })
6495 })?;
6496
6497 if all_edits_within_excerpt {
6498 return Ok(());
6499 }
6500 }
6501 }
6502
6503 let mut ranges_to_highlight = Vec::new();
6504 let excerpt_buffer = cx.new(|cx| {
6505 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6506 for (buffer_handle, transaction) in &entries {
6507 let edited_ranges = buffer_handle
6508 .read(cx)
6509 .edited_ranges_for_transaction::<Point>(transaction)
6510 .collect::<Vec<_>>();
6511 let (ranges, _) = multibuffer.set_excerpts_for_path(
6512 PathKey::for_buffer(buffer_handle, cx),
6513 buffer_handle.clone(),
6514 edited_ranges,
6515 multibuffer_context_lines(cx),
6516 cx,
6517 );
6518
6519 ranges_to_highlight.extend(ranges);
6520 }
6521 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6522 multibuffer
6523 })?;
6524
6525 workspace.update_in(cx, |workspace, window, cx| {
6526 let project = workspace.project().clone();
6527 let editor =
6528 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6529 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6530 editor.update(cx, |editor, cx| {
6531 editor.highlight_background::<Self>(
6532 &ranges_to_highlight,
6533 |theme| theme.colors().editor_highlighted_line_background,
6534 cx,
6535 );
6536 });
6537 })?;
6538
6539 Ok(())
6540 }
6541
6542 pub fn clear_code_action_providers(&mut self) {
6543 self.code_action_providers.clear();
6544 self.available_code_actions.take();
6545 }
6546
6547 pub fn add_code_action_provider(
6548 &mut self,
6549 provider: Rc<dyn CodeActionProvider>,
6550 window: &mut Window,
6551 cx: &mut Context<Self>,
6552 ) {
6553 if self
6554 .code_action_providers
6555 .iter()
6556 .any(|existing_provider| existing_provider.id() == provider.id())
6557 {
6558 return;
6559 }
6560
6561 self.code_action_providers.push(provider);
6562 self.refresh_code_actions(window, cx);
6563 }
6564
6565 pub fn remove_code_action_provider(
6566 &mut self,
6567 id: Arc<str>,
6568 window: &mut Window,
6569 cx: &mut Context<Self>,
6570 ) {
6571 self.code_action_providers
6572 .retain(|provider| provider.id() != id);
6573 self.refresh_code_actions(window, cx);
6574 }
6575
6576 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6577 !self.code_action_providers.is_empty()
6578 && EditorSettings::get_global(cx).toolbar.code_actions
6579 }
6580
6581 pub fn has_available_code_actions(&self) -> bool {
6582 self.available_code_actions
6583 .as_ref()
6584 .is_some_and(|(_, actions)| !actions.is_empty())
6585 }
6586
6587 fn render_inline_code_actions(
6588 &self,
6589 icon_size: ui::IconSize,
6590 display_row: DisplayRow,
6591 is_active: bool,
6592 cx: &mut Context<Self>,
6593 ) -> AnyElement {
6594 let show_tooltip = !self.context_menu_visible();
6595 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6596 .icon_size(icon_size)
6597 .shape(ui::IconButtonShape::Square)
6598 .icon_color(ui::Color::Hidden)
6599 .toggle_state(is_active)
6600 .when(show_tooltip, |this| {
6601 this.tooltip({
6602 let focus_handle = self.focus_handle.clone();
6603 move |window, cx| {
6604 Tooltip::for_action_in(
6605 "Toggle Code Actions",
6606 &ToggleCodeActions {
6607 deployed_from: None,
6608 quick_launch: false,
6609 },
6610 &focus_handle,
6611 window,
6612 cx,
6613 )
6614 }
6615 })
6616 })
6617 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6618 window.focus(&editor.focus_handle(cx));
6619 editor.toggle_code_actions(
6620 &crate::actions::ToggleCodeActions {
6621 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6622 display_row,
6623 )),
6624 quick_launch: false,
6625 },
6626 window,
6627 cx,
6628 );
6629 }))
6630 .into_any_element()
6631 }
6632
6633 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6634 &self.context_menu
6635 }
6636
6637 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6638 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6639 cx.background_executor()
6640 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6641 .await;
6642
6643 let (start_buffer, start, _, end, newest_selection) = this
6644 .update(cx, |this, cx| {
6645 let newest_selection = this.selections.newest_anchor().clone();
6646 if newest_selection.head().diff_base_anchor.is_some() {
6647 return None;
6648 }
6649 let display_snapshot = this.display_snapshot(cx);
6650 let newest_selection_adjusted =
6651 this.selections.newest_adjusted(&display_snapshot);
6652 let buffer = this.buffer.read(cx);
6653
6654 let (start_buffer, start) =
6655 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6656 let (end_buffer, end) =
6657 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6658
6659 Some((start_buffer, start, end_buffer, end, newest_selection))
6660 })?
6661 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6662 .context(
6663 "Expected selection to lie in a single buffer when refreshing code actions",
6664 )?;
6665 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6666 let providers = this.code_action_providers.clone();
6667 let tasks = this
6668 .code_action_providers
6669 .iter()
6670 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6671 .collect::<Vec<_>>();
6672 (providers, tasks)
6673 })?;
6674
6675 let mut actions = Vec::new();
6676 for (provider, provider_actions) in
6677 providers.into_iter().zip(future::join_all(tasks).await)
6678 {
6679 if let Some(provider_actions) = provider_actions.log_err() {
6680 actions.extend(provider_actions.into_iter().map(|action| {
6681 AvailableCodeAction {
6682 excerpt_id: newest_selection.start.excerpt_id,
6683 action,
6684 provider: provider.clone(),
6685 }
6686 }));
6687 }
6688 }
6689
6690 this.update(cx, |this, cx| {
6691 this.available_code_actions = if actions.is_empty() {
6692 None
6693 } else {
6694 Some((
6695 Location {
6696 buffer: start_buffer,
6697 range: start..end,
6698 },
6699 actions.into(),
6700 ))
6701 };
6702 cx.notify();
6703 })
6704 }));
6705 }
6706
6707 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6708 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6709 self.show_git_blame_inline = false;
6710
6711 self.show_git_blame_inline_delay_task =
6712 Some(cx.spawn_in(window, async move |this, cx| {
6713 cx.background_executor().timer(delay).await;
6714
6715 this.update(cx, |this, cx| {
6716 this.show_git_blame_inline = true;
6717 cx.notify();
6718 })
6719 .log_err();
6720 }));
6721 }
6722 }
6723
6724 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6725 let snapshot = self.snapshot(window, cx);
6726 let cursor = self
6727 .selections
6728 .newest::<Point>(&snapshot.display_snapshot)
6729 .head();
6730 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6731 else {
6732 return;
6733 };
6734
6735 let Some(blame) = self.blame.as_ref() else {
6736 return;
6737 };
6738
6739 let row_info = RowInfo {
6740 buffer_id: Some(buffer.remote_id()),
6741 buffer_row: Some(point.row),
6742 ..Default::default()
6743 };
6744 let Some((buffer, blame_entry)) = blame
6745 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6746 .flatten()
6747 else {
6748 return;
6749 };
6750
6751 let anchor = self.selections.newest_anchor().head();
6752 let position = self.to_pixel_point(anchor, &snapshot, window);
6753 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6754 self.show_blame_popover(
6755 buffer,
6756 &blame_entry,
6757 position + last_bounds.origin,
6758 true,
6759 cx,
6760 );
6761 };
6762 }
6763
6764 fn show_blame_popover(
6765 &mut self,
6766 buffer: BufferId,
6767 blame_entry: &BlameEntry,
6768 position: gpui::Point<Pixels>,
6769 ignore_timeout: bool,
6770 cx: &mut Context<Self>,
6771 ) {
6772 if let Some(state) = &mut self.inline_blame_popover {
6773 state.hide_task.take();
6774 } else {
6775 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6776 let blame_entry = blame_entry.clone();
6777 let show_task = cx.spawn(async move |editor, cx| {
6778 if !ignore_timeout {
6779 cx.background_executor()
6780 .timer(std::time::Duration::from_millis(blame_popover_delay))
6781 .await;
6782 }
6783 editor
6784 .update(cx, |editor, cx| {
6785 editor.inline_blame_popover_show_task.take();
6786 let Some(blame) = editor.blame.as_ref() else {
6787 return;
6788 };
6789 let blame = blame.read(cx);
6790 let details = blame.details_for_entry(buffer, &blame_entry);
6791 let markdown = cx.new(|cx| {
6792 Markdown::new(
6793 details
6794 .as_ref()
6795 .map(|message| message.message.clone())
6796 .unwrap_or_default(),
6797 None,
6798 None,
6799 cx,
6800 )
6801 });
6802 editor.inline_blame_popover = Some(InlineBlamePopover {
6803 position,
6804 hide_task: None,
6805 popover_bounds: None,
6806 popover_state: InlineBlamePopoverState {
6807 scroll_handle: ScrollHandle::new(),
6808 commit_message: details,
6809 markdown,
6810 },
6811 keyboard_grace: ignore_timeout,
6812 });
6813 cx.notify();
6814 })
6815 .ok();
6816 });
6817 self.inline_blame_popover_show_task = Some(show_task);
6818 }
6819 }
6820
6821 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6822 self.inline_blame_popover_show_task.take();
6823 if let Some(state) = &mut self.inline_blame_popover {
6824 let hide_task = cx.spawn(async move |editor, cx| {
6825 cx.background_executor()
6826 .timer(std::time::Duration::from_millis(100))
6827 .await;
6828 editor
6829 .update(cx, |editor, cx| {
6830 editor.inline_blame_popover.take();
6831 cx.notify();
6832 })
6833 .ok();
6834 });
6835 state.hide_task = Some(hide_task);
6836 }
6837 }
6838
6839 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6840 if self.pending_rename.is_some() {
6841 return None;
6842 }
6843
6844 let provider = self.semantics_provider.clone()?;
6845 let buffer = self.buffer.read(cx);
6846 let newest_selection = self.selections.newest_anchor().clone();
6847 let cursor_position = newest_selection.head();
6848 let (cursor_buffer, cursor_buffer_position) =
6849 buffer.text_anchor_for_position(cursor_position, cx)?;
6850 let (tail_buffer, tail_buffer_position) =
6851 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6852 if cursor_buffer != tail_buffer {
6853 return None;
6854 }
6855
6856 let snapshot = cursor_buffer.read(cx).snapshot();
6857 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6858 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6859 if start_word_range != end_word_range {
6860 self.document_highlights_task.take();
6861 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6862 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6863 return None;
6864 }
6865
6866 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6867 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6868 cx.background_executor()
6869 .timer(Duration::from_millis(debounce))
6870 .await;
6871
6872 let highlights = if let Some(highlights) = cx
6873 .update(|cx| {
6874 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6875 })
6876 .ok()
6877 .flatten()
6878 {
6879 highlights.await.log_err()
6880 } else {
6881 None
6882 };
6883
6884 if let Some(highlights) = highlights {
6885 this.update(cx, |this, cx| {
6886 if this.pending_rename.is_some() {
6887 return;
6888 }
6889
6890 let buffer = this.buffer.read(cx);
6891 if buffer
6892 .text_anchor_for_position(cursor_position, cx)
6893 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6894 {
6895 return;
6896 }
6897
6898 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6899 let mut write_ranges = Vec::new();
6900 let mut read_ranges = Vec::new();
6901 for highlight in highlights {
6902 let buffer_id = cursor_buffer.read(cx).remote_id();
6903 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6904 {
6905 let start = highlight
6906 .range
6907 .start
6908 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6909 let end = highlight
6910 .range
6911 .end
6912 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6913 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6914 continue;
6915 }
6916
6917 let range =
6918 Anchor::range_in_buffer(excerpt_id, buffer_id, *start..*end);
6919 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6920 write_ranges.push(range);
6921 } else {
6922 read_ranges.push(range);
6923 }
6924 }
6925 }
6926
6927 this.highlight_background::<DocumentHighlightRead>(
6928 &read_ranges,
6929 |theme| theme.colors().editor_document_highlight_read_background,
6930 cx,
6931 );
6932 this.highlight_background::<DocumentHighlightWrite>(
6933 &write_ranges,
6934 |theme| theme.colors().editor_document_highlight_write_background,
6935 cx,
6936 );
6937 cx.notify();
6938 })
6939 .log_err();
6940 }
6941 }));
6942 None
6943 }
6944
6945 fn prepare_highlight_query_from_selection(
6946 &mut self,
6947 cx: &mut Context<Editor>,
6948 ) -> Option<(String, Range<Anchor>)> {
6949 if matches!(self.mode, EditorMode::SingleLine) {
6950 return None;
6951 }
6952 if !EditorSettings::get_global(cx).selection_highlight {
6953 return None;
6954 }
6955 if self.selections.count() != 1 || self.selections.line_mode() {
6956 return None;
6957 }
6958 let selection = self.selections.newest_anchor();
6959 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6960 let selection_point_range = selection.start.to_point(&multi_buffer_snapshot)
6961 ..selection.end.to_point(&multi_buffer_snapshot);
6962 // If the selection spans multiple rows OR it is empty
6963 if selection_point_range.start.row != selection_point_range.end.row
6964 || selection_point_range.start.column == selection_point_range.end.column
6965 {
6966 return None;
6967 }
6968
6969 let query = multi_buffer_snapshot
6970 .text_for_range(selection.range())
6971 .collect::<String>();
6972 if query.trim().is_empty() {
6973 return None;
6974 }
6975 Some((query, selection.range()))
6976 }
6977
6978 fn update_selection_occurrence_highlights(
6979 &mut self,
6980 query_text: String,
6981 query_range: Range<Anchor>,
6982 multi_buffer_range_to_query: Range<Point>,
6983 use_debounce: bool,
6984 window: &mut Window,
6985 cx: &mut Context<Editor>,
6986 ) -> Task<()> {
6987 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6988 cx.spawn_in(window, async move |editor, cx| {
6989 if use_debounce {
6990 cx.background_executor()
6991 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6992 .await;
6993 }
6994 let match_task = cx.background_spawn(async move {
6995 let buffer_ranges = multi_buffer_snapshot
6996 .range_to_buffer_ranges(multi_buffer_range_to_query)
6997 .into_iter()
6998 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6999 let mut match_ranges = Vec::new();
7000 let Ok(regex) = project::search::SearchQuery::text(
7001 query_text.clone(),
7002 false,
7003 false,
7004 false,
7005 Default::default(),
7006 Default::default(),
7007 false,
7008 None,
7009 ) else {
7010 return Vec::default();
7011 };
7012 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7013 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7014 match_ranges.extend(
7015 regex
7016 .search(buffer_snapshot, Some(search_range.clone()))
7017 .await
7018 .into_iter()
7019 .filter_map(|match_range| {
7020 let match_start = buffer_snapshot
7021 .anchor_after(search_range.start + match_range.start);
7022 let match_end = buffer_snapshot
7023 .anchor_before(search_range.start + match_range.end);
7024 let match_anchor_range = Anchor::range_in_buffer(
7025 excerpt_id,
7026 buffer_snapshot.remote_id(),
7027 match_start..match_end,
7028 );
7029 (match_anchor_range != query_range).then_some(match_anchor_range)
7030 }),
7031 );
7032 }
7033 match_ranges
7034 });
7035 let match_ranges = match_task.await;
7036 editor
7037 .update_in(cx, |editor, _, cx| {
7038 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7039 if !match_ranges.is_empty() {
7040 editor.highlight_background::<SelectedTextHighlight>(
7041 &match_ranges,
7042 |theme| theme.colors().editor_document_highlight_bracket_background,
7043 cx,
7044 )
7045 }
7046 })
7047 .log_err();
7048 })
7049 }
7050
7051 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7052 struct NewlineFold;
7053 let type_id = std::any::TypeId::of::<NewlineFold>();
7054 if !self.mode.is_single_line() {
7055 return;
7056 }
7057 let snapshot = self.snapshot(window, cx);
7058 if snapshot.buffer_snapshot().max_point().row == 0 {
7059 return;
7060 }
7061 let task = cx.background_spawn(async move {
7062 let new_newlines = snapshot
7063 .buffer_chars_at(0)
7064 .filter_map(|(c, i)| {
7065 if c == '\n' {
7066 Some(
7067 snapshot.buffer_snapshot().anchor_after(i)
7068 ..snapshot.buffer_snapshot().anchor_before(i + 1),
7069 )
7070 } else {
7071 None
7072 }
7073 })
7074 .collect::<Vec<_>>();
7075 let existing_newlines = snapshot
7076 .folds_in_range(0..snapshot.buffer_snapshot().len())
7077 .filter_map(|fold| {
7078 if fold.placeholder.type_tag == Some(type_id) {
7079 Some(fold.range.start..fold.range.end)
7080 } else {
7081 None
7082 }
7083 })
7084 .collect::<Vec<_>>();
7085
7086 (new_newlines, existing_newlines)
7087 });
7088 self.folding_newlines = cx.spawn(async move |this, cx| {
7089 let (new_newlines, existing_newlines) = task.await;
7090 if new_newlines == existing_newlines {
7091 return;
7092 }
7093 let placeholder = FoldPlaceholder {
7094 render: Arc::new(move |_, _, cx| {
7095 div()
7096 .bg(cx.theme().status().hint_background)
7097 .border_b_1()
7098 .size_full()
7099 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7100 .border_color(cx.theme().status().hint)
7101 .child("\\n")
7102 .into_any()
7103 }),
7104 constrain_width: false,
7105 merge_adjacent: false,
7106 type_tag: Some(type_id),
7107 };
7108 let creases = new_newlines
7109 .into_iter()
7110 .map(|range| Crease::simple(range, placeholder.clone()))
7111 .collect();
7112 this.update(cx, |this, cx| {
7113 this.display_map.update(cx, |display_map, cx| {
7114 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7115 display_map.fold(creases, cx);
7116 });
7117 })
7118 .ok();
7119 });
7120 }
7121
7122 fn refresh_selected_text_highlights(
7123 &mut self,
7124 on_buffer_edit: bool,
7125 window: &mut Window,
7126 cx: &mut Context<Editor>,
7127 ) {
7128 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
7129 else {
7130 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7131 self.quick_selection_highlight_task.take();
7132 self.debounced_selection_highlight_task.take();
7133 return;
7134 };
7135 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7136 if on_buffer_edit
7137 || self
7138 .quick_selection_highlight_task
7139 .as_ref()
7140 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7141 {
7142 let multi_buffer_visible_start = self
7143 .scroll_manager
7144 .anchor()
7145 .anchor
7146 .to_point(&multi_buffer_snapshot);
7147 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7148 multi_buffer_visible_start
7149 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7150 Bias::Left,
7151 );
7152 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7153 self.quick_selection_highlight_task = Some((
7154 query_range.clone(),
7155 self.update_selection_occurrence_highlights(
7156 query_text.clone(),
7157 query_range.clone(),
7158 multi_buffer_visible_range,
7159 false,
7160 window,
7161 cx,
7162 ),
7163 ));
7164 }
7165 if on_buffer_edit
7166 || self
7167 .debounced_selection_highlight_task
7168 .as_ref()
7169 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7170 {
7171 let multi_buffer_start = multi_buffer_snapshot
7172 .anchor_before(0)
7173 .to_point(&multi_buffer_snapshot);
7174 let multi_buffer_end = multi_buffer_snapshot
7175 .anchor_after(multi_buffer_snapshot.len())
7176 .to_point(&multi_buffer_snapshot);
7177 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7178 self.debounced_selection_highlight_task = Some((
7179 query_range.clone(),
7180 self.update_selection_occurrence_highlights(
7181 query_text,
7182 query_range,
7183 multi_buffer_full_range,
7184 true,
7185 window,
7186 cx,
7187 ),
7188 ));
7189 }
7190 }
7191
7192 pub fn refresh_edit_prediction(
7193 &mut self,
7194 debounce: bool,
7195 user_requested: bool,
7196 window: &mut Window,
7197 cx: &mut Context<Self>,
7198 ) -> Option<()> {
7199 if DisableAiSettings::get_global(cx).disable_ai {
7200 return None;
7201 }
7202
7203 let provider = self.edit_prediction_provider()?;
7204 let cursor = self.selections.newest_anchor().head();
7205 let (buffer, cursor_buffer_position) =
7206 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7207
7208 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7209 self.discard_edit_prediction(false, cx);
7210 return None;
7211 }
7212
7213 self.update_visible_edit_prediction(window, cx);
7214
7215 if !user_requested
7216 && (!self.should_show_edit_predictions()
7217 || !self.is_focused(window)
7218 || buffer.read(cx).is_empty())
7219 {
7220 self.discard_edit_prediction(false, cx);
7221 return None;
7222 }
7223
7224 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7225 Some(())
7226 }
7227
7228 fn show_edit_predictions_in_menu(&self) -> bool {
7229 match self.edit_prediction_settings {
7230 EditPredictionSettings::Disabled => false,
7231 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7232 }
7233 }
7234
7235 pub fn edit_predictions_enabled(&self) -> bool {
7236 match self.edit_prediction_settings {
7237 EditPredictionSettings::Disabled => false,
7238 EditPredictionSettings::Enabled { .. } => true,
7239 }
7240 }
7241
7242 fn edit_prediction_requires_modifier(&self) -> bool {
7243 match self.edit_prediction_settings {
7244 EditPredictionSettings::Disabled => false,
7245 EditPredictionSettings::Enabled {
7246 preview_requires_modifier,
7247 ..
7248 } => preview_requires_modifier,
7249 }
7250 }
7251
7252 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7253 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7254 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7255 self.discard_edit_prediction(false, cx);
7256 } else {
7257 let selection = self.selections.newest_anchor();
7258 let cursor = selection.head();
7259
7260 if let Some((buffer, cursor_buffer_position)) =
7261 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7262 {
7263 self.edit_prediction_settings =
7264 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7265 }
7266 }
7267 }
7268
7269 fn edit_prediction_settings_at_position(
7270 &self,
7271 buffer: &Entity<Buffer>,
7272 buffer_position: language::Anchor,
7273 cx: &App,
7274 ) -> EditPredictionSettings {
7275 if !self.mode.is_full()
7276 || !self.show_edit_predictions_override.unwrap_or(true)
7277 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7278 {
7279 return EditPredictionSettings::Disabled;
7280 }
7281
7282 let buffer = buffer.read(cx);
7283
7284 let file = buffer.file();
7285
7286 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7287 return EditPredictionSettings::Disabled;
7288 };
7289
7290 let by_provider = matches!(
7291 self.menu_edit_predictions_policy,
7292 MenuEditPredictionsPolicy::ByProvider
7293 );
7294
7295 let show_in_menu = by_provider
7296 && self
7297 .edit_prediction_provider
7298 .as_ref()
7299 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7300
7301 let preview_requires_modifier =
7302 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7303
7304 EditPredictionSettings::Enabled {
7305 show_in_menu,
7306 preview_requires_modifier,
7307 }
7308 }
7309
7310 fn should_show_edit_predictions(&self) -> bool {
7311 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7312 }
7313
7314 pub fn edit_prediction_preview_is_active(&self) -> bool {
7315 matches!(
7316 self.edit_prediction_preview,
7317 EditPredictionPreview::Active { .. }
7318 )
7319 }
7320
7321 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7322 let cursor = self.selections.newest_anchor().head();
7323 if let Some((buffer, cursor_position)) =
7324 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7325 {
7326 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7327 } else {
7328 false
7329 }
7330 }
7331
7332 pub fn supports_minimap(&self, cx: &App) -> bool {
7333 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7334 }
7335
7336 fn edit_predictions_enabled_in_buffer(
7337 &self,
7338 buffer: &Entity<Buffer>,
7339 buffer_position: language::Anchor,
7340 cx: &App,
7341 ) -> bool {
7342 maybe!({
7343 if self.read_only(cx) {
7344 return Some(false);
7345 }
7346 let provider = self.edit_prediction_provider()?;
7347 if !provider.is_enabled(buffer, buffer_position, cx) {
7348 return Some(false);
7349 }
7350 let buffer = buffer.read(cx);
7351 let Some(file) = buffer.file() else {
7352 return Some(true);
7353 };
7354 let settings = all_language_settings(Some(file), cx);
7355 Some(settings.edit_predictions_enabled_for_file(file, cx))
7356 })
7357 .unwrap_or(false)
7358 }
7359
7360 fn cycle_edit_prediction(
7361 &mut self,
7362 direction: Direction,
7363 window: &mut Window,
7364 cx: &mut Context<Self>,
7365 ) -> Option<()> {
7366 let provider = self.edit_prediction_provider()?;
7367 let cursor = self.selections.newest_anchor().head();
7368 let (buffer, cursor_buffer_position) =
7369 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7370 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7371 return None;
7372 }
7373
7374 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7375 self.update_visible_edit_prediction(window, cx);
7376
7377 Some(())
7378 }
7379
7380 pub fn show_edit_prediction(
7381 &mut self,
7382 _: &ShowEditPrediction,
7383 window: &mut Window,
7384 cx: &mut Context<Self>,
7385 ) {
7386 if !self.has_active_edit_prediction() {
7387 self.refresh_edit_prediction(false, true, window, cx);
7388 return;
7389 }
7390
7391 self.update_visible_edit_prediction(window, cx);
7392 }
7393
7394 pub fn display_cursor_names(
7395 &mut self,
7396 _: &DisplayCursorNames,
7397 window: &mut Window,
7398 cx: &mut Context<Self>,
7399 ) {
7400 self.show_cursor_names(window, cx);
7401 }
7402
7403 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7404 self.show_cursor_names = true;
7405 cx.notify();
7406 cx.spawn_in(window, async move |this, cx| {
7407 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7408 this.update(cx, |this, cx| {
7409 this.show_cursor_names = false;
7410 cx.notify()
7411 })
7412 .ok()
7413 })
7414 .detach();
7415 }
7416
7417 pub fn next_edit_prediction(
7418 &mut self,
7419 _: &NextEditPrediction,
7420 window: &mut Window,
7421 cx: &mut Context<Self>,
7422 ) {
7423 if self.has_active_edit_prediction() {
7424 self.cycle_edit_prediction(Direction::Next, window, cx);
7425 } else {
7426 let is_copilot_disabled = self
7427 .refresh_edit_prediction(false, true, window, cx)
7428 .is_none();
7429 if is_copilot_disabled {
7430 cx.propagate();
7431 }
7432 }
7433 }
7434
7435 pub fn previous_edit_prediction(
7436 &mut self,
7437 _: &PreviousEditPrediction,
7438 window: &mut Window,
7439 cx: &mut Context<Self>,
7440 ) {
7441 if self.has_active_edit_prediction() {
7442 self.cycle_edit_prediction(Direction::Prev, window, cx);
7443 } else {
7444 let is_copilot_disabled = self
7445 .refresh_edit_prediction(false, true, window, cx)
7446 .is_none();
7447 if is_copilot_disabled {
7448 cx.propagate();
7449 }
7450 }
7451 }
7452
7453 pub fn accept_edit_prediction(
7454 &mut self,
7455 _: &AcceptEditPrediction,
7456 window: &mut Window,
7457 cx: &mut Context<Self>,
7458 ) {
7459 if self.show_edit_predictions_in_menu() {
7460 self.hide_context_menu(window, cx);
7461 }
7462
7463 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7464 return;
7465 };
7466
7467 match &active_edit_prediction.completion {
7468 EditPrediction::MoveWithin { target, .. } => {
7469 let target = *target;
7470
7471 if let Some(position_map) = &self.last_position_map {
7472 if position_map
7473 .visible_row_range
7474 .contains(&target.to_display_point(&position_map.snapshot).row())
7475 || !self.edit_prediction_requires_modifier()
7476 {
7477 self.unfold_ranges(&[target..target], true, false, cx);
7478 // Note that this is also done in vim's handler of the Tab action.
7479 self.change_selections(
7480 SelectionEffects::scroll(Autoscroll::newest()),
7481 window,
7482 cx,
7483 |selections| {
7484 selections.select_anchor_ranges([target..target]);
7485 },
7486 );
7487 self.clear_row_highlights::<EditPredictionPreview>();
7488
7489 self.edit_prediction_preview
7490 .set_previous_scroll_position(None);
7491 } else {
7492 self.edit_prediction_preview
7493 .set_previous_scroll_position(Some(
7494 position_map.snapshot.scroll_anchor,
7495 ));
7496
7497 self.highlight_rows::<EditPredictionPreview>(
7498 target..target,
7499 cx.theme().colors().editor_highlighted_line_background,
7500 RowHighlightOptions {
7501 autoscroll: true,
7502 ..Default::default()
7503 },
7504 cx,
7505 );
7506 self.request_autoscroll(Autoscroll::fit(), cx);
7507 }
7508 }
7509 }
7510 EditPrediction::MoveOutside { snapshot, target } => {
7511 if let Some(workspace) = self.workspace() {
7512 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7513 .detach_and_log_err(cx);
7514 }
7515 }
7516 EditPrediction::Edit { edits, .. } => {
7517 self.report_edit_prediction_event(
7518 active_edit_prediction.completion_id.clone(),
7519 true,
7520 cx,
7521 );
7522
7523 if let Some(provider) = self.edit_prediction_provider() {
7524 provider.accept(cx);
7525 }
7526
7527 // Store the transaction ID and selections before applying the edit
7528 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7529
7530 let snapshot = self.buffer.read(cx).snapshot(cx);
7531 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7532
7533 self.buffer.update(cx, |buffer, cx| {
7534 buffer.edit(edits.iter().cloned(), None, cx)
7535 });
7536
7537 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7538 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7539 });
7540
7541 let selections = self.selections.disjoint_anchors_arc();
7542 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7543 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7544 if has_new_transaction {
7545 self.selection_history
7546 .insert_transaction(transaction_id_now, selections);
7547 }
7548 }
7549
7550 self.update_visible_edit_prediction(window, cx);
7551 if self.active_edit_prediction.is_none() {
7552 self.refresh_edit_prediction(true, true, window, cx);
7553 }
7554
7555 cx.notify();
7556 }
7557 }
7558
7559 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7560 }
7561
7562 pub fn accept_partial_edit_prediction(
7563 &mut self,
7564 _: &AcceptPartialEditPrediction,
7565 window: &mut Window,
7566 cx: &mut Context<Self>,
7567 ) {
7568 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7569 return;
7570 };
7571 if self.selections.count() != 1 {
7572 return;
7573 }
7574
7575 match &active_edit_prediction.completion {
7576 EditPrediction::MoveWithin { target, .. } => {
7577 let target = *target;
7578 self.change_selections(
7579 SelectionEffects::scroll(Autoscroll::newest()),
7580 window,
7581 cx,
7582 |selections| {
7583 selections.select_anchor_ranges([target..target]);
7584 },
7585 );
7586 }
7587 EditPrediction::MoveOutside { snapshot, target } => {
7588 if let Some(workspace) = self.workspace() {
7589 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7590 .detach_and_log_err(cx);
7591 }
7592 }
7593 EditPrediction::Edit { edits, .. } => {
7594 self.report_edit_prediction_event(
7595 active_edit_prediction.completion_id.clone(),
7596 true,
7597 cx,
7598 );
7599
7600 // Find an insertion that starts at the cursor position.
7601 let snapshot = self.buffer.read(cx).snapshot(cx);
7602 let cursor_offset = self
7603 .selections
7604 .newest::<usize>(&self.display_snapshot(cx))
7605 .head();
7606 let insertion = edits.iter().find_map(|(range, text)| {
7607 let range = range.to_offset(&snapshot);
7608 if range.is_empty() && range.start == cursor_offset {
7609 Some(text)
7610 } else {
7611 None
7612 }
7613 });
7614
7615 if let Some(text) = insertion {
7616 let mut partial_completion = text
7617 .chars()
7618 .by_ref()
7619 .take_while(|c| c.is_alphabetic())
7620 .collect::<String>();
7621 if partial_completion.is_empty() {
7622 partial_completion = text
7623 .chars()
7624 .by_ref()
7625 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7626 .collect::<String>();
7627 }
7628
7629 cx.emit(EditorEvent::InputHandled {
7630 utf16_range_to_replace: None,
7631 text: partial_completion.clone().into(),
7632 });
7633
7634 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7635
7636 self.refresh_edit_prediction(true, true, window, cx);
7637 cx.notify();
7638 } else {
7639 self.accept_edit_prediction(&Default::default(), window, cx);
7640 }
7641 }
7642 }
7643 }
7644
7645 fn discard_edit_prediction(
7646 &mut self,
7647 should_report_edit_prediction_event: bool,
7648 cx: &mut Context<Self>,
7649 ) -> bool {
7650 if should_report_edit_prediction_event {
7651 let completion_id = self
7652 .active_edit_prediction
7653 .as_ref()
7654 .and_then(|active_completion| active_completion.completion_id.clone());
7655
7656 self.report_edit_prediction_event(completion_id, false, cx);
7657 }
7658
7659 if let Some(provider) = self.edit_prediction_provider() {
7660 provider.discard(cx);
7661 }
7662
7663 self.take_active_edit_prediction(cx)
7664 }
7665
7666 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7667 let Some(provider) = self.edit_prediction_provider() else {
7668 return;
7669 };
7670
7671 let Some((_, buffer, _)) = self
7672 .buffer
7673 .read(cx)
7674 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7675 else {
7676 return;
7677 };
7678
7679 let extension = buffer
7680 .read(cx)
7681 .file()
7682 .and_then(|file| Some(file.path().extension()?.to_string()));
7683
7684 let event_type = match accepted {
7685 true => "Edit Prediction Accepted",
7686 false => "Edit Prediction Discarded",
7687 };
7688 telemetry::event!(
7689 event_type,
7690 provider = provider.name(),
7691 prediction_id = id,
7692 suggestion_accepted = accepted,
7693 file_extension = extension,
7694 );
7695 }
7696
7697 fn open_editor_at_anchor(
7698 snapshot: &language::BufferSnapshot,
7699 target: language::Anchor,
7700 workspace: &Entity<Workspace>,
7701 window: &mut Window,
7702 cx: &mut App,
7703 ) -> Task<Result<()>> {
7704 workspace.update(cx, |workspace, cx| {
7705 let path = snapshot.file().map(|file| file.full_path(cx));
7706 let Some(path) =
7707 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7708 else {
7709 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7710 };
7711 let target = text::ToPoint::to_point(&target, snapshot);
7712 let item = workspace.open_path(path, None, true, window, cx);
7713 window.spawn(cx, async move |cx| {
7714 let Some(editor) = item.await?.downcast::<Editor>() else {
7715 return Ok(());
7716 };
7717 editor
7718 .update_in(cx, |editor, window, cx| {
7719 editor.go_to_singleton_buffer_point(target, window, cx);
7720 })
7721 .ok();
7722 anyhow::Ok(())
7723 })
7724 })
7725 }
7726
7727 pub fn has_active_edit_prediction(&self) -> bool {
7728 self.active_edit_prediction.is_some()
7729 }
7730
7731 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7732 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7733 return false;
7734 };
7735
7736 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7737 self.clear_highlights::<EditPredictionHighlight>(cx);
7738 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7739 true
7740 }
7741
7742 /// Returns true when we're displaying the edit prediction popover below the cursor
7743 /// like we are not previewing and the LSP autocomplete menu is visible
7744 /// or we are in `when_holding_modifier` mode.
7745 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7746 if self.edit_prediction_preview_is_active()
7747 || !self.show_edit_predictions_in_menu()
7748 || !self.edit_predictions_enabled()
7749 {
7750 return false;
7751 }
7752
7753 if self.has_visible_completions_menu() {
7754 return true;
7755 }
7756
7757 has_completion && self.edit_prediction_requires_modifier()
7758 }
7759
7760 fn handle_modifiers_changed(
7761 &mut self,
7762 modifiers: Modifiers,
7763 position_map: &PositionMap,
7764 window: &mut Window,
7765 cx: &mut Context<Self>,
7766 ) {
7767 if self.show_edit_predictions_in_menu() {
7768 self.update_edit_prediction_preview(&modifiers, window, cx);
7769 }
7770
7771 self.update_selection_mode(&modifiers, position_map, window, cx);
7772
7773 let mouse_position = window.mouse_position();
7774 if !position_map.text_hitbox.is_hovered(window) {
7775 return;
7776 }
7777
7778 self.update_hovered_link(
7779 position_map.point_for_position(mouse_position),
7780 &position_map.snapshot,
7781 modifiers,
7782 window,
7783 cx,
7784 )
7785 }
7786
7787 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7788 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7789 if invert {
7790 match multi_cursor_setting {
7791 MultiCursorModifier::Alt => modifiers.alt,
7792 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7793 }
7794 } else {
7795 match multi_cursor_setting {
7796 MultiCursorModifier::Alt => modifiers.secondary(),
7797 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7798 }
7799 }
7800 }
7801
7802 fn columnar_selection_mode(
7803 modifiers: &Modifiers,
7804 cx: &mut Context<Self>,
7805 ) -> Option<ColumnarMode> {
7806 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7807 if Self::multi_cursor_modifier(false, modifiers, cx) {
7808 Some(ColumnarMode::FromMouse)
7809 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7810 Some(ColumnarMode::FromSelection)
7811 } else {
7812 None
7813 }
7814 } else {
7815 None
7816 }
7817 }
7818
7819 fn update_selection_mode(
7820 &mut self,
7821 modifiers: &Modifiers,
7822 position_map: &PositionMap,
7823 window: &mut Window,
7824 cx: &mut Context<Self>,
7825 ) {
7826 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7827 return;
7828 };
7829 if self.selections.pending_anchor().is_none() {
7830 return;
7831 }
7832
7833 let mouse_position = window.mouse_position();
7834 let point_for_position = position_map.point_for_position(mouse_position);
7835 let position = point_for_position.previous_valid;
7836
7837 self.select(
7838 SelectPhase::BeginColumnar {
7839 position,
7840 reset: false,
7841 mode,
7842 goal_column: point_for_position.exact_unclipped.column(),
7843 },
7844 window,
7845 cx,
7846 );
7847 }
7848
7849 fn update_edit_prediction_preview(
7850 &mut self,
7851 modifiers: &Modifiers,
7852 window: &mut Window,
7853 cx: &mut Context<Self>,
7854 ) {
7855 let mut modifiers_held = false;
7856 if let Some(accept_keystroke) = self
7857 .accept_edit_prediction_keybind(false, window, cx)
7858 .keystroke()
7859 {
7860 modifiers_held = modifiers_held
7861 || (accept_keystroke.modifiers() == modifiers
7862 && accept_keystroke.modifiers().modified());
7863 };
7864 if let Some(accept_partial_keystroke) = self
7865 .accept_edit_prediction_keybind(true, window, cx)
7866 .keystroke()
7867 {
7868 modifiers_held = modifiers_held
7869 || (accept_partial_keystroke.modifiers() == modifiers
7870 && accept_partial_keystroke.modifiers().modified());
7871 }
7872
7873 if modifiers_held {
7874 if matches!(
7875 self.edit_prediction_preview,
7876 EditPredictionPreview::Inactive { .. }
7877 ) {
7878 self.edit_prediction_preview = EditPredictionPreview::Active {
7879 previous_scroll_position: None,
7880 since: Instant::now(),
7881 };
7882
7883 self.update_visible_edit_prediction(window, cx);
7884 cx.notify();
7885 }
7886 } else if let EditPredictionPreview::Active {
7887 previous_scroll_position,
7888 since,
7889 } = self.edit_prediction_preview
7890 {
7891 if let (Some(previous_scroll_position), Some(position_map)) =
7892 (previous_scroll_position, self.last_position_map.as_ref())
7893 {
7894 self.set_scroll_position(
7895 previous_scroll_position
7896 .scroll_position(&position_map.snapshot.display_snapshot),
7897 window,
7898 cx,
7899 );
7900 }
7901
7902 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7903 released_too_fast: since.elapsed() < Duration::from_millis(200),
7904 };
7905 self.clear_row_highlights::<EditPredictionPreview>();
7906 self.update_visible_edit_prediction(window, cx);
7907 cx.notify();
7908 }
7909 }
7910
7911 fn update_visible_edit_prediction(
7912 &mut self,
7913 _window: &mut Window,
7914 cx: &mut Context<Self>,
7915 ) -> Option<()> {
7916 if DisableAiSettings::get_global(cx).disable_ai {
7917 return None;
7918 }
7919
7920 if self.ime_transaction.is_some() {
7921 self.discard_edit_prediction(false, cx);
7922 return None;
7923 }
7924
7925 let selection = self.selections.newest_anchor();
7926 let cursor = selection.head();
7927 let multibuffer = self.buffer.read(cx).snapshot(cx);
7928 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7929 let excerpt_id = cursor.excerpt_id;
7930
7931 let show_in_menu = self.show_edit_predictions_in_menu();
7932 let completions_menu_has_precedence = !show_in_menu
7933 && (self.context_menu.borrow().is_some()
7934 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7935
7936 if completions_menu_has_precedence
7937 || !offset_selection.is_empty()
7938 || self
7939 .active_edit_prediction
7940 .as_ref()
7941 .is_some_and(|completion| {
7942 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7943 return false;
7944 };
7945 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7946 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7947 !invalidation_range.contains(&offset_selection.head())
7948 })
7949 {
7950 self.discard_edit_prediction(false, cx);
7951 return None;
7952 }
7953
7954 self.take_active_edit_prediction(cx);
7955 let Some(provider) = self.edit_prediction_provider() else {
7956 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7957 return None;
7958 };
7959
7960 let (buffer, cursor_buffer_position) =
7961 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7962
7963 self.edit_prediction_settings =
7964 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7965
7966 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7967
7968 if self.edit_prediction_indent_conflict {
7969 let cursor_point = cursor.to_point(&multibuffer);
7970
7971 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7972
7973 if let Some((_, indent)) = indents.iter().next()
7974 && indent.len == cursor_point.column
7975 {
7976 self.edit_prediction_indent_conflict = false;
7977 }
7978 }
7979
7980 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7981
7982 let (completion_id, edits, edit_preview) = match edit_prediction {
7983 edit_prediction::EditPrediction::Local {
7984 id,
7985 edits,
7986 edit_preview,
7987 } => (id, edits, edit_preview),
7988 edit_prediction::EditPrediction::Jump {
7989 id,
7990 snapshot,
7991 target,
7992 } => {
7993 self.stale_edit_prediction_in_menu = None;
7994 self.active_edit_prediction = Some(EditPredictionState {
7995 inlay_ids: vec![],
7996 completion: EditPrediction::MoveOutside { snapshot, target },
7997 completion_id: id,
7998 invalidation_range: None,
7999 });
8000 cx.notify();
8001 return Some(());
8002 }
8003 };
8004
8005 let edits = edits
8006 .into_iter()
8007 .flat_map(|(range, new_text)| {
8008 Some((
8009 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8010 new_text,
8011 ))
8012 })
8013 .collect::<Vec<_>>();
8014 if edits.is_empty() {
8015 return None;
8016 }
8017
8018 let first_edit_start = edits.first().unwrap().0.start;
8019 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8020 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8021
8022 let last_edit_end = edits.last().unwrap().0.end;
8023 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8024 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8025
8026 let cursor_row = cursor.to_point(&multibuffer).row;
8027
8028 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8029
8030 let mut inlay_ids = Vec::new();
8031 let invalidation_row_range;
8032 let move_invalidation_row_range = if cursor_row < edit_start_row {
8033 Some(cursor_row..edit_end_row)
8034 } else if cursor_row > edit_end_row {
8035 Some(edit_start_row..cursor_row)
8036 } else {
8037 None
8038 };
8039 let supports_jump = self
8040 .edit_prediction_provider
8041 .as_ref()
8042 .map(|provider| provider.provider.supports_jump_to_edit())
8043 .unwrap_or(true);
8044
8045 let is_move = supports_jump
8046 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8047 let completion = if is_move {
8048 invalidation_row_range =
8049 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8050 let target = first_edit_start;
8051 EditPrediction::MoveWithin { target, snapshot }
8052 } else {
8053 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8054 && !self.edit_predictions_hidden_for_vim_mode;
8055
8056 if show_completions_in_buffer {
8057 if edits
8058 .iter()
8059 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8060 {
8061 let mut inlays = Vec::new();
8062 for (range, new_text) in &edits {
8063 let inlay = Inlay::edit_prediction(
8064 post_inc(&mut self.next_inlay_id),
8065 range.start,
8066 new_text.as_str(),
8067 );
8068 inlay_ids.push(inlay.id);
8069 inlays.push(inlay);
8070 }
8071
8072 self.splice_inlays(&[], inlays, cx);
8073 } else {
8074 let background_color = cx.theme().status().deleted_background;
8075 self.highlight_text::<EditPredictionHighlight>(
8076 edits.iter().map(|(range, _)| range.clone()).collect(),
8077 HighlightStyle {
8078 background_color: Some(background_color),
8079 ..Default::default()
8080 },
8081 cx,
8082 );
8083 }
8084 }
8085
8086 invalidation_row_range = edit_start_row..edit_end_row;
8087
8088 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8089 if provider.show_tab_accept_marker() {
8090 EditDisplayMode::TabAccept
8091 } else {
8092 EditDisplayMode::Inline
8093 }
8094 } else {
8095 EditDisplayMode::DiffPopover
8096 };
8097
8098 EditPrediction::Edit {
8099 edits,
8100 edit_preview,
8101 display_mode,
8102 snapshot,
8103 }
8104 };
8105
8106 let invalidation_range = multibuffer
8107 .anchor_before(Point::new(invalidation_row_range.start, 0))
8108 ..multibuffer.anchor_after(Point::new(
8109 invalidation_row_range.end,
8110 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8111 ));
8112
8113 self.stale_edit_prediction_in_menu = None;
8114 self.active_edit_prediction = Some(EditPredictionState {
8115 inlay_ids,
8116 completion,
8117 completion_id,
8118 invalidation_range: Some(invalidation_range),
8119 });
8120
8121 cx.notify();
8122
8123 Some(())
8124 }
8125
8126 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8127 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8128 }
8129
8130 fn clear_tasks(&mut self) {
8131 self.tasks.clear()
8132 }
8133
8134 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8135 if self.tasks.insert(key, value).is_some() {
8136 // This case should hopefully be rare, but just in case...
8137 log::error!(
8138 "multiple different run targets found on a single line, only the last target will be rendered"
8139 )
8140 }
8141 }
8142
8143 /// Get all display points of breakpoints that will be rendered within editor
8144 ///
8145 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8146 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8147 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8148 fn active_breakpoints(
8149 &self,
8150 range: Range<DisplayRow>,
8151 window: &mut Window,
8152 cx: &mut Context<Self>,
8153 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8154 let mut breakpoint_display_points = HashMap::default();
8155
8156 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8157 return breakpoint_display_points;
8158 };
8159
8160 let snapshot = self.snapshot(window, cx);
8161
8162 let multi_buffer_snapshot = snapshot.display_snapshot.buffer_snapshot();
8163 let Some(project) = self.project() else {
8164 return breakpoint_display_points;
8165 };
8166
8167 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8168 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8169
8170 for (buffer_snapshot, range, excerpt_id) in
8171 multi_buffer_snapshot.range_to_buffer_ranges(range)
8172 {
8173 let Some(buffer) = project
8174 .read(cx)
8175 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8176 else {
8177 continue;
8178 };
8179 let breakpoints = breakpoint_store.read(cx).breakpoints(
8180 &buffer,
8181 Some(
8182 buffer_snapshot.anchor_before(range.start)
8183 ..buffer_snapshot.anchor_after(range.end),
8184 ),
8185 buffer_snapshot,
8186 cx,
8187 );
8188 for (breakpoint, state) in breakpoints {
8189 let multi_buffer_anchor =
8190 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8191 let position = multi_buffer_anchor
8192 .to_point(multi_buffer_snapshot)
8193 .to_display_point(&snapshot);
8194
8195 breakpoint_display_points.insert(
8196 position.row(),
8197 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8198 );
8199 }
8200 }
8201
8202 breakpoint_display_points
8203 }
8204
8205 fn breakpoint_context_menu(
8206 &self,
8207 anchor: Anchor,
8208 window: &mut Window,
8209 cx: &mut Context<Self>,
8210 ) -> Entity<ui::ContextMenu> {
8211 let weak_editor = cx.weak_entity();
8212 let focus_handle = self.focus_handle(cx);
8213
8214 let row = self
8215 .buffer
8216 .read(cx)
8217 .snapshot(cx)
8218 .summary_for_anchor::<Point>(&anchor)
8219 .row;
8220
8221 let breakpoint = self
8222 .breakpoint_at_row(row, window, cx)
8223 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8224
8225 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8226 "Edit Log Breakpoint"
8227 } else {
8228 "Set Log Breakpoint"
8229 };
8230
8231 let condition_breakpoint_msg = if breakpoint
8232 .as_ref()
8233 .is_some_and(|bp| bp.1.condition.is_some())
8234 {
8235 "Edit Condition Breakpoint"
8236 } else {
8237 "Set Condition Breakpoint"
8238 };
8239
8240 let hit_condition_breakpoint_msg = if breakpoint
8241 .as_ref()
8242 .is_some_and(|bp| bp.1.hit_condition.is_some())
8243 {
8244 "Edit Hit Condition Breakpoint"
8245 } else {
8246 "Set Hit Condition Breakpoint"
8247 };
8248
8249 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8250 "Unset Breakpoint"
8251 } else {
8252 "Set Breakpoint"
8253 };
8254
8255 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8256
8257 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8258 BreakpointState::Enabled => Some("Disable"),
8259 BreakpointState::Disabled => Some("Enable"),
8260 });
8261
8262 let (anchor, breakpoint) =
8263 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8264
8265 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8266 menu.on_blur_subscription(Subscription::new(|| {}))
8267 .context(focus_handle)
8268 .when(run_to_cursor, |this| {
8269 let weak_editor = weak_editor.clone();
8270 this.entry("Run to cursor", None, move |window, cx| {
8271 weak_editor
8272 .update(cx, |editor, cx| {
8273 editor.change_selections(
8274 SelectionEffects::no_scroll(),
8275 window,
8276 cx,
8277 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8278 );
8279 })
8280 .ok();
8281
8282 window.dispatch_action(Box::new(RunToCursor), cx);
8283 })
8284 .separator()
8285 })
8286 .when_some(toggle_state_msg, |this, msg| {
8287 this.entry(msg, None, {
8288 let weak_editor = weak_editor.clone();
8289 let breakpoint = breakpoint.clone();
8290 move |_window, cx| {
8291 weak_editor
8292 .update(cx, |this, cx| {
8293 this.edit_breakpoint_at_anchor(
8294 anchor,
8295 breakpoint.as_ref().clone(),
8296 BreakpointEditAction::InvertState,
8297 cx,
8298 );
8299 })
8300 .log_err();
8301 }
8302 })
8303 })
8304 .entry(set_breakpoint_msg, None, {
8305 let weak_editor = weak_editor.clone();
8306 let breakpoint = breakpoint.clone();
8307 move |_window, cx| {
8308 weak_editor
8309 .update(cx, |this, cx| {
8310 this.edit_breakpoint_at_anchor(
8311 anchor,
8312 breakpoint.as_ref().clone(),
8313 BreakpointEditAction::Toggle,
8314 cx,
8315 );
8316 })
8317 .log_err();
8318 }
8319 })
8320 .entry(log_breakpoint_msg, None, {
8321 let breakpoint = breakpoint.clone();
8322 let weak_editor = weak_editor.clone();
8323 move |window, cx| {
8324 weak_editor
8325 .update(cx, |this, cx| {
8326 this.add_edit_breakpoint_block(
8327 anchor,
8328 breakpoint.as_ref(),
8329 BreakpointPromptEditAction::Log,
8330 window,
8331 cx,
8332 );
8333 })
8334 .log_err();
8335 }
8336 })
8337 .entry(condition_breakpoint_msg, None, {
8338 let breakpoint = breakpoint.clone();
8339 let weak_editor = weak_editor.clone();
8340 move |window, cx| {
8341 weak_editor
8342 .update(cx, |this, cx| {
8343 this.add_edit_breakpoint_block(
8344 anchor,
8345 breakpoint.as_ref(),
8346 BreakpointPromptEditAction::Condition,
8347 window,
8348 cx,
8349 );
8350 })
8351 .log_err();
8352 }
8353 })
8354 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8355 weak_editor
8356 .update(cx, |this, cx| {
8357 this.add_edit_breakpoint_block(
8358 anchor,
8359 breakpoint.as_ref(),
8360 BreakpointPromptEditAction::HitCondition,
8361 window,
8362 cx,
8363 );
8364 })
8365 .log_err();
8366 })
8367 })
8368 }
8369
8370 fn render_breakpoint(
8371 &self,
8372 position: Anchor,
8373 row: DisplayRow,
8374 breakpoint: &Breakpoint,
8375 state: Option<BreakpointSessionState>,
8376 cx: &mut Context<Self>,
8377 ) -> IconButton {
8378 let is_rejected = state.is_some_and(|s| !s.verified);
8379 // Is it a breakpoint that shows up when hovering over gutter?
8380 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8381 (false, false),
8382 |PhantomBreakpointIndicator {
8383 is_active,
8384 display_row,
8385 collides_with_existing_breakpoint,
8386 }| {
8387 (
8388 is_active && display_row == row,
8389 collides_with_existing_breakpoint,
8390 )
8391 },
8392 );
8393
8394 let (color, icon) = {
8395 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8396 (false, false) => ui::IconName::DebugBreakpoint,
8397 (true, false) => ui::IconName::DebugLogBreakpoint,
8398 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8399 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8400 };
8401
8402 let color = if is_phantom {
8403 Color::Hint
8404 } else if is_rejected {
8405 Color::Disabled
8406 } else {
8407 Color::Debugger
8408 };
8409
8410 (color, icon)
8411 };
8412
8413 let breakpoint = Arc::from(breakpoint.clone());
8414
8415 let alt_as_text = gpui::Keystroke {
8416 modifiers: Modifiers::secondary_key(),
8417 ..Default::default()
8418 };
8419 let primary_action_text = if breakpoint.is_disabled() {
8420 "Enable breakpoint"
8421 } else if is_phantom && !collides_with_existing {
8422 "Set breakpoint"
8423 } else {
8424 "Unset breakpoint"
8425 };
8426 let focus_handle = self.focus_handle.clone();
8427
8428 let meta = if is_rejected {
8429 SharedString::from("No executable code is associated with this line.")
8430 } else if collides_with_existing && !breakpoint.is_disabled() {
8431 SharedString::from(format!(
8432 "{alt_as_text}-click to disable,\nright-click for more options."
8433 ))
8434 } else {
8435 SharedString::from("Right-click for more options.")
8436 };
8437 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8438 .icon_size(IconSize::XSmall)
8439 .size(ui::ButtonSize::None)
8440 .when(is_rejected, |this| {
8441 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8442 })
8443 .icon_color(color)
8444 .style(ButtonStyle::Transparent)
8445 .on_click(cx.listener({
8446 move |editor, event: &ClickEvent, window, cx| {
8447 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8448 BreakpointEditAction::InvertState
8449 } else {
8450 BreakpointEditAction::Toggle
8451 };
8452
8453 window.focus(&editor.focus_handle(cx));
8454 editor.edit_breakpoint_at_anchor(
8455 position,
8456 breakpoint.as_ref().clone(),
8457 edit_action,
8458 cx,
8459 );
8460 }
8461 }))
8462 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8463 editor.set_breakpoint_context_menu(
8464 row,
8465 Some(position),
8466 event.position(),
8467 window,
8468 cx,
8469 );
8470 }))
8471 .tooltip(move |window, cx| {
8472 Tooltip::with_meta_in(
8473 primary_action_text,
8474 Some(&ToggleBreakpoint),
8475 meta.clone(),
8476 &focus_handle,
8477 window,
8478 cx,
8479 )
8480 })
8481 }
8482
8483 fn build_tasks_context(
8484 project: &Entity<Project>,
8485 buffer: &Entity<Buffer>,
8486 buffer_row: u32,
8487 tasks: &Arc<RunnableTasks>,
8488 cx: &mut Context<Self>,
8489 ) -> Task<Option<task::TaskContext>> {
8490 let position = Point::new(buffer_row, tasks.column);
8491 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8492 let location = Location {
8493 buffer: buffer.clone(),
8494 range: range_start..range_start,
8495 };
8496 // Fill in the environmental variables from the tree-sitter captures
8497 let mut captured_task_variables = TaskVariables::default();
8498 for (capture_name, value) in tasks.extra_variables.clone() {
8499 captured_task_variables.insert(
8500 task::VariableName::Custom(capture_name.into()),
8501 value.clone(),
8502 );
8503 }
8504 project.update(cx, |project, cx| {
8505 project.task_store().update(cx, |task_store, cx| {
8506 task_store.task_context_for_location(captured_task_variables, location, cx)
8507 })
8508 })
8509 }
8510
8511 pub fn spawn_nearest_task(
8512 &mut self,
8513 action: &SpawnNearestTask,
8514 window: &mut Window,
8515 cx: &mut Context<Self>,
8516 ) {
8517 let Some((workspace, _)) = self.workspace.clone() else {
8518 return;
8519 };
8520 let Some(project) = self.project.clone() else {
8521 return;
8522 };
8523
8524 // Try to find a closest, enclosing node using tree-sitter that has a task
8525 let Some((buffer, buffer_row, tasks)) = self
8526 .find_enclosing_node_task(cx)
8527 // Or find the task that's closest in row-distance.
8528 .or_else(|| self.find_closest_task(cx))
8529 else {
8530 return;
8531 };
8532
8533 let reveal_strategy = action.reveal;
8534 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8535 cx.spawn_in(window, async move |_, cx| {
8536 let context = task_context.await?;
8537 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8538
8539 let resolved = &mut resolved_task.resolved;
8540 resolved.reveal = reveal_strategy;
8541
8542 workspace
8543 .update_in(cx, |workspace, window, cx| {
8544 workspace.schedule_resolved_task(
8545 task_source_kind,
8546 resolved_task,
8547 false,
8548 window,
8549 cx,
8550 );
8551 })
8552 .ok()
8553 })
8554 .detach();
8555 }
8556
8557 fn find_closest_task(
8558 &mut self,
8559 cx: &mut Context<Self>,
8560 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8561 let cursor_row = self
8562 .selections
8563 .newest_adjusted(&self.display_snapshot(cx))
8564 .head()
8565 .row;
8566
8567 let ((buffer_id, row), tasks) = self
8568 .tasks
8569 .iter()
8570 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8571
8572 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8573 let tasks = Arc::new(tasks.to_owned());
8574 Some((buffer, *row, tasks))
8575 }
8576
8577 fn find_enclosing_node_task(
8578 &mut self,
8579 cx: &mut Context<Self>,
8580 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8581 let snapshot = self.buffer.read(cx).snapshot(cx);
8582 let offset = self
8583 .selections
8584 .newest::<usize>(&self.display_snapshot(cx))
8585 .head();
8586 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8587 let buffer_id = excerpt.buffer().remote_id();
8588
8589 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8590 let mut cursor = layer.node().walk();
8591
8592 while cursor.goto_first_child_for_byte(offset).is_some() {
8593 if cursor.node().end_byte() == offset {
8594 cursor.goto_next_sibling();
8595 }
8596 }
8597
8598 // Ascend to the smallest ancestor that contains the range and has a task.
8599 loop {
8600 let node = cursor.node();
8601 let node_range = node.byte_range();
8602 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8603
8604 // Check if this node contains our offset
8605 if node_range.start <= offset && node_range.end >= offset {
8606 // If it contains offset, check for task
8607 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8608 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8609 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8610 }
8611 }
8612
8613 if !cursor.goto_parent() {
8614 break;
8615 }
8616 }
8617 None
8618 }
8619
8620 fn render_run_indicator(
8621 &self,
8622 _style: &EditorStyle,
8623 is_active: bool,
8624 row: DisplayRow,
8625 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8626 cx: &mut Context<Self>,
8627 ) -> IconButton {
8628 let color = Color::Muted;
8629 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8630
8631 IconButton::new(
8632 ("run_indicator", row.0 as usize),
8633 ui::IconName::PlayOutlined,
8634 )
8635 .shape(ui::IconButtonShape::Square)
8636 .icon_size(IconSize::XSmall)
8637 .icon_color(color)
8638 .toggle_state(is_active)
8639 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8640 let quick_launch = match e {
8641 ClickEvent::Keyboard(_) => true,
8642 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8643 };
8644
8645 window.focus(&editor.focus_handle(cx));
8646 editor.toggle_code_actions(
8647 &ToggleCodeActions {
8648 deployed_from: Some(CodeActionSource::RunMenu(row)),
8649 quick_launch,
8650 },
8651 window,
8652 cx,
8653 );
8654 }))
8655 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8656 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8657 }))
8658 }
8659
8660 pub fn context_menu_visible(&self) -> bool {
8661 !self.edit_prediction_preview_is_active()
8662 && self
8663 .context_menu
8664 .borrow()
8665 .as_ref()
8666 .is_some_and(|menu| menu.visible())
8667 }
8668
8669 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8670 self.context_menu
8671 .borrow()
8672 .as_ref()
8673 .map(|menu| menu.origin())
8674 }
8675
8676 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8677 self.context_menu_options = Some(options);
8678 }
8679
8680 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8681 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8682
8683 fn render_edit_prediction_popover(
8684 &mut self,
8685 text_bounds: &Bounds<Pixels>,
8686 content_origin: gpui::Point<Pixels>,
8687 right_margin: Pixels,
8688 editor_snapshot: &EditorSnapshot,
8689 visible_row_range: Range<DisplayRow>,
8690 scroll_top: ScrollOffset,
8691 scroll_bottom: ScrollOffset,
8692 line_layouts: &[LineWithInvisibles],
8693 line_height: Pixels,
8694 scroll_position: gpui::Point<ScrollOffset>,
8695 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8696 newest_selection_head: Option<DisplayPoint>,
8697 editor_width: Pixels,
8698 style: &EditorStyle,
8699 window: &mut Window,
8700 cx: &mut App,
8701 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8702 if self.mode().is_minimap() {
8703 return None;
8704 }
8705 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8706
8707 if self.edit_prediction_visible_in_cursor_popover(true) {
8708 return None;
8709 }
8710
8711 match &active_edit_prediction.completion {
8712 EditPrediction::MoveWithin { target, .. } => {
8713 let target_display_point = target.to_display_point(editor_snapshot);
8714
8715 if self.edit_prediction_requires_modifier() {
8716 if !self.edit_prediction_preview_is_active() {
8717 return None;
8718 }
8719
8720 self.render_edit_prediction_modifier_jump_popover(
8721 text_bounds,
8722 content_origin,
8723 visible_row_range,
8724 line_layouts,
8725 line_height,
8726 scroll_pixel_position,
8727 newest_selection_head,
8728 target_display_point,
8729 window,
8730 cx,
8731 )
8732 } else {
8733 self.render_edit_prediction_eager_jump_popover(
8734 text_bounds,
8735 content_origin,
8736 editor_snapshot,
8737 visible_row_range,
8738 scroll_top,
8739 scroll_bottom,
8740 line_height,
8741 scroll_pixel_position,
8742 target_display_point,
8743 editor_width,
8744 window,
8745 cx,
8746 )
8747 }
8748 }
8749 EditPrediction::Edit {
8750 display_mode: EditDisplayMode::Inline,
8751 ..
8752 } => None,
8753 EditPrediction::Edit {
8754 display_mode: EditDisplayMode::TabAccept,
8755 edits,
8756 ..
8757 } => {
8758 let range = &edits.first()?.0;
8759 let target_display_point = range.end.to_display_point(editor_snapshot);
8760
8761 self.render_edit_prediction_end_of_line_popover(
8762 "Accept",
8763 editor_snapshot,
8764 visible_row_range,
8765 target_display_point,
8766 line_height,
8767 scroll_pixel_position,
8768 content_origin,
8769 editor_width,
8770 window,
8771 cx,
8772 )
8773 }
8774 EditPrediction::Edit {
8775 edits,
8776 edit_preview,
8777 display_mode: EditDisplayMode::DiffPopover,
8778 snapshot,
8779 } => self.render_edit_prediction_diff_popover(
8780 text_bounds,
8781 content_origin,
8782 right_margin,
8783 editor_snapshot,
8784 visible_row_range,
8785 line_layouts,
8786 line_height,
8787 scroll_position,
8788 scroll_pixel_position,
8789 newest_selection_head,
8790 editor_width,
8791 style,
8792 edits,
8793 edit_preview,
8794 snapshot,
8795 window,
8796 cx,
8797 ),
8798 EditPrediction::MoveOutside { snapshot, .. } => {
8799 let file_name = snapshot
8800 .file()
8801 .map(|file| file.file_name(cx))
8802 .unwrap_or("untitled");
8803 let mut element = self
8804 .render_edit_prediction_line_popover(
8805 format!("Jump to {file_name}"),
8806 Some(IconName::ZedPredict),
8807 window,
8808 cx,
8809 )
8810 .into_any();
8811
8812 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8813 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8814 let origin_y = text_bounds.size.height - size.height - px(30.);
8815 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8816 element.prepaint_at(origin, window, cx);
8817
8818 Some((element, origin))
8819 }
8820 }
8821 }
8822
8823 fn render_edit_prediction_modifier_jump_popover(
8824 &mut self,
8825 text_bounds: &Bounds<Pixels>,
8826 content_origin: gpui::Point<Pixels>,
8827 visible_row_range: Range<DisplayRow>,
8828 line_layouts: &[LineWithInvisibles],
8829 line_height: Pixels,
8830 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8831 newest_selection_head: Option<DisplayPoint>,
8832 target_display_point: DisplayPoint,
8833 window: &mut Window,
8834 cx: &mut App,
8835 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8836 let scrolled_content_origin =
8837 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8838
8839 const SCROLL_PADDING_Y: Pixels = px(12.);
8840
8841 if target_display_point.row() < visible_row_range.start {
8842 return self.render_edit_prediction_scroll_popover(
8843 |_| SCROLL_PADDING_Y,
8844 IconName::ArrowUp,
8845 visible_row_range,
8846 line_layouts,
8847 newest_selection_head,
8848 scrolled_content_origin,
8849 window,
8850 cx,
8851 );
8852 } else if target_display_point.row() >= visible_row_range.end {
8853 return self.render_edit_prediction_scroll_popover(
8854 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8855 IconName::ArrowDown,
8856 visible_row_range,
8857 line_layouts,
8858 newest_selection_head,
8859 scrolled_content_origin,
8860 window,
8861 cx,
8862 );
8863 }
8864
8865 const POLE_WIDTH: Pixels = px(2.);
8866
8867 let line_layout =
8868 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8869 let target_column = target_display_point.column() as usize;
8870
8871 let target_x = line_layout.x_for_index(target_column);
8872 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8873 - scroll_pixel_position.y;
8874
8875 let flag_on_right = target_x < text_bounds.size.width / 2.;
8876
8877 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8878 border_color.l += 0.001;
8879
8880 let mut element = v_flex()
8881 .items_end()
8882 .when(flag_on_right, |el| el.items_start())
8883 .child(if flag_on_right {
8884 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8885 .rounded_bl(px(0.))
8886 .rounded_tl(px(0.))
8887 .border_l_2()
8888 .border_color(border_color)
8889 } else {
8890 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8891 .rounded_br(px(0.))
8892 .rounded_tr(px(0.))
8893 .border_r_2()
8894 .border_color(border_color)
8895 })
8896 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8897 .into_any();
8898
8899 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8900
8901 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8902 - point(
8903 if flag_on_right {
8904 POLE_WIDTH
8905 } else {
8906 size.width - POLE_WIDTH
8907 },
8908 size.height - line_height,
8909 );
8910
8911 origin.x = origin.x.max(content_origin.x);
8912
8913 element.prepaint_at(origin, window, cx);
8914
8915 Some((element, origin))
8916 }
8917
8918 fn render_edit_prediction_scroll_popover(
8919 &mut self,
8920 to_y: impl Fn(Size<Pixels>) -> Pixels,
8921 scroll_icon: IconName,
8922 visible_row_range: Range<DisplayRow>,
8923 line_layouts: &[LineWithInvisibles],
8924 newest_selection_head: Option<DisplayPoint>,
8925 scrolled_content_origin: gpui::Point<Pixels>,
8926 window: &mut Window,
8927 cx: &mut App,
8928 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8929 let mut element = self
8930 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8931 .into_any();
8932
8933 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8934
8935 let cursor = newest_selection_head?;
8936 let cursor_row_layout =
8937 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8938 let cursor_column = cursor.column() as usize;
8939
8940 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8941
8942 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8943
8944 element.prepaint_at(origin, window, cx);
8945 Some((element, origin))
8946 }
8947
8948 fn render_edit_prediction_eager_jump_popover(
8949 &mut self,
8950 text_bounds: &Bounds<Pixels>,
8951 content_origin: gpui::Point<Pixels>,
8952 editor_snapshot: &EditorSnapshot,
8953 visible_row_range: Range<DisplayRow>,
8954 scroll_top: ScrollOffset,
8955 scroll_bottom: ScrollOffset,
8956 line_height: Pixels,
8957 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8958 target_display_point: DisplayPoint,
8959 editor_width: Pixels,
8960 window: &mut Window,
8961 cx: &mut App,
8962 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8963 if target_display_point.row().as_f64() < scroll_top {
8964 let mut element = self
8965 .render_edit_prediction_line_popover(
8966 "Jump to Edit",
8967 Some(IconName::ArrowUp),
8968 window,
8969 cx,
8970 )
8971 .into_any();
8972
8973 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8974 let offset = point(
8975 (text_bounds.size.width - size.width) / 2.,
8976 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8977 );
8978
8979 let origin = text_bounds.origin + offset;
8980 element.prepaint_at(origin, window, cx);
8981 Some((element, origin))
8982 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8983 let mut element = self
8984 .render_edit_prediction_line_popover(
8985 "Jump to Edit",
8986 Some(IconName::ArrowDown),
8987 window,
8988 cx,
8989 )
8990 .into_any();
8991
8992 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8993 let offset = point(
8994 (text_bounds.size.width - size.width) / 2.,
8995 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8996 );
8997
8998 let origin = text_bounds.origin + offset;
8999 element.prepaint_at(origin, window, cx);
9000 Some((element, origin))
9001 } else {
9002 self.render_edit_prediction_end_of_line_popover(
9003 "Jump to Edit",
9004 editor_snapshot,
9005 visible_row_range,
9006 target_display_point,
9007 line_height,
9008 scroll_pixel_position,
9009 content_origin,
9010 editor_width,
9011 window,
9012 cx,
9013 )
9014 }
9015 }
9016
9017 fn render_edit_prediction_end_of_line_popover(
9018 self: &mut Editor,
9019 label: &'static str,
9020 editor_snapshot: &EditorSnapshot,
9021 visible_row_range: Range<DisplayRow>,
9022 target_display_point: DisplayPoint,
9023 line_height: Pixels,
9024 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9025 content_origin: gpui::Point<Pixels>,
9026 editor_width: Pixels,
9027 window: &mut Window,
9028 cx: &mut App,
9029 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9030 let target_line_end = DisplayPoint::new(
9031 target_display_point.row(),
9032 editor_snapshot.line_len(target_display_point.row()),
9033 );
9034
9035 let mut element = self
9036 .render_edit_prediction_line_popover(label, None, window, cx)
9037 .into_any();
9038
9039 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9040
9041 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
9042
9043 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9044 let mut origin = start_point
9045 + line_origin
9046 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9047 origin.x = origin.x.max(content_origin.x);
9048
9049 let max_x = content_origin.x + editor_width - size.width;
9050
9051 if origin.x > max_x {
9052 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9053
9054 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9055 origin.y += offset;
9056 IconName::ArrowUp
9057 } else {
9058 origin.y -= offset;
9059 IconName::ArrowDown
9060 };
9061
9062 element = self
9063 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9064 .into_any();
9065
9066 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9067
9068 origin.x = content_origin.x + editor_width - size.width - px(2.);
9069 }
9070
9071 element.prepaint_at(origin, window, cx);
9072 Some((element, origin))
9073 }
9074
9075 fn render_edit_prediction_diff_popover(
9076 self: &Editor,
9077 text_bounds: &Bounds<Pixels>,
9078 content_origin: gpui::Point<Pixels>,
9079 right_margin: Pixels,
9080 editor_snapshot: &EditorSnapshot,
9081 visible_row_range: Range<DisplayRow>,
9082 line_layouts: &[LineWithInvisibles],
9083 line_height: Pixels,
9084 scroll_position: gpui::Point<ScrollOffset>,
9085 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9086 newest_selection_head: Option<DisplayPoint>,
9087 editor_width: Pixels,
9088 style: &EditorStyle,
9089 edits: &Vec<(Range<Anchor>, String)>,
9090 edit_preview: &Option<language::EditPreview>,
9091 snapshot: &language::BufferSnapshot,
9092 window: &mut Window,
9093 cx: &mut App,
9094 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9095 let edit_start = edits
9096 .first()
9097 .unwrap()
9098 .0
9099 .start
9100 .to_display_point(editor_snapshot);
9101 let edit_end = edits
9102 .last()
9103 .unwrap()
9104 .0
9105 .end
9106 .to_display_point(editor_snapshot);
9107
9108 let is_visible = visible_row_range.contains(&edit_start.row())
9109 || visible_row_range.contains(&edit_end.row());
9110 if !is_visible {
9111 return None;
9112 }
9113
9114 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9115 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9116 } else {
9117 // Fallback for providers without edit_preview
9118 crate::edit_prediction_fallback_text(edits, cx)
9119 };
9120
9121 let styled_text = highlighted_edits.to_styled_text(&style.text);
9122 let line_count = highlighted_edits.text.lines().count();
9123
9124 const BORDER_WIDTH: Pixels = px(1.);
9125
9126 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9127 let has_keybind = keybind.is_some();
9128
9129 let mut element = h_flex()
9130 .items_start()
9131 .child(
9132 h_flex()
9133 .bg(cx.theme().colors().editor_background)
9134 .border(BORDER_WIDTH)
9135 .shadow_xs()
9136 .border_color(cx.theme().colors().border)
9137 .rounded_l_lg()
9138 .when(line_count > 1, |el| el.rounded_br_lg())
9139 .pr_1()
9140 .child(styled_text),
9141 )
9142 .child(
9143 h_flex()
9144 .h(line_height + BORDER_WIDTH * 2.)
9145 .px_1p5()
9146 .gap_1()
9147 // Workaround: For some reason, there's a gap if we don't do this
9148 .ml(-BORDER_WIDTH)
9149 .shadow(vec![gpui::BoxShadow {
9150 color: gpui::black().opacity(0.05),
9151 offset: point(px(1.), px(1.)),
9152 blur_radius: px(2.),
9153 spread_radius: px(0.),
9154 }])
9155 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9156 .border(BORDER_WIDTH)
9157 .border_color(cx.theme().colors().border)
9158 .rounded_r_lg()
9159 .id("edit_prediction_diff_popover_keybind")
9160 .when(!has_keybind, |el| {
9161 let status_colors = cx.theme().status();
9162
9163 el.bg(status_colors.error_background)
9164 .border_color(status_colors.error.opacity(0.6))
9165 .child(Icon::new(IconName::Info).color(Color::Error))
9166 .cursor_default()
9167 .hoverable_tooltip(move |_window, cx| {
9168 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9169 })
9170 })
9171 .children(keybind),
9172 )
9173 .into_any();
9174
9175 let longest_row =
9176 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9177 let longest_line_width = if visible_row_range.contains(&longest_row) {
9178 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9179 } else {
9180 layout_line(
9181 longest_row,
9182 editor_snapshot,
9183 style,
9184 editor_width,
9185 |_| false,
9186 window,
9187 cx,
9188 )
9189 .width
9190 };
9191
9192 let viewport_bounds =
9193 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9194 right: -right_margin,
9195 ..Default::default()
9196 });
9197
9198 let x_after_longest = Pixels::from(
9199 ScrollPixelOffset::from(
9200 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9201 ) - scroll_pixel_position.x,
9202 );
9203
9204 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9205
9206 // Fully visible if it can be displayed within the window (allow overlapping other
9207 // panes). However, this is only allowed if the popover starts within text_bounds.
9208 let can_position_to_the_right = x_after_longest < text_bounds.right()
9209 && x_after_longest + element_bounds.width < viewport_bounds.right();
9210
9211 let mut origin = if can_position_to_the_right {
9212 point(
9213 x_after_longest,
9214 text_bounds.origin.y
9215 + Pixels::from(
9216 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9217 - scroll_pixel_position.y,
9218 ),
9219 )
9220 } else {
9221 let cursor_row = newest_selection_head.map(|head| head.row());
9222 let above_edit = edit_start
9223 .row()
9224 .0
9225 .checked_sub(line_count as u32)
9226 .map(DisplayRow);
9227 let below_edit = Some(edit_end.row() + 1);
9228 let above_cursor =
9229 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9230 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9231
9232 // Place the edit popover adjacent to the edit if there is a location
9233 // available that is onscreen and does not obscure the cursor. Otherwise,
9234 // place it adjacent to the cursor.
9235 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9236 .into_iter()
9237 .flatten()
9238 .find(|&start_row| {
9239 let end_row = start_row + line_count as u32;
9240 visible_row_range.contains(&start_row)
9241 && visible_row_range.contains(&end_row)
9242 && cursor_row
9243 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9244 })?;
9245
9246 content_origin
9247 + point(
9248 Pixels::from(-scroll_pixel_position.x),
9249 Pixels::from(
9250 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9251 ),
9252 )
9253 };
9254
9255 origin.x -= BORDER_WIDTH;
9256
9257 window.defer_draw(element, origin, 1);
9258
9259 // Do not return an element, since it will already be drawn due to defer_draw.
9260 None
9261 }
9262
9263 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9264 px(30.)
9265 }
9266
9267 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9268 if self.read_only(cx) {
9269 cx.theme().players().read_only()
9270 } else {
9271 self.style.as_ref().unwrap().local_player
9272 }
9273 }
9274
9275 fn render_edit_prediction_accept_keybind(
9276 &self,
9277 window: &mut Window,
9278 cx: &App,
9279 ) -> Option<AnyElement> {
9280 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9281 let accept_keystroke = accept_binding.keystroke()?;
9282
9283 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9284
9285 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9286 Color::Accent
9287 } else {
9288 Color::Muted
9289 };
9290
9291 h_flex()
9292 .px_0p5()
9293 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9294 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9295 .text_size(TextSize::XSmall.rems(cx))
9296 .child(h_flex().children(ui::render_modifiers(
9297 accept_keystroke.modifiers(),
9298 PlatformStyle::platform(),
9299 Some(modifiers_color),
9300 Some(IconSize::XSmall.rems().into()),
9301 true,
9302 )))
9303 .when(is_platform_style_mac, |parent| {
9304 parent.child(accept_keystroke.key().to_string())
9305 })
9306 .when(!is_platform_style_mac, |parent| {
9307 parent.child(
9308 Key::new(
9309 util::capitalize(accept_keystroke.key()),
9310 Some(Color::Default),
9311 )
9312 .size(Some(IconSize::XSmall.rems().into())),
9313 )
9314 })
9315 .into_any()
9316 .into()
9317 }
9318
9319 fn render_edit_prediction_line_popover(
9320 &self,
9321 label: impl Into<SharedString>,
9322 icon: Option<IconName>,
9323 window: &mut Window,
9324 cx: &App,
9325 ) -> Stateful<Div> {
9326 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9327
9328 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9329 let has_keybind = keybind.is_some();
9330
9331 h_flex()
9332 .id("ep-line-popover")
9333 .py_0p5()
9334 .pl_1()
9335 .pr(padding_right)
9336 .gap_1()
9337 .rounded_md()
9338 .border_1()
9339 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9340 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9341 .shadow_xs()
9342 .when(!has_keybind, |el| {
9343 let status_colors = cx.theme().status();
9344
9345 el.bg(status_colors.error_background)
9346 .border_color(status_colors.error.opacity(0.6))
9347 .pl_2()
9348 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9349 .cursor_default()
9350 .hoverable_tooltip(move |_window, cx| {
9351 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9352 })
9353 })
9354 .children(keybind)
9355 .child(
9356 Label::new(label)
9357 .size(LabelSize::Small)
9358 .when(!has_keybind, |el| {
9359 el.color(cx.theme().status().error.into()).strikethrough()
9360 }),
9361 )
9362 .when(!has_keybind, |el| {
9363 el.child(
9364 h_flex().ml_1().child(
9365 Icon::new(IconName::Info)
9366 .size(IconSize::Small)
9367 .color(cx.theme().status().error.into()),
9368 ),
9369 )
9370 })
9371 .when_some(icon, |element, icon| {
9372 element.child(
9373 div()
9374 .mt(px(1.5))
9375 .child(Icon::new(icon).size(IconSize::Small)),
9376 )
9377 })
9378 }
9379
9380 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9381 let accent_color = cx.theme().colors().text_accent;
9382 let editor_bg_color = cx.theme().colors().editor_background;
9383 editor_bg_color.blend(accent_color.opacity(0.1))
9384 }
9385
9386 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9387 let accent_color = cx.theme().colors().text_accent;
9388 let editor_bg_color = cx.theme().colors().editor_background;
9389 editor_bg_color.blend(accent_color.opacity(0.6))
9390 }
9391 fn get_prediction_provider_icon_name(
9392 provider: &Option<RegisteredEditPredictionProvider>,
9393 ) -> IconName {
9394 match provider {
9395 Some(provider) => match provider.provider.name() {
9396 "copilot" => IconName::Copilot,
9397 "supermaven" => IconName::Supermaven,
9398 _ => IconName::ZedPredict,
9399 },
9400 None => IconName::ZedPredict,
9401 }
9402 }
9403
9404 fn render_edit_prediction_cursor_popover(
9405 &self,
9406 min_width: Pixels,
9407 max_width: Pixels,
9408 cursor_point: Point,
9409 style: &EditorStyle,
9410 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9411 _window: &Window,
9412 cx: &mut Context<Editor>,
9413 ) -> Option<AnyElement> {
9414 let provider = self.edit_prediction_provider.as_ref()?;
9415 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9416
9417 let is_refreshing = provider.provider.is_refreshing(cx);
9418
9419 fn pending_completion_container(icon: IconName) -> Div {
9420 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9421 }
9422
9423 let completion = match &self.active_edit_prediction {
9424 Some(prediction) => {
9425 if !self.has_visible_completions_menu() {
9426 const RADIUS: Pixels = px(6.);
9427 const BORDER_WIDTH: Pixels = px(1.);
9428
9429 return Some(
9430 h_flex()
9431 .elevation_2(cx)
9432 .border(BORDER_WIDTH)
9433 .border_color(cx.theme().colors().border)
9434 .when(accept_keystroke.is_none(), |el| {
9435 el.border_color(cx.theme().status().error)
9436 })
9437 .rounded(RADIUS)
9438 .rounded_tl(px(0.))
9439 .overflow_hidden()
9440 .child(div().px_1p5().child(match &prediction.completion {
9441 EditPrediction::MoveWithin { target, snapshot } => {
9442 use text::ToPoint as _;
9443 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9444 {
9445 Icon::new(IconName::ZedPredictDown)
9446 } else {
9447 Icon::new(IconName::ZedPredictUp)
9448 }
9449 }
9450 EditPrediction::MoveOutside { .. } => {
9451 // TODO [zeta2] custom icon for external jump?
9452 Icon::new(provider_icon)
9453 }
9454 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9455 }))
9456 .child(
9457 h_flex()
9458 .gap_1()
9459 .py_1()
9460 .px_2()
9461 .rounded_r(RADIUS - BORDER_WIDTH)
9462 .border_l_1()
9463 .border_color(cx.theme().colors().border)
9464 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9465 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9466 el.child(
9467 Label::new("Hold")
9468 .size(LabelSize::Small)
9469 .when(accept_keystroke.is_none(), |el| {
9470 el.strikethrough()
9471 })
9472 .line_height_style(LineHeightStyle::UiLabel),
9473 )
9474 })
9475 .id("edit_prediction_cursor_popover_keybind")
9476 .when(accept_keystroke.is_none(), |el| {
9477 let status_colors = cx.theme().status();
9478
9479 el.bg(status_colors.error_background)
9480 .border_color(status_colors.error.opacity(0.6))
9481 .child(Icon::new(IconName::Info).color(Color::Error))
9482 .cursor_default()
9483 .hoverable_tooltip(move |_window, cx| {
9484 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9485 .into()
9486 })
9487 })
9488 .when_some(
9489 accept_keystroke.as_ref(),
9490 |el, accept_keystroke| {
9491 el.child(h_flex().children(ui::render_modifiers(
9492 accept_keystroke.modifiers(),
9493 PlatformStyle::platform(),
9494 Some(Color::Default),
9495 Some(IconSize::XSmall.rems().into()),
9496 false,
9497 )))
9498 },
9499 ),
9500 )
9501 .into_any(),
9502 );
9503 }
9504
9505 self.render_edit_prediction_cursor_popover_preview(
9506 prediction,
9507 cursor_point,
9508 style,
9509 cx,
9510 )?
9511 }
9512
9513 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9514 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9515 stale_completion,
9516 cursor_point,
9517 style,
9518 cx,
9519 )?,
9520
9521 None => pending_completion_container(provider_icon)
9522 .child(Label::new("...").size(LabelSize::Small)),
9523 },
9524
9525 None => pending_completion_container(provider_icon)
9526 .child(Label::new("...").size(LabelSize::Small)),
9527 };
9528
9529 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9530 completion
9531 .with_animation(
9532 "loading-completion",
9533 Animation::new(Duration::from_secs(2))
9534 .repeat()
9535 .with_easing(pulsating_between(0.4, 0.8)),
9536 |label, delta| label.opacity(delta),
9537 )
9538 .into_any_element()
9539 } else {
9540 completion.into_any_element()
9541 };
9542
9543 let has_completion = self.active_edit_prediction.is_some();
9544
9545 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9546 Some(
9547 h_flex()
9548 .min_w(min_width)
9549 .max_w(max_width)
9550 .flex_1()
9551 .elevation_2(cx)
9552 .border_color(cx.theme().colors().border)
9553 .child(
9554 div()
9555 .flex_1()
9556 .py_1()
9557 .px_2()
9558 .overflow_hidden()
9559 .child(completion),
9560 )
9561 .when_some(accept_keystroke, |el, accept_keystroke| {
9562 if !accept_keystroke.modifiers().modified() {
9563 return el;
9564 }
9565
9566 el.child(
9567 h_flex()
9568 .h_full()
9569 .border_l_1()
9570 .rounded_r_lg()
9571 .border_color(cx.theme().colors().border)
9572 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9573 .gap_1()
9574 .py_1()
9575 .px_2()
9576 .child(
9577 h_flex()
9578 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9579 .when(is_platform_style_mac, |parent| parent.gap_1())
9580 .child(h_flex().children(ui::render_modifiers(
9581 accept_keystroke.modifiers(),
9582 PlatformStyle::platform(),
9583 Some(if !has_completion {
9584 Color::Muted
9585 } else {
9586 Color::Default
9587 }),
9588 None,
9589 false,
9590 ))),
9591 )
9592 .child(Label::new("Preview").into_any_element())
9593 .opacity(if has_completion { 1.0 } else { 0.4 }),
9594 )
9595 })
9596 .into_any(),
9597 )
9598 }
9599
9600 fn render_edit_prediction_cursor_popover_preview(
9601 &self,
9602 completion: &EditPredictionState,
9603 cursor_point: Point,
9604 style: &EditorStyle,
9605 cx: &mut Context<Editor>,
9606 ) -> Option<Div> {
9607 use text::ToPoint as _;
9608
9609 fn render_relative_row_jump(
9610 prefix: impl Into<String>,
9611 current_row: u32,
9612 target_row: u32,
9613 ) -> Div {
9614 let (row_diff, arrow) = if target_row < current_row {
9615 (current_row - target_row, IconName::ArrowUp)
9616 } else {
9617 (target_row - current_row, IconName::ArrowDown)
9618 };
9619
9620 h_flex()
9621 .child(
9622 Label::new(format!("{}{}", prefix.into(), row_diff))
9623 .color(Color::Muted)
9624 .size(LabelSize::Small),
9625 )
9626 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9627 }
9628
9629 let supports_jump = self
9630 .edit_prediction_provider
9631 .as_ref()
9632 .map(|provider| provider.provider.supports_jump_to_edit())
9633 .unwrap_or(true);
9634
9635 match &completion.completion {
9636 EditPrediction::MoveWithin {
9637 target, snapshot, ..
9638 } => {
9639 if !supports_jump {
9640 return None;
9641 }
9642
9643 Some(
9644 h_flex()
9645 .px_2()
9646 .gap_2()
9647 .flex_1()
9648 .child(
9649 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9650 Icon::new(IconName::ZedPredictDown)
9651 } else {
9652 Icon::new(IconName::ZedPredictUp)
9653 },
9654 )
9655 .child(Label::new("Jump to Edit")),
9656 )
9657 }
9658 EditPrediction::MoveOutside { snapshot, .. } => {
9659 let file_name = snapshot
9660 .file()
9661 .map(|file| file.file_name(cx))
9662 .unwrap_or("untitled");
9663 Some(
9664 h_flex()
9665 .px_2()
9666 .gap_2()
9667 .flex_1()
9668 .child(Icon::new(IconName::ZedPredict))
9669 .child(Label::new(format!("Jump to {file_name}"))),
9670 )
9671 }
9672 EditPrediction::Edit {
9673 edits,
9674 edit_preview,
9675 snapshot,
9676 display_mode: _,
9677 } => {
9678 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9679
9680 let (highlighted_edits, has_more_lines) =
9681 if let Some(edit_preview) = edit_preview.as_ref() {
9682 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9683 .first_line_preview()
9684 } else {
9685 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9686 };
9687
9688 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9689 .with_default_highlights(&style.text, highlighted_edits.highlights);
9690
9691 let preview = h_flex()
9692 .gap_1()
9693 .min_w_16()
9694 .child(styled_text)
9695 .when(has_more_lines, |parent| parent.child("…"));
9696
9697 let left = if supports_jump && first_edit_row != cursor_point.row {
9698 render_relative_row_jump("", cursor_point.row, first_edit_row)
9699 .into_any_element()
9700 } else {
9701 let icon_name =
9702 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9703 Icon::new(icon_name).into_any_element()
9704 };
9705
9706 Some(
9707 h_flex()
9708 .h_full()
9709 .flex_1()
9710 .gap_2()
9711 .pr_1()
9712 .overflow_x_hidden()
9713 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9714 .child(left)
9715 .child(preview),
9716 )
9717 }
9718 }
9719 }
9720
9721 pub fn render_context_menu(
9722 &self,
9723 style: &EditorStyle,
9724 max_height_in_lines: u32,
9725 window: &mut Window,
9726 cx: &mut Context<Editor>,
9727 ) -> Option<AnyElement> {
9728 let menu = self.context_menu.borrow();
9729 let menu = menu.as_ref()?;
9730 if !menu.visible() {
9731 return None;
9732 };
9733 Some(menu.render(style, max_height_in_lines, window, cx))
9734 }
9735
9736 fn render_context_menu_aside(
9737 &mut self,
9738 max_size: Size<Pixels>,
9739 window: &mut Window,
9740 cx: &mut Context<Editor>,
9741 ) -> Option<AnyElement> {
9742 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9743 if menu.visible() {
9744 menu.render_aside(max_size, window, cx)
9745 } else {
9746 None
9747 }
9748 })
9749 }
9750
9751 fn hide_context_menu(
9752 &mut self,
9753 window: &mut Window,
9754 cx: &mut Context<Self>,
9755 ) -> Option<CodeContextMenu> {
9756 cx.notify();
9757 self.completion_tasks.clear();
9758 let context_menu = self.context_menu.borrow_mut().take();
9759 self.stale_edit_prediction_in_menu.take();
9760 self.update_visible_edit_prediction(window, cx);
9761 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9762 && let Some(completion_provider) = &self.completion_provider
9763 {
9764 completion_provider.selection_changed(None, window, cx);
9765 }
9766 context_menu
9767 }
9768
9769 fn show_snippet_choices(
9770 &mut self,
9771 choices: &Vec<String>,
9772 selection: Range<Anchor>,
9773 cx: &mut Context<Self>,
9774 ) {
9775 let Some((_, buffer, _)) = self
9776 .buffer()
9777 .read(cx)
9778 .excerpt_containing(selection.start, cx)
9779 else {
9780 return;
9781 };
9782 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9783 else {
9784 return;
9785 };
9786 if buffer != end_buffer {
9787 log::error!("expected anchor range to have matching buffer IDs");
9788 return;
9789 }
9790
9791 let id = post_inc(&mut self.next_completion_id);
9792 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9793 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9794 CompletionsMenu::new_snippet_choices(
9795 id,
9796 true,
9797 choices,
9798 selection,
9799 buffer,
9800 snippet_sort_order,
9801 ),
9802 ));
9803 }
9804
9805 pub fn insert_snippet(
9806 &mut self,
9807 insertion_ranges: &[Range<usize>],
9808 snippet: Snippet,
9809 window: &mut Window,
9810 cx: &mut Context<Self>,
9811 ) -> Result<()> {
9812 struct Tabstop<T> {
9813 is_end_tabstop: bool,
9814 ranges: Vec<Range<T>>,
9815 choices: Option<Vec<String>>,
9816 }
9817
9818 let tabstops = self.buffer.update(cx, |buffer, cx| {
9819 let snippet_text: Arc<str> = snippet.text.clone().into();
9820 let edits = insertion_ranges
9821 .iter()
9822 .cloned()
9823 .map(|range| (range, snippet_text.clone()));
9824 let autoindent_mode = AutoindentMode::Block {
9825 original_indent_columns: Vec::new(),
9826 };
9827 buffer.edit(edits, Some(autoindent_mode), cx);
9828
9829 let snapshot = &*buffer.read(cx);
9830 let snippet = &snippet;
9831 snippet
9832 .tabstops
9833 .iter()
9834 .map(|tabstop| {
9835 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9836 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9837 });
9838 let mut tabstop_ranges = tabstop
9839 .ranges
9840 .iter()
9841 .flat_map(|tabstop_range| {
9842 let mut delta = 0_isize;
9843 insertion_ranges.iter().map(move |insertion_range| {
9844 let insertion_start = insertion_range.start as isize + delta;
9845 delta +=
9846 snippet.text.len() as isize - insertion_range.len() as isize;
9847
9848 let start = ((insertion_start + tabstop_range.start) as usize)
9849 .min(snapshot.len());
9850 let end = ((insertion_start + tabstop_range.end) as usize)
9851 .min(snapshot.len());
9852 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9853 })
9854 })
9855 .collect::<Vec<_>>();
9856 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9857
9858 Tabstop {
9859 is_end_tabstop,
9860 ranges: tabstop_ranges,
9861 choices: tabstop.choices.clone(),
9862 }
9863 })
9864 .collect::<Vec<_>>()
9865 });
9866 if let Some(tabstop) = tabstops.first() {
9867 self.change_selections(Default::default(), window, cx, |s| {
9868 // Reverse order so that the first range is the newest created selection.
9869 // Completions will use it and autoscroll will prioritize it.
9870 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9871 });
9872
9873 if let Some(choices) = &tabstop.choices
9874 && let Some(selection) = tabstop.ranges.first()
9875 {
9876 self.show_snippet_choices(choices, selection.clone(), cx)
9877 }
9878
9879 // If we're already at the last tabstop and it's at the end of the snippet,
9880 // we're done, we don't need to keep the state around.
9881 if !tabstop.is_end_tabstop {
9882 let choices = tabstops
9883 .iter()
9884 .map(|tabstop| tabstop.choices.clone())
9885 .collect();
9886
9887 let ranges = tabstops
9888 .into_iter()
9889 .map(|tabstop| tabstop.ranges)
9890 .collect::<Vec<_>>();
9891
9892 self.snippet_stack.push(SnippetState {
9893 active_index: 0,
9894 ranges,
9895 choices,
9896 });
9897 }
9898
9899 // Check whether the just-entered snippet ends with an auto-closable bracket.
9900 if self.autoclose_regions.is_empty() {
9901 let snapshot = self.buffer.read(cx).snapshot(cx);
9902 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
9903 let selection_head = selection.head();
9904 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9905 continue;
9906 };
9907
9908 let mut bracket_pair = None;
9909 let max_lookup_length = scope
9910 .brackets()
9911 .map(|(pair, _)| {
9912 pair.start
9913 .as_str()
9914 .chars()
9915 .count()
9916 .max(pair.end.as_str().chars().count())
9917 })
9918 .max();
9919 if let Some(max_lookup_length) = max_lookup_length {
9920 let next_text = snapshot
9921 .chars_at(selection_head)
9922 .take(max_lookup_length)
9923 .collect::<String>();
9924 let prev_text = snapshot
9925 .reversed_chars_at(selection_head)
9926 .take(max_lookup_length)
9927 .collect::<String>();
9928
9929 for (pair, enabled) in scope.brackets() {
9930 if enabled
9931 && pair.close
9932 && prev_text.starts_with(pair.start.as_str())
9933 && next_text.starts_with(pair.end.as_str())
9934 {
9935 bracket_pair = Some(pair.clone());
9936 break;
9937 }
9938 }
9939 }
9940
9941 if let Some(pair) = bracket_pair {
9942 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9943 let autoclose_enabled =
9944 self.use_autoclose && snapshot_settings.use_autoclose;
9945 if autoclose_enabled {
9946 let start = snapshot.anchor_after(selection_head);
9947 let end = snapshot.anchor_after(selection_head);
9948 self.autoclose_regions.push(AutocloseRegion {
9949 selection_id: selection.id,
9950 range: start..end,
9951 pair,
9952 });
9953 }
9954 }
9955 }
9956 }
9957 }
9958 Ok(())
9959 }
9960
9961 pub fn move_to_next_snippet_tabstop(
9962 &mut self,
9963 window: &mut Window,
9964 cx: &mut Context<Self>,
9965 ) -> bool {
9966 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9967 }
9968
9969 pub fn move_to_prev_snippet_tabstop(
9970 &mut self,
9971 window: &mut Window,
9972 cx: &mut Context<Self>,
9973 ) -> bool {
9974 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9975 }
9976
9977 pub fn move_to_snippet_tabstop(
9978 &mut self,
9979 bias: Bias,
9980 window: &mut Window,
9981 cx: &mut Context<Self>,
9982 ) -> bool {
9983 if let Some(mut snippet) = self.snippet_stack.pop() {
9984 match bias {
9985 Bias::Left => {
9986 if snippet.active_index > 0 {
9987 snippet.active_index -= 1;
9988 } else {
9989 self.snippet_stack.push(snippet);
9990 return false;
9991 }
9992 }
9993 Bias::Right => {
9994 if snippet.active_index + 1 < snippet.ranges.len() {
9995 snippet.active_index += 1;
9996 } else {
9997 self.snippet_stack.push(snippet);
9998 return false;
9999 }
10000 }
10001 }
10002 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10003 self.change_selections(Default::default(), window, cx, |s| {
10004 // Reverse order so that the first range is the newest created selection.
10005 // Completions will use it and autoscroll will prioritize it.
10006 s.select_ranges(current_ranges.iter().rev().cloned())
10007 });
10008
10009 if let Some(choices) = &snippet.choices[snippet.active_index]
10010 && let Some(selection) = current_ranges.first()
10011 {
10012 self.show_snippet_choices(choices, selection.clone(), cx);
10013 }
10014
10015 // If snippet state is not at the last tabstop, push it back on the stack
10016 if snippet.active_index + 1 < snippet.ranges.len() {
10017 self.snippet_stack.push(snippet);
10018 }
10019 return true;
10020 }
10021 }
10022
10023 false
10024 }
10025
10026 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10027 self.transact(window, cx, |this, window, cx| {
10028 this.select_all(&SelectAll, window, cx);
10029 this.insert("", window, cx);
10030 });
10031 }
10032
10033 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10034 if self.read_only(cx) {
10035 return;
10036 }
10037 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10038 self.transact(window, cx, |this, window, cx| {
10039 this.select_autoclose_pair(window, cx);
10040
10041 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10042
10043 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10044 if !this.linked_edit_ranges.is_empty() {
10045 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10046 let snapshot = this.buffer.read(cx).snapshot(cx);
10047
10048 for selection in selections.iter() {
10049 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10050 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10051 if selection_start.buffer_id != selection_end.buffer_id {
10052 continue;
10053 }
10054 if let Some(ranges) =
10055 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10056 {
10057 for (buffer, entries) in ranges {
10058 linked_ranges.entry(buffer).or_default().extend(entries);
10059 }
10060 }
10061 }
10062 }
10063
10064 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10065 for selection in &mut selections {
10066 if selection.is_empty() {
10067 let old_head = selection.head();
10068 let mut new_head =
10069 movement::left(&display_map, old_head.to_display_point(&display_map))
10070 .to_point(&display_map);
10071 if let Some((buffer, line_buffer_range)) = display_map
10072 .buffer_snapshot()
10073 .buffer_line_for_row(MultiBufferRow(old_head.row))
10074 {
10075 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10076 let indent_len = match indent_size.kind {
10077 IndentKind::Space => {
10078 buffer.settings_at(line_buffer_range.start, cx).tab_size
10079 }
10080 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10081 };
10082 if old_head.column <= indent_size.len && old_head.column > 0 {
10083 let indent_len = indent_len.get();
10084 new_head = cmp::min(
10085 new_head,
10086 MultiBufferPoint::new(
10087 old_head.row,
10088 ((old_head.column - 1) / indent_len) * indent_len,
10089 ),
10090 );
10091 }
10092 }
10093
10094 selection.set_head(new_head, SelectionGoal::None);
10095 }
10096 }
10097
10098 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10099 this.insert("", window, cx);
10100 let empty_str: Arc<str> = Arc::from("");
10101 for (buffer, edits) in linked_ranges {
10102 let snapshot = buffer.read(cx).snapshot();
10103 use text::ToPoint as TP;
10104
10105 let edits = edits
10106 .into_iter()
10107 .map(|range| {
10108 let end_point = TP::to_point(&range.end, &snapshot);
10109 let mut start_point = TP::to_point(&range.start, &snapshot);
10110
10111 if end_point == start_point {
10112 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10113 .saturating_sub(1);
10114 start_point =
10115 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10116 };
10117
10118 (start_point..end_point, empty_str.clone())
10119 })
10120 .sorted_by_key(|(range, _)| range.start)
10121 .collect::<Vec<_>>();
10122 buffer.update(cx, |this, cx| {
10123 this.edit(edits, None, cx);
10124 })
10125 }
10126 this.refresh_edit_prediction(true, false, window, cx);
10127 refresh_linked_ranges(this, window, cx);
10128 });
10129 }
10130
10131 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10132 if self.read_only(cx) {
10133 return;
10134 }
10135 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10136 self.transact(window, cx, |this, window, cx| {
10137 this.change_selections(Default::default(), window, cx, |s| {
10138 s.move_with(|map, selection| {
10139 if selection.is_empty() {
10140 let cursor = movement::right(map, selection.head());
10141 selection.end = cursor;
10142 selection.reversed = true;
10143 selection.goal = SelectionGoal::None;
10144 }
10145 })
10146 });
10147 this.insert("", window, cx);
10148 this.refresh_edit_prediction(true, false, window, cx);
10149 });
10150 }
10151
10152 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10153 if self.mode.is_single_line() {
10154 cx.propagate();
10155 return;
10156 }
10157
10158 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10159 if self.move_to_prev_snippet_tabstop(window, cx) {
10160 return;
10161 }
10162 self.outdent(&Outdent, window, cx);
10163 }
10164
10165 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10166 if self.mode.is_single_line() {
10167 cx.propagate();
10168 return;
10169 }
10170
10171 if self.move_to_next_snippet_tabstop(window, cx) {
10172 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10173 return;
10174 }
10175 if self.read_only(cx) {
10176 return;
10177 }
10178 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10179 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10180 let buffer = self.buffer.read(cx);
10181 let snapshot = buffer.snapshot(cx);
10182 let rows_iter = selections.iter().map(|s| s.head().row);
10183 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10184
10185 let has_some_cursor_in_whitespace = selections
10186 .iter()
10187 .filter(|selection| selection.is_empty())
10188 .any(|selection| {
10189 let cursor = selection.head();
10190 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10191 cursor.column < current_indent.len
10192 });
10193
10194 let mut edits = Vec::new();
10195 let mut prev_edited_row = 0;
10196 let mut row_delta = 0;
10197 for selection in &mut selections {
10198 if selection.start.row != prev_edited_row {
10199 row_delta = 0;
10200 }
10201 prev_edited_row = selection.end.row;
10202
10203 // If the selection is non-empty, then increase the indentation of the selected lines.
10204 if !selection.is_empty() {
10205 row_delta =
10206 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10207 continue;
10208 }
10209
10210 let cursor = selection.head();
10211 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10212 if let Some(suggested_indent) =
10213 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10214 {
10215 // Don't do anything if already at suggested indent
10216 // and there is any other cursor which is not
10217 if has_some_cursor_in_whitespace
10218 && cursor.column == current_indent.len
10219 && current_indent.len == suggested_indent.len
10220 {
10221 continue;
10222 }
10223
10224 // Adjust line and move cursor to suggested indent
10225 // if cursor is not at suggested indent
10226 if cursor.column < suggested_indent.len
10227 && cursor.column <= current_indent.len
10228 && current_indent.len <= suggested_indent.len
10229 {
10230 selection.start = Point::new(cursor.row, suggested_indent.len);
10231 selection.end = selection.start;
10232 if row_delta == 0 {
10233 edits.extend(Buffer::edit_for_indent_size_adjustment(
10234 cursor.row,
10235 current_indent,
10236 suggested_indent,
10237 ));
10238 row_delta = suggested_indent.len - current_indent.len;
10239 }
10240 continue;
10241 }
10242
10243 // If current indent is more than suggested indent
10244 // only move cursor to current indent and skip indent
10245 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10246 selection.start = Point::new(cursor.row, current_indent.len);
10247 selection.end = selection.start;
10248 continue;
10249 }
10250 }
10251
10252 // Otherwise, insert a hard or soft tab.
10253 let settings = buffer.language_settings_at(cursor, cx);
10254 let tab_size = if settings.hard_tabs {
10255 IndentSize::tab()
10256 } else {
10257 let tab_size = settings.tab_size.get();
10258 let indent_remainder = snapshot
10259 .text_for_range(Point::new(cursor.row, 0)..cursor)
10260 .flat_map(str::chars)
10261 .fold(row_delta % tab_size, |counter: u32, c| {
10262 if c == '\t' {
10263 0
10264 } else {
10265 (counter + 1) % tab_size
10266 }
10267 });
10268
10269 let chars_to_next_tab_stop = tab_size - indent_remainder;
10270 IndentSize::spaces(chars_to_next_tab_stop)
10271 };
10272 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10273 selection.end = selection.start;
10274 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10275 row_delta += tab_size.len;
10276 }
10277
10278 self.transact(window, cx, |this, window, cx| {
10279 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10280 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10281 this.refresh_edit_prediction(true, false, window, cx);
10282 });
10283 }
10284
10285 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10286 if self.read_only(cx) {
10287 return;
10288 }
10289 if self.mode.is_single_line() {
10290 cx.propagate();
10291 return;
10292 }
10293
10294 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10295 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10296 let mut prev_edited_row = 0;
10297 let mut row_delta = 0;
10298 let mut edits = Vec::new();
10299 let buffer = self.buffer.read(cx);
10300 let snapshot = buffer.snapshot(cx);
10301 for selection in &mut selections {
10302 if selection.start.row != prev_edited_row {
10303 row_delta = 0;
10304 }
10305 prev_edited_row = selection.end.row;
10306
10307 row_delta =
10308 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10309 }
10310
10311 self.transact(window, cx, |this, window, cx| {
10312 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10313 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10314 });
10315 }
10316
10317 fn indent_selection(
10318 buffer: &MultiBuffer,
10319 snapshot: &MultiBufferSnapshot,
10320 selection: &mut Selection<Point>,
10321 edits: &mut Vec<(Range<Point>, String)>,
10322 delta_for_start_row: u32,
10323 cx: &App,
10324 ) -> u32 {
10325 let settings = buffer.language_settings_at(selection.start, cx);
10326 let tab_size = settings.tab_size.get();
10327 let indent_kind = if settings.hard_tabs {
10328 IndentKind::Tab
10329 } else {
10330 IndentKind::Space
10331 };
10332 let mut start_row = selection.start.row;
10333 let mut end_row = selection.end.row + 1;
10334
10335 // If a selection ends at the beginning of a line, don't indent
10336 // that last line.
10337 if selection.end.column == 0 && selection.end.row > selection.start.row {
10338 end_row -= 1;
10339 }
10340
10341 // Avoid re-indenting a row that has already been indented by a
10342 // previous selection, but still update this selection's column
10343 // to reflect that indentation.
10344 if delta_for_start_row > 0 {
10345 start_row += 1;
10346 selection.start.column += delta_for_start_row;
10347 if selection.end.row == selection.start.row {
10348 selection.end.column += delta_for_start_row;
10349 }
10350 }
10351
10352 let mut delta_for_end_row = 0;
10353 let has_multiple_rows = start_row + 1 != end_row;
10354 for row in start_row..end_row {
10355 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10356 let indent_delta = match (current_indent.kind, indent_kind) {
10357 (IndentKind::Space, IndentKind::Space) => {
10358 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10359 IndentSize::spaces(columns_to_next_tab_stop)
10360 }
10361 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10362 (_, IndentKind::Tab) => IndentSize::tab(),
10363 };
10364
10365 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10366 0
10367 } else {
10368 selection.start.column
10369 };
10370 let row_start = Point::new(row, start);
10371 edits.push((
10372 row_start..row_start,
10373 indent_delta.chars().collect::<String>(),
10374 ));
10375
10376 // Update this selection's endpoints to reflect the indentation.
10377 if row == selection.start.row {
10378 selection.start.column += indent_delta.len;
10379 }
10380 if row == selection.end.row {
10381 selection.end.column += indent_delta.len;
10382 delta_for_end_row = indent_delta.len;
10383 }
10384 }
10385
10386 if selection.start.row == selection.end.row {
10387 delta_for_start_row + delta_for_end_row
10388 } else {
10389 delta_for_end_row
10390 }
10391 }
10392
10393 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10394 if self.read_only(cx) {
10395 return;
10396 }
10397 if self.mode.is_single_line() {
10398 cx.propagate();
10399 return;
10400 }
10401
10402 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10403 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10404 let selections = self.selections.all::<Point>(&display_map);
10405 let mut deletion_ranges = Vec::new();
10406 let mut last_outdent = None;
10407 {
10408 let buffer = self.buffer.read(cx);
10409 let snapshot = buffer.snapshot(cx);
10410 for selection in &selections {
10411 let settings = buffer.language_settings_at(selection.start, cx);
10412 let tab_size = settings.tab_size.get();
10413 let mut rows = selection.spanned_rows(false, &display_map);
10414
10415 // Avoid re-outdenting a row that has already been outdented by a
10416 // previous selection.
10417 if let Some(last_row) = last_outdent
10418 && last_row == rows.start
10419 {
10420 rows.start = rows.start.next_row();
10421 }
10422 let has_multiple_rows = rows.len() > 1;
10423 for row in rows.iter_rows() {
10424 let indent_size = snapshot.indent_size_for_line(row);
10425 if indent_size.len > 0 {
10426 let deletion_len = match indent_size.kind {
10427 IndentKind::Space => {
10428 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10429 if columns_to_prev_tab_stop == 0 {
10430 tab_size
10431 } else {
10432 columns_to_prev_tab_stop
10433 }
10434 }
10435 IndentKind::Tab => 1,
10436 };
10437 let start = if has_multiple_rows
10438 || deletion_len > selection.start.column
10439 || indent_size.len < selection.start.column
10440 {
10441 0
10442 } else {
10443 selection.start.column - deletion_len
10444 };
10445 deletion_ranges.push(
10446 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10447 );
10448 last_outdent = Some(row);
10449 }
10450 }
10451 }
10452 }
10453
10454 self.transact(window, cx, |this, window, cx| {
10455 this.buffer.update(cx, |buffer, cx| {
10456 let empty_str: Arc<str> = Arc::default();
10457 buffer.edit(
10458 deletion_ranges
10459 .into_iter()
10460 .map(|range| (range, empty_str.clone())),
10461 None,
10462 cx,
10463 );
10464 });
10465 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10466 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10467 });
10468 }
10469
10470 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10471 if self.read_only(cx) {
10472 return;
10473 }
10474 if self.mode.is_single_line() {
10475 cx.propagate();
10476 return;
10477 }
10478
10479 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10480 let selections = self
10481 .selections
10482 .all::<usize>(&self.display_snapshot(cx))
10483 .into_iter()
10484 .map(|s| s.range());
10485
10486 self.transact(window, cx, |this, window, cx| {
10487 this.buffer.update(cx, |buffer, cx| {
10488 buffer.autoindent_ranges(selections, cx);
10489 });
10490 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10491 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10492 });
10493 }
10494
10495 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10496 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10497 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10498 let selections = self.selections.all::<Point>(&display_map);
10499
10500 let mut new_cursors = Vec::new();
10501 let mut edit_ranges = Vec::new();
10502 let mut selections = selections.iter().peekable();
10503 while let Some(selection) = selections.next() {
10504 let mut rows = selection.spanned_rows(false, &display_map);
10505
10506 // Accumulate contiguous regions of rows that we want to delete.
10507 while let Some(next_selection) = selections.peek() {
10508 let next_rows = next_selection.spanned_rows(false, &display_map);
10509 if next_rows.start <= rows.end {
10510 rows.end = next_rows.end;
10511 selections.next().unwrap();
10512 } else {
10513 break;
10514 }
10515 }
10516
10517 let buffer = display_map.buffer_snapshot();
10518 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10519 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10520 // If there's a line after the range, delete the \n from the end of the row range
10521 (
10522 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10523 rows.end,
10524 )
10525 } else {
10526 // If there isn't a line after the range, delete the \n from the line before the
10527 // start of the row range
10528 edit_start = edit_start.saturating_sub(1);
10529 (buffer.len(), rows.start.previous_row())
10530 };
10531
10532 let text_layout_details = self.text_layout_details(window);
10533 let x = display_map.x_for_display_point(
10534 selection.head().to_display_point(&display_map),
10535 &text_layout_details,
10536 );
10537 let row = Point::new(target_row.0, 0)
10538 .to_display_point(&display_map)
10539 .row();
10540 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10541
10542 new_cursors.push((
10543 selection.id,
10544 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10545 SelectionGoal::None,
10546 ));
10547 edit_ranges.push(edit_start..edit_end);
10548 }
10549
10550 self.transact(window, cx, |this, window, cx| {
10551 let buffer = this.buffer.update(cx, |buffer, cx| {
10552 let empty_str: Arc<str> = Arc::default();
10553 buffer.edit(
10554 edit_ranges
10555 .into_iter()
10556 .map(|range| (range, empty_str.clone())),
10557 None,
10558 cx,
10559 );
10560 buffer.snapshot(cx)
10561 });
10562 let new_selections = new_cursors
10563 .into_iter()
10564 .map(|(id, cursor, goal)| {
10565 let cursor = cursor.to_point(&buffer);
10566 Selection {
10567 id,
10568 start: cursor,
10569 end: cursor,
10570 reversed: false,
10571 goal,
10572 }
10573 })
10574 .collect();
10575
10576 this.change_selections(Default::default(), window, cx, |s| {
10577 s.select(new_selections);
10578 });
10579 });
10580 }
10581
10582 pub fn join_lines_impl(
10583 &mut self,
10584 insert_whitespace: bool,
10585 window: &mut Window,
10586 cx: &mut Context<Self>,
10587 ) {
10588 if self.read_only(cx) {
10589 return;
10590 }
10591 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10592 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10593 let start = MultiBufferRow(selection.start.row);
10594 // Treat single line selections as if they include the next line. Otherwise this action
10595 // would do nothing for single line selections individual cursors.
10596 let end = if selection.start.row == selection.end.row {
10597 MultiBufferRow(selection.start.row + 1)
10598 } else {
10599 MultiBufferRow(selection.end.row)
10600 };
10601
10602 if let Some(last_row_range) = row_ranges.last_mut()
10603 && start <= last_row_range.end
10604 {
10605 last_row_range.end = end;
10606 continue;
10607 }
10608 row_ranges.push(start..end);
10609 }
10610
10611 let snapshot = self.buffer.read(cx).snapshot(cx);
10612 let mut cursor_positions = Vec::new();
10613 for row_range in &row_ranges {
10614 let anchor = snapshot.anchor_before(Point::new(
10615 row_range.end.previous_row().0,
10616 snapshot.line_len(row_range.end.previous_row()),
10617 ));
10618 cursor_positions.push(anchor..anchor);
10619 }
10620
10621 self.transact(window, cx, |this, window, cx| {
10622 for row_range in row_ranges.into_iter().rev() {
10623 for row in row_range.iter_rows().rev() {
10624 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10625 let next_line_row = row.next_row();
10626 let indent = snapshot.indent_size_for_line(next_line_row);
10627 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10628
10629 let replace =
10630 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10631 " "
10632 } else {
10633 ""
10634 };
10635
10636 this.buffer.update(cx, |buffer, cx| {
10637 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10638 });
10639 }
10640 }
10641
10642 this.change_selections(Default::default(), window, cx, |s| {
10643 s.select_anchor_ranges(cursor_positions)
10644 });
10645 });
10646 }
10647
10648 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10649 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10650 self.join_lines_impl(true, window, cx);
10651 }
10652
10653 pub fn sort_lines_case_sensitive(
10654 &mut self,
10655 _: &SortLinesCaseSensitive,
10656 window: &mut Window,
10657 cx: &mut Context<Self>,
10658 ) {
10659 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10660 }
10661
10662 pub fn sort_lines_by_length(
10663 &mut self,
10664 _: &SortLinesByLength,
10665 window: &mut Window,
10666 cx: &mut Context<Self>,
10667 ) {
10668 self.manipulate_immutable_lines(window, cx, |lines| {
10669 lines.sort_by_key(|&line| line.chars().count())
10670 })
10671 }
10672
10673 pub fn sort_lines_case_insensitive(
10674 &mut self,
10675 _: &SortLinesCaseInsensitive,
10676 window: &mut Window,
10677 cx: &mut Context<Self>,
10678 ) {
10679 self.manipulate_immutable_lines(window, cx, |lines| {
10680 lines.sort_by_key(|line| line.to_lowercase())
10681 })
10682 }
10683
10684 pub fn unique_lines_case_insensitive(
10685 &mut self,
10686 _: &UniqueLinesCaseInsensitive,
10687 window: &mut Window,
10688 cx: &mut Context<Self>,
10689 ) {
10690 self.manipulate_immutable_lines(window, cx, |lines| {
10691 let mut seen = HashSet::default();
10692 lines.retain(|line| seen.insert(line.to_lowercase()));
10693 })
10694 }
10695
10696 pub fn unique_lines_case_sensitive(
10697 &mut self,
10698 _: &UniqueLinesCaseSensitive,
10699 window: &mut Window,
10700 cx: &mut Context<Self>,
10701 ) {
10702 self.manipulate_immutable_lines(window, cx, |lines| {
10703 let mut seen = HashSet::default();
10704 lines.retain(|line| seen.insert(*line));
10705 })
10706 }
10707
10708 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10709 let snapshot = self.buffer.read(cx).snapshot(cx);
10710 for selection in self.selections.disjoint_anchors_arc().iter() {
10711 if snapshot
10712 .language_at(selection.start)
10713 .and_then(|lang| lang.config().wrap_characters.as_ref())
10714 .is_some()
10715 {
10716 return true;
10717 }
10718 }
10719 false
10720 }
10721
10722 fn wrap_selections_in_tag(
10723 &mut self,
10724 _: &WrapSelectionsInTag,
10725 window: &mut Window,
10726 cx: &mut Context<Self>,
10727 ) {
10728 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10729
10730 let snapshot = self.buffer.read(cx).snapshot(cx);
10731
10732 let mut edits = Vec::new();
10733 let mut boundaries = Vec::new();
10734
10735 for selection in self
10736 .selections
10737 .all::<Point>(&self.display_snapshot(cx))
10738 .iter()
10739 {
10740 let Some(wrap_config) = snapshot
10741 .language_at(selection.start)
10742 .and_then(|lang| lang.config().wrap_characters.clone())
10743 else {
10744 continue;
10745 };
10746
10747 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10748 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10749
10750 let start_before = snapshot.anchor_before(selection.start);
10751 let end_after = snapshot.anchor_after(selection.end);
10752
10753 edits.push((start_before..start_before, open_tag));
10754 edits.push((end_after..end_after, close_tag));
10755
10756 boundaries.push((
10757 start_before,
10758 end_after,
10759 wrap_config.start_prefix.len(),
10760 wrap_config.end_suffix.len(),
10761 ));
10762 }
10763
10764 if edits.is_empty() {
10765 return;
10766 }
10767
10768 self.transact(window, cx, |this, window, cx| {
10769 let buffer = this.buffer.update(cx, |buffer, cx| {
10770 buffer.edit(edits, None, cx);
10771 buffer.snapshot(cx)
10772 });
10773
10774 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10775 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10776 boundaries.into_iter()
10777 {
10778 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10779 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10780 new_selections.push(open_offset..open_offset);
10781 new_selections.push(close_offset..close_offset);
10782 }
10783
10784 this.change_selections(Default::default(), window, cx, |s| {
10785 s.select_ranges(new_selections);
10786 });
10787
10788 this.request_autoscroll(Autoscroll::fit(), cx);
10789 });
10790 }
10791
10792 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10793 let Some(project) = self.project.clone() else {
10794 return;
10795 };
10796 self.reload(project, window, cx)
10797 .detach_and_notify_err(window, cx);
10798 }
10799
10800 pub fn restore_file(
10801 &mut self,
10802 _: &::git::RestoreFile,
10803 window: &mut Window,
10804 cx: &mut Context<Self>,
10805 ) {
10806 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10807 let mut buffer_ids = HashSet::default();
10808 let snapshot = self.buffer().read(cx).snapshot(cx);
10809 for selection in self.selections.all::<usize>(&self.display_snapshot(cx)) {
10810 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10811 }
10812
10813 let buffer = self.buffer().read(cx);
10814 let ranges = buffer_ids
10815 .into_iter()
10816 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10817 .collect::<Vec<_>>();
10818
10819 self.restore_hunks_in_ranges(ranges, window, cx);
10820 }
10821
10822 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10823 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10824 let selections = self
10825 .selections
10826 .all(&self.display_snapshot(cx))
10827 .into_iter()
10828 .map(|s| s.range())
10829 .collect();
10830 self.restore_hunks_in_ranges(selections, window, cx);
10831 }
10832
10833 pub fn restore_hunks_in_ranges(
10834 &mut self,
10835 ranges: Vec<Range<Point>>,
10836 window: &mut Window,
10837 cx: &mut Context<Editor>,
10838 ) {
10839 let mut revert_changes = HashMap::default();
10840 let chunk_by = self
10841 .snapshot(window, cx)
10842 .hunks_for_ranges(ranges)
10843 .into_iter()
10844 .chunk_by(|hunk| hunk.buffer_id);
10845 for (buffer_id, hunks) in &chunk_by {
10846 let hunks = hunks.collect::<Vec<_>>();
10847 for hunk in &hunks {
10848 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10849 }
10850 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10851 }
10852 drop(chunk_by);
10853 if !revert_changes.is_empty() {
10854 self.transact(window, cx, |editor, window, cx| {
10855 editor.restore(revert_changes, window, cx);
10856 });
10857 }
10858 }
10859
10860 pub fn open_active_item_in_terminal(
10861 &mut self,
10862 _: &OpenInTerminal,
10863 window: &mut Window,
10864 cx: &mut Context<Self>,
10865 ) {
10866 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10867 let project_path = buffer.read(cx).project_path(cx)?;
10868 let project = self.project()?.read(cx);
10869 let entry = project.entry_for_path(&project_path, cx)?;
10870 let parent = match &entry.canonical_path {
10871 Some(canonical_path) => canonical_path.to_path_buf(),
10872 None => project.absolute_path(&project_path, cx)?,
10873 }
10874 .parent()?
10875 .to_path_buf();
10876 Some(parent)
10877 }) {
10878 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10879 }
10880 }
10881
10882 fn set_breakpoint_context_menu(
10883 &mut self,
10884 display_row: DisplayRow,
10885 position: Option<Anchor>,
10886 clicked_point: gpui::Point<Pixels>,
10887 window: &mut Window,
10888 cx: &mut Context<Self>,
10889 ) {
10890 let source = self
10891 .buffer
10892 .read(cx)
10893 .snapshot(cx)
10894 .anchor_before(Point::new(display_row.0, 0u32));
10895
10896 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10897
10898 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10899 self,
10900 source,
10901 clicked_point,
10902 context_menu,
10903 window,
10904 cx,
10905 );
10906 }
10907
10908 fn add_edit_breakpoint_block(
10909 &mut self,
10910 anchor: Anchor,
10911 breakpoint: &Breakpoint,
10912 edit_action: BreakpointPromptEditAction,
10913 window: &mut Window,
10914 cx: &mut Context<Self>,
10915 ) {
10916 let weak_editor = cx.weak_entity();
10917 let bp_prompt = cx.new(|cx| {
10918 BreakpointPromptEditor::new(
10919 weak_editor,
10920 anchor,
10921 breakpoint.clone(),
10922 edit_action,
10923 window,
10924 cx,
10925 )
10926 });
10927
10928 let height = bp_prompt.update(cx, |this, cx| {
10929 this.prompt
10930 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10931 });
10932 let cloned_prompt = bp_prompt.clone();
10933 let blocks = vec![BlockProperties {
10934 style: BlockStyle::Sticky,
10935 placement: BlockPlacement::Above(anchor),
10936 height: Some(height),
10937 render: Arc::new(move |cx| {
10938 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10939 cloned_prompt.clone().into_any_element()
10940 }),
10941 priority: 0,
10942 }];
10943
10944 let focus_handle = bp_prompt.focus_handle(cx);
10945 window.focus(&focus_handle);
10946
10947 let block_ids = self.insert_blocks(blocks, None, cx);
10948 bp_prompt.update(cx, |prompt, _| {
10949 prompt.add_block_ids(block_ids);
10950 });
10951 }
10952
10953 pub(crate) fn breakpoint_at_row(
10954 &self,
10955 row: u32,
10956 window: &mut Window,
10957 cx: &mut Context<Self>,
10958 ) -> Option<(Anchor, Breakpoint)> {
10959 let snapshot = self.snapshot(window, cx);
10960 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
10961
10962 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10963 }
10964
10965 pub(crate) fn breakpoint_at_anchor(
10966 &self,
10967 breakpoint_position: Anchor,
10968 snapshot: &EditorSnapshot,
10969 cx: &mut Context<Self>,
10970 ) -> Option<(Anchor, Breakpoint)> {
10971 let buffer = self
10972 .buffer
10973 .read(cx)
10974 .buffer_for_anchor(breakpoint_position, cx)?;
10975
10976 let enclosing_excerpt = breakpoint_position.excerpt_id;
10977 let buffer_snapshot = buffer.read(cx).snapshot();
10978
10979 let row = buffer_snapshot
10980 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10981 .row;
10982
10983 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
10984 let anchor_end = snapshot
10985 .buffer_snapshot()
10986 .anchor_after(Point::new(row, line_len));
10987
10988 self.breakpoint_store
10989 .as_ref()?
10990 .read_with(cx, |breakpoint_store, cx| {
10991 breakpoint_store
10992 .breakpoints(
10993 &buffer,
10994 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10995 &buffer_snapshot,
10996 cx,
10997 )
10998 .next()
10999 .and_then(|(bp, _)| {
11000 let breakpoint_row = buffer_snapshot
11001 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11002 .row;
11003
11004 if breakpoint_row == row {
11005 snapshot
11006 .buffer_snapshot()
11007 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11008 .map(|position| (position, bp.bp.clone()))
11009 } else {
11010 None
11011 }
11012 })
11013 })
11014 }
11015
11016 pub fn edit_log_breakpoint(
11017 &mut self,
11018 _: &EditLogBreakpoint,
11019 window: &mut Window,
11020 cx: &mut Context<Self>,
11021 ) {
11022 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11023 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11024 message: None,
11025 state: BreakpointState::Enabled,
11026 condition: None,
11027 hit_condition: None,
11028 });
11029
11030 self.add_edit_breakpoint_block(
11031 anchor,
11032 &breakpoint,
11033 BreakpointPromptEditAction::Log,
11034 window,
11035 cx,
11036 );
11037 }
11038 }
11039
11040 fn breakpoints_at_cursors(
11041 &self,
11042 window: &mut Window,
11043 cx: &mut Context<Self>,
11044 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11045 let snapshot = self.snapshot(window, cx);
11046 let cursors = self
11047 .selections
11048 .disjoint_anchors_arc()
11049 .iter()
11050 .map(|selection| {
11051 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11052
11053 let breakpoint_position = self
11054 .breakpoint_at_row(cursor_position.row, window, cx)
11055 .map(|bp| bp.0)
11056 .unwrap_or_else(|| {
11057 snapshot
11058 .display_snapshot
11059 .buffer_snapshot()
11060 .anchor_after(Point::new(cursor_position.row, 0))
11061 });
11062
11063 let breakpoint = self
11064 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11065 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11066
11067 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11068 })
11069 // 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.
11070 .collect::<HashMap<Anchor, _>>();
11071
11072 cursors.into_iter().collect()
11073 }
11074
11075 pub fn enable_breakpoint(
11076 &mut self,
11077 _: &crate::actions::EnableBreakpoint,
11078 window: &mut Window,
11079 cx: &mut Context<Self>,
11080 ) {
11081 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11082 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11083 continue;
11084 };
11085 self.edit_breakpoint_at_anchor(
11086 anchor,
11087 breakpoint,
11088 BreakpointEditAction::InvertState,
11089 cx,
11090 );
11091 }
11092 }
11093
11094 pub fn disable_breakpoint(
11095 &mut self,
11096 _: &crate::actions::DisableBreakpoint,
11097 window: &mut Window,
11098 cx: &mut Context<Self>,
11099 ) {
11100 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11101 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11102 continue;
11103 };
11104 self.edit_breakpoint_at_anchor(
11105 anchor,
11106 breakpoint,
11107 BreakpointEditAction::InvertState,
11108 cx,
11109 );
11110 }
11111 }
11112
11113 pub fn toggle_breakpoint(
11114 &mut self,
11115 _: &crate::actions::ToggleBreakpoint,
11116 window: &mut Window,
11117 cx: &mut Context<Self>,
11118 ) {
11119 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11120 if let Some(breakpoint) = breakpoint {
11121 self.edit_breakpoint_at_anchor(
11122 anchor,
11123 breakpoint,
11124 BreakpointEditAction::Toggle,
11125 cx,
11126 );
11127 } else {
11128 self.edit_breakpoint_at_anchor(
11129 anchor,
11130 Breakpoint::new_standard(),
11131 BreakpointEditAction::Toggle,
11132 cx,
11133 );
11134 }
11135 }
11136 }
11137
11138 pub fn edit_breakpoint_at_anchor(
11139 &mut self,
11140 breakpoint_position: Anchor,
11141 breakpoint: Breakpoint,
11142 edit_action: BreakpointEditAction,
11143 cx: &mut Context<Self>,
11144 ) {
11145 let Some(breakpoint_store) = &self.breakpoint_store else {
11146 return;
11147 };
11148
11149 let Some(buffer) = self
11150 .buffer
11151 .read(cx)
11152 .buffer_for_anchor(breakpoint_position, cx)
11153 else {
11154 return;
11155 };
11156
11157 breakpoint_store.update(cx, |breakpoint_store, cx| {
11158 breakpoint_store.toggle_breakpoint(
11159 buffer,
11160 BreakpointWithPosition {
11161 position: breakpoint_position.text_anchor,
11162 bp: breakpoint,
11163 },
11164 edit_action,
11165 cx,
11166 );
11167 });
11168
11169 cx.notify();
11170 }
11171
11172 #[cfg(any(test, feature = "test-support"))]
11173 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11174 self.breakpoint_store.clone()
11175 }
11176
11177 pub fn prepare_restore_change(
11178 &self,
11179 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11180 hunk: &MultiBufferDiffHunk,
11181 cx: &mut App,
11182 ) -> Option<()> {
11183 if hunk.is_created_file() {
11184 return None;
11185 }
11186 let buffer = self.buffer.read(cx);
11187 let diff = buffer.diff_for(hunk.buffer_id)?;
11188 let buffer = buffer.buffer(hunk.buffer_id)?;
11189 let buffer = buffer.read(cx);
11190 let original_text = diff
11191 .read(cx)
11192 .base_text()
11193 .as_rope()
11194 .slice(hunk.diff_base_byte_range.clone());
11195 let buffer_snapshot = buffer.snapshot();
11196 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11197 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11198 probe
11199 .0
11200 .start
11201 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11202 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11203 }) {
11204 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11205 Some(())
11206 } else {
11207 None
11208 }
11209 }
11210
11211 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11212 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11213 }
11214
11215 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11216 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11217 }
11218
11219 fn manipulate_lines<M>(
11220 &mut self,
11221 window: &mut Window,
11222 cx: &mut Context<Self>,
11223 mut manipulate: M,
11224 ) where
11225 M: FnMut(&str) -> LineManipulationResult,
11226 {
11227 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11228
11229 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11230 let buffer = self.buffer.read(cx).snapshot(cx);
11231
11232 let mut edits = Vec::new();
11233
11234 let selections = self.selections.all::<Point>(&display_map);
11235 let mut selections = selections.iter().peekable();
11236 let mut contiguous_row_selections = Vec::new();
11237 let mut new_selections = Vec::new();
11238 let mut added_lines = 0;
11239 let mut removed_lines = 0;
11240
11241 while let Some(selection) = selections.next() {
11242 let (start_row, end_row) = consume_contiguous_rows(
11243 &mut contiguous_row_selections,
11244 selection,
11245 &display_map,
11246 &mut selections,
11247 );
11248
11249 let start_point = Point::new(start_row.0, 0);
11250 let end_point = Point::new(
11251 end_row.previous_row().0,
11252 buffer.line_len(end_row.previous_row()),
11253 );
11254 let text = buffer
11255 .text_for_range(start_point..end_point)
11256 .collect::<String>();
11257
11258 let LineManipulationResult {
11259 new_text,
11260 line_count_before,
11261 line_count_after,
11262 } = manipulate(&text);
11263
11264 edits.push((start_point..end_point, new_text));
11265
11266 // Selections must change based on added and removed line count
11267 let start_row =
11268 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11269 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11270 new_selections.push(Selection {
11271 id: selection.id,
11272 start: start_row,
11273 end: end_row,
11274 goal: SelectionGoal::None,
11275 reversed: selection.reversed,
11276 });
11277
11278 if line_count_after > line_count_before {
11279 added_lines += line_count_after - line_count_before;
11280 } else if line_count_before > line_count_after {
11281 removed_lines += line_count_before - line_count_after;
11282 }
11283 }
11284
11285 self.transact(window, cx, |this, window, cx| {
11286 let buffer = this.buffer.update(cx, |buffer, cx| {
11287 buffer.edit(edits, None, cx);
11288 buffer.snapshot(cx)
11289 });
11290
11291 // Recalculate offsets on newly edited buffer
11292 let new_selections = new_selections
11293 .iter()
11294 .map(|s| {
11295 let start_point = Point::new(s.start.0, 0);
11296 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11297 Selection {
11298 id: s.id,
11299 start: buffer.point_to_offset(start_point),
11300 end: buffer.point_to_offset(end_point),
11301 goal: s.goal,
11302 reversed: s.reversed,
11303 }
11304 })
11305 .collect();
11306
11307 this.change_selections(Default::default(), window, cx, |s| {
11308 s.select(new_selections);
11309 });
11310
11311 this.request_autoscroll(Autoscroll::fit(), cx);
11312 });
11313 }
11314
11315 fn manipulate_immutable_lines<Fn>(
11316 &mut self,
11317 window: &mut Window,
11318 cx: &mut Context<Self>,
11319 mut callback: Fn,
11320 ) where
11321 Fn: FnMut(&mut Vec<&str>),
11322 {
11323 self.manipulate_lines(window, cx, |text| {
11324 let mut lines: Vec<&str> = text.split('\n').collect();
11325 let line_count_before = lines.len();
11326
11327 callback(&mut lines);
11328
11329 LineManipulationResult {
11330 new_text: lines.join("\n"),
11331 line_count_before,
11332 line_count_after: lines.len(),
11333 }
11334 });
11335 }
11336
11337 fn manipulate_mutable_lines<Fn>(
11338 &mut self,
11339 window: &mut Window,
11340 cx: &mut Context<Self>,
11341 mut callback: Fn,
11342 ) where
11343 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11344 {
11345 self.manipulate_lines(window, cx, |text| {
11346 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11347 let line_count_before = lines.len();
11348
11349 callback(&mut lines);
11350
11351 LineManipulationResult {
11352 new_text: lines.join("\n"),
11353 line_count_before,
11354 line_count_after: lines.len(),
11355 }
11356 });
11357 }
11358
11359 pub fn convert_indentation_to_spaces(
11360 &mut self,
11361 _: &ConvertIndentationToSpaces,
11362 window: &mut Window,
11363 cx: &mut Context<Self>,
11364 ) {
11365 let settings = self.buffer.read(cx).language_settings(cx);
11366 let tab_size = settings.tab_size.get() as usize;
11367
11368 self.manipulate_mutable_lines(window, cx, |lines| {
11369 // Allocates a reasonably sized scratch buffer once for the whole loop
11370 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11371 // Avoids recomputing spaces that could be inserted many times
11372 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11373 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11374 .collect();
11375
11376 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11377 let mut chars = line.as_ref().chars();
11378 let mut col = 0;
11379 let mut changed = false;
11380
11381 for ch in chars.by_ref() {
11382 match ch {
11383 ' ' => {
11384 reindented_line.push(' ');
11385 col += 1;
11386 }
11387 '\t' => {
11388 // \t are converted to spaces depending on the current column
11389 let spaces_len = tab_size - (col % tab_size);
11390 reindented_line.extend(&space_cache[spaces_len - 1]);
11391 col += spaces_len;
11392 changed = true;
11393 }
11394 _ => {
11395 // If we dont append before break, the character is consumed
11396 reindented_line.push(ch);
11397 break;
11398 }
11399 }
11400 }
11401
11402 if !changed {
11403 reindented_line.clear();
11404 continue;
11405 }
11406 // Append the rest of the line and replace old reference with new one
11407 reindented_line.extend(chars);
11408 *line = Cow::Owned(reindented_line.clone());
11409 reindented_line.clear();
11410 }
11411 });
11412 }
11413
11414 pub fn convert_indentation_to_tabs(
11415 &mut self,
11416 _: &ConvertIndentationToTabs,
11417 window: &mut Window,
11418 cx: &mut Context<Self>,
11419 ) {
11420 let settings = self.buffer.read(cx).language_settings(cx);
11421 let tab_size = settings.tab_size.get() as usize;
11422
11423 self.manipulate_mutable_lines(window, cx, |lines| {
11424 // Allocates a reasonably sized buffer once for the whole loop
11425 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11426 // Avoids recomputing spaces that could be inserted many times
11427 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11428 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11429 .collect();
11430
11431 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11432 let mut chars = line.chars();
11433 let mut spaces_count = 0;
11434 let mut first_non_indent_char = None;
11435 let mut changed = false;
11436
11437 for ch in chars.by_ref() {
11438 match ch {
11439 ' ' => {
11440 // Keep track of spaces. Append \t when we reach tab_size
11441 spaces_count += 1;
11442 changed = true;
11443 if spaces_count == tab_size {
11444 reindented_line.push('\t');
11445 spaces_count = 0;
11446 }
11447 }
11448 '\t' => {
11449 reindented_line.push('\t');
11450 spaces_count = 0;
11451 }
11452 _ => {
11453 // Dont append it yet, we might have remaining spaces
11454 first_non_indent_char = Some(ch);
11455 break;
11456 }
11457 }
11458 }
11459
11460 if !changed {
11461 reindented_line.clear();
11462 continue;
11463 }
11464 // Remaining spaces that didn't make a full tab stop
11465 if spaces_count > 0 {
11466 reindented_line.extend(&space_cache[spaces_count - 1]);
11467 }
11468 // If we consume an extra character that was not indentation, add it back
11469 if let Some(extra_char) = first_non_indent_char {
11470 reindented_line.push(extra_char);
11471 }
11472 // Append the rest of the line and replace old reference with new one
11473 reindented_line.extend(chars);
11474 *line = Cow::Owned(reindented_line.clone());
11475 reindented_line.clear();
11476 }
11477 });
11478 }
11479
11480 pub fn convert_to_upper_case(
11481 &mut self,
11482 _: &ConvertToUpperCase,
11483 window: &mut Window,
11484 cx: &mut Context<Self>,
11485 ) {
11486 self.manipulate_text(window, cx, |text| text.to_uppercase())
11487 }
11488
11489 pub fn convert_to_lower_case(
11490 &mut self,
11491 _: &ConvertToLowerCase,
11492 window: &mut Window,
11493 cx: &mut Context<Self>,
11494 ) {
11495 self.manipulate_text(window, cx, |text| text.to_lowercase())
11496 }
11497
11498 pub fn convert_to_title_case(
11499 &mut self,
11500 _: &ConvertToTitleCase,
11501 window: &mut Window,
11502 cx: &mut Context<Self>,
11503 ) {
11504 self.manipulate_text(window, cx, |text| {
11505 text.split('\n')
11506 .map(|line| line.to_case(Case::Title))
11507 .join("\n")
11508 })
11509 }
11510
11511 pub fn convert_to_snake_case(
11512 &mut self,
11513 _: &ConvertToSnakeCase,
11514 window: &mut Window,
11515 cx: &mut Context<Self>,
11516 ) {
11517 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11518 }
11519
11520 pub fn convert_to_kebab_case(
11521 &mut self,
11522 _: &ConvertToKebabCase,
11523 window: &mut Window,
11524 cx: &mut Context<Self>,
11525 ) {
11526 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11527 }
11528
11529 pub fn convert_to_upper_camel_case(
11530 &mut self,
11531 _: &ConvertToUpperCamelCase,
11532 window: &mut Window,
11533 cx: &mut Context<Self>,
11534 ) {
11535 self.manipulate_text(window, cx, |text| {
11536 text.split('\n')
11537 .map(|line| line.to_case(Case::UpperCamel))
11538 .join("\n")
11539 })
11540 }
11541
11542 pub fn convert_to_lower_camel_case(
11543 &mut self,
11544 _: &ConvertToLowerCamelCase,
11545 window: &mut Window,
11546 cx: &mut Context<Self>,
11547 ) {
11548 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11549 }
11550
11551 pub fn convert_to_opposite_case(
11552 &mut self,
11553 _: &ConvertToOppositeCase,
11554 window: &mut Window,
11555 cx: &mut Context<Self>,
11556 ) {
11557 self.manipulate_text(window, cx, |text| {
11558 text.chars()
11559 .fold(String::with_capacity(text.len()), |mut t, c| {
11560 if c.is_uppercase() {
11561 t.extend(c.to_lowercase());
11562 } else {
11563 t.extend(c.to_uppercase());
11564 }
11565 t
11566 })
11567 })
11568 }
11569
11570 pub fn convert_to_sentence_case(
11571 &mut self,
11572 _: &ConvertToSentenceCase,
11573 window: &mut Window,
11574 cx: &mut Context<Self>,
11575 ) {
11576 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11577 }
11578
11579 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11580 self.manipulate_text(window, cx, |text| {
11581 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11582 if has_upper_case_characters {
11583 text.to_lowercase()
11584 } else {
11585 text.to_uppercase()
11586 }
11587 })
11588 }
11589
11590 pub fn convert_to_rot13(
11591 &mut self,
11592 _: &ConvertToRot13,
11593 window: &mut Window,
11594 cx: &mut Context<Self>,
11595 ) {
11596 self.manipulate_text(window, cx, |text| {
11597 text.chars()
11598 .map(|c| match c {
11599 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11600 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11601 _ => c,
11602 })
11603 .collect()
11604 })
11605 }
11606
11607 pub fn convert_to_rot47(
11608 &mut self,
11609 _: &ConvertToRot47,
11610 window: &mut Window,
11611 cx: &mut Context<Self>,
11612 ) {
11613 self.manipulate_text(window, cx, |text| {
11614 text.chars()
11615 .map(|c| {
11616 let code_point = c as u32;
11617 if code_point >= 33 && code_point <= 126 {
11618 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11619 }
11620 c
11621 })
11622 .collect()
11623 })
11624 }
11625
11626 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11627 where
11628 Fn: FnMut(&str) -> String,
11629 {
11630 let buffer = self.buffer.read(cx).snapshot(cx);
11631
11632 let mut new_selections = Vec::new();
11633 let mut edits = Vec::new();
11634 let mut selection_adjustment = 0i32;
11635
11636 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11637 let selection_is_empty = selection.is_empty();
11638
11639 let (start, end) = if selection_is_empty {
11640 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11641 (word_range.start, word_range.end)
11642 } else {
11643 (
11644 buffer.point_to_offset(selection.start),
11645 buffer.point_to_offset(selection.end),
11646 )
11647 };
11648
11649 let text = buffer.text_for_range(start..end).collect::<String>();
11650 let old_length = text.len() as i32;
11651 let text = callback(&text);
11652
11653 new_selections.push(Selection {
11654 start: (start as i32 - selection_adjustment) as usize,
11655 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11656 goal: SelectionGoal::None,
11657 id: selection.id,
11658 reversed: selection.reversed,
11659 });
11660
11661 selection_adjustment += old_length - text.len() as i32;
11662
11663 edits.push((start..end, text));
11664 }
11665
11666 self.transact(window, cx, |this, window, cx| {
11667 this.buffer.update(cx, |buffer, cx| {
11668 buffer.edit(edits, None, cx);
11669 });
11670
11671 this.change_selections(Default::default(), window, cx, |s| {
11672 s.select(new_selections);
11673 });
11674
11675 this.request_autoscroll(Autoscroll::fit(), cx);
11676 });
11677 }
11678
11679 pub fn move_selection_on_drop(
11680 &mut self,
11681 selection: &Selection<Anchor>,
11682 target: DisplayPoint,
11683 is_cut: bool,
11684 window: &mut Window,
11685 cx: &mut Context<Self>,
11686 ) {
11687 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11688 let buffer = display_map.buffer_snapshot();
11689 let mut edits = Vec::new();
11690 let insert_point = display_map
11691 .clip_point(target, Bias::Left)
11692 .to_point(&display_map);
11693 let text = buffer
11694 .text_for_range(selection.start..selection.end)
11695 .collect::<String>();
11696 if is_cut {
11697 edits.push(((selection.start..selection.end), String::new()));
11698 }
11699 let insert_anchor = buffer.anchor_before(insert_point);
11700 edits.push(((insert_anchor..insert_anchor), text));
11701 let last_edit_start = insert_anchor.bias_left(buffer);
11702 let last_edit_end = insert_anchor.bias_right(buffer);
11703 self.transact(window, cx, |this, window, cx| {
11704 this.buffer.update(cx, |buffer, cx| {
11705 buffer.edit(edits, None, cx);
11706 });
11707 this.change_selections(Default::default(), window, cx, |s| {
11708 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11709 });
11710 });
11711 }
11712
11713 pub fn clear_selection_drag_state(&mut self) {
11714 self.selection_drag_state = SelectionDragState::None;
11715 }
11716
11717 pub fn duplicate(
11718 &mut self,
11719 upwards: bool,
11720 whole_lines: bool,
11721 window: &mut Window,
11722 cx: &mut Context<Self>,
11723 ) {
11724 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11725
11726 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11727 let buffer = display_map.buffer_snapshot();
11728 let selections = self.selections.all::<Point>(&display_map);
11729
11730 let mut edits = Vec::new();
11731 let mut selections_iter = selections.iter().peekable();
11732 while let Some(selection) = selections_iter.next() {
11733 let mut rows = selection.spanned_rows(false, &display_map);
11734 // duplicate line-wise
11735 if whole_lines || selection.start == selection.end {
11736 // Avoid duplicating the same lines twice.
11737 while let Some(next_selection) = selections_iter.peek() {
11738 let next_rows = next_selection.spanned_rows(false, &display_map);
11739 if next_rows.start < rows.end {
11740 rows.end = next_rows.end;
11741 selections_iter.next().unwrap();
11742 } else {
11743 break;
11744 }
11745 }
11746
11747 // Copy the text from the selected row region and splice it either at the start
11748 // or end of the region.
11749 let start = Point::new(rows.start.0, 0);
11750 let end = Point::new(
11751 rows.end.previous_row().0,
11752 buffer.line_len(rows.end.previous_row()),
11753 );
11754
11755 let mut text = buffer.text_for_range(start..end).collect::<String>();
11756
11757 let insert_location = if upwards {
11758 // When duplicating upward, we need to insert before the current line.
11759 // If we're on the last line and it doesn't end with a newline,
11760 // we need to add a newline before the duplicated content.
11761 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11762 && buffer.max_point().column > 0
11763 && !text.ends_with('\n');
11764
11765 if needs_leading_newline {
11766 text.insert(0, '\n');
11767 end
11768 } else {
11769 text.push('\n');
11770 Point::new(rows.end.0, 0)
11771 }
11772 } else {
11773 text.push('\n');
11774 start
11775 };
11776 edits.push((insert_location..insert_location, text));
11777 } else {
11778 // duplicate character-wise
11779 let start = selection.start;
11780 let end = selection.end;
11781 let text = buffer.text_for_range(start..end).collect::<String>();
11782 edits.push((selection.end..selection.end, text));
11783 }
11784 }
11785
11786 self.transact(window, cx, |this, _, cx| {
11787 this.buffer.update(cx, |buffer, cx| {
11788 buffer.edit(edits, None, cx);
11789 });
11790
11791 this.request_autoscroll(Autoscroll::fit(), cx);
11792 });
11793 }
11794
11795 pub fn duplicate_line_up(
11796 &mut self,
11797 _: &DuplicateLineUp,
11798 window: &mut Window,
11799 cx: &mut Context<Self>,
11800 ) {
11801 self.duplicate(true, true, window, cx);
11802 }
11803
11804 pub fn duplicate_line_down(
11805 &mut self,
11806 _: &DuplicateLineDown,
11807 window: &mut Window,
11808 cx: &mut Context<Self>,
11809 ) {
11810 self.duplicate(false, true, window, cx);
11811 }
11812
11813 pub fn duplicate_selection(
11814 &mut self,
11815 _: &DuplicateSelection,
11816 window: &mut Window,
11817 cx: &mut Context<Self>,
11818 ) {
11819 self.duplicate(false, false, window, cx);
11820 }
11821
11822 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11823 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11824 if self.mode.is_single_line() {
11825 cx.propagate();
11826 return;
11827 }
11828
11829 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11830 let buffer = self.buffer.read(cx).snapshot(cx);
11831
11832 let mut edits = Vec::new();
11833 let mut unfold_ranges = Vec::new();
11834 let mut refold_creases = Vec::new();
11835
11836 let selections = self.selections.all::<Point>(&display_map);
11837 let mut selections = selections.iter().peekable();
11838 let mut contiguous_row_selections = Vec::new();
11839 let mut new_selections = Vec::new();
11840
11841 while let Some(selection) = selections.next() {
11842 // Find all the selections that span a contiguous row range
11843 let (start_row, end_row) = consume_contiguous_rows(
11844 &mut contiguous_row_selections,
11845 selection,
11846 &display_map,
11847 &mut selections,
11848 );
11849
11850 // Move the text spanned by the row range to be before the line preceding the row range
11851 if start_row.0 > 0 {
11852 let range_to_move = Point::new(
11853 start_row.previous_row().0,
11854 buffer.line_len(start_row.previous_row()),
11855 )
11856 ..Point::new(
11857 end_row.previous_row().0,
11858 buffer.line_len(end_row.previous_row()),
11859 );
11860 let insertion_point = display_map
11861 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11862 .0;
11863
11864 // Don't move lines across excerpts
11865 if buffer
11866 .excerpt_containing(insertion_point..range_to_move.end)
11867 .is_some()
11868 {
11869 let text = buffer
11870 .text_for_range(range_to_move.clone())
11871 .flat_map(|s| s.chars())
11872 .skip(1)
11873 .chain(['\n'])
11874 .collect::<String>();
11875
11876 edits.push((
11877 buffer.anchor_after(range_to_move.start)
11878 ..buffer.anchor_before(range_to_move.end),
11879 String::new(),
11880 ));
11881 let insertion_anchor = buffer.anchor_after(insertion_point);
11882 edits.push((insertion_anchor..insertion_anchor, text));
11883
11884 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11885
11886 // Move selections up
11887 new_selections.extend(contiguous_row_selections.drain(..).map(
11888 |mut selection| {
11889 selection.start.row -= row_delta;
11890 selection.end.row -= row_delta;
11891 selection
11892 },
11893 ));
11894
11895 // Move folds up
11896 unfold_ranges.push(range_to_move.clone());
11897 for fold in display_map.folds_in_range(
11898 buffer.anchor_before(range_to_move.start)
11899 ..buffer.anchor_after(range_to_move.end),
11900 ) {
11901 let mut start = fold.range.start.to_point(&buffer);
11902 let mut end = fold.range.end.to_point(&buffer);
11903 start.row -= row_delta;
11904 end.row -= row_delta;
11905 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11906 }
11907 }
11908 }
11909
11910 // If we didn't move line(s), preserve the existing selections
11911 new_selections.append(&mut contiguous_row_selections);
11912 }
11913
11914 self.transact(window, cx, |this, window, cx| {
11915 this.unfold_ranges(&unfold_ranges, true, true, cx);
11916 this.buffer.update(cx, |buffer, cx| {
11917 for (range, text) in edits {
11918 buffer.edit([(range, text)], None, cx);
11919 }
11920 });
11921 this.fold_creases(refold_creases, true, window, cx);
11922 this.change_selections(Default::default(), window, cx, |s| {
11923 s.select(new_selections);
11924 })
11925 });
11926 }
11927
11928 pub fn move_line_down(
11929 &mut self,
11930 _: &MoveLineDown,
11931 window: &mut Window,
11932 cx: &mut Context<Self>,
11933 ) {
11934 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11935 if self.mode.is_single_line() {
11936 cx.propagate();
11937 return;
11938 }
11939
11940 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11941 let buffer = self.buffer.read(cx).snapshot(cx);
11942
11943 let mut edits = Vec::new();
11944 let mut unfold_ranges = Vec::new();
11945 let mut refold_creases = Vec::new();
11946
11947 let selections = self.selections.all::<Point>(&display_map);
11948 let mut selections = selections.iter().peekable();
11949 let mut contiguous_row_selections = Vec::new();
11950 let mut new_selections = Vec::new();
11951
11952 while let Some(selection) = selections.next() {
11953 // Find all the selections that span a contiguous row range
11954 let (start_row, end_row) = consume_contiguous_rows(
11955 &mut contiguous_row_selections,
11956 selection,
11957 &display_map,
11958 &mut selections,
11959 );
11960
11961 // Move the text spanned by the row range to be after the last line of the row range
11962 if end_row.0 <= buffer.max_point().row {
11963 let range_to_move =
11964 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11965 let insertion_point = display_map
11966 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11967 .0;
11968
11969 // Don't move lines across excerpt boundaries
11970 if buffer
11971 .excerpt_containing(range_to_move.start..insertion_point)
11972 .is_some()
11973 {
11974 let mut text = String::from("\n");
11975 text.extend(buffer.text_for_range(range_to_move.clone()));
11976 text.pop(); // Drop trailing newline
11977 edits.push((
11978 buffer.anchor_after(range_to_move.start)
11979 ..buffer.anchor_before(range_to_move.end),
11980 String::new(),
11981 ));
11982 let insertion_anchor = buffer.anchor_after(insertion_point);
11983 edits.push((insertion_anchor..insertion_anchor, text));
11984
11985 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11986
11987 // Move selections down
11988 new_selections.extend(contiguous_row_selections.drain(..).map(
11989 |mut selection| {
11990 selection.start.row += row_delta;
11991 selection.end.row += row_delta;
11992 selection
11993 },
11994 ));
11995
11996 // Move folds down
11997 unfold_ranges.push(range_to_move.clone());
11998 for fold in display_map.folds_in_range(
11999 buffer.anchor_before(range_to_move.start)
12000 ..buffer.anchor_after(range_to_move.end),
12001 ) {
12002 let mut start = fold.range.start.to_point(&buffer);
12003 let mut end = fold.range.end.to_point(&buffer);
12004 start.row += row_delta;
12005 end.row += row_delta;
12006 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12007 }
12008 }
12009 }
12010
12011 // If we didn't move line(s), preserve the existing selections
12012 new_selections.append(&mut contiguous_row_selections);
12013 }
12014
12015 self.transact(window, cx, |this, window, cx| {
12016 this.unfold_ranges(&unfold_ranges, true, true, cx);
12017 this.buffer.update(cx, |buffer, cx| {
12018 for (range, text) in edits {
12019 buffer.edit([(range, text)], None, cx);
12020 }
12021 });
12022 this.fold_creases(refold_creases, true, window, cx);
12023 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12024 });
12025 }
12026
12027 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12028 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12029 let text_layout_details = &self.text_layout_details(window);
12030 self.transact(window, cx, |this, window, cx| {
12031 let edits = this.change_selections(Default::default(), window, cx, |s| {
12032 let mut edits: Vec<(Range<usize>, String)> = Default::default();
12033 s.move_with(|display_map, selection| {
12034 if !selection.is_empty() {
12035 return;
12036 }
12037
12038 let mut head = selection.head();
12039 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12040 if head.column() == display_map.line_len(head.row()) {
12041 transpose_offset = display_map
12042 .buffer_snapshot()
12043 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12044 }
12045
12046 if transpose_offset == 0 {
12047 return;
12048 }
12049
12050 *head.column_mut() += 1;
12051 head = display_map.clip_point(head, Bias::Right);
12052 let goal = SelectionGoal::HorizontalPosition(
12053 display_map
12054 .x_for_display_point(head, text_layout_details)
12055 .into(),
12056 );
12057 selection.collapse_to(head, goal);
12058
12059 let transpose_start = display_map
12060 .buffer_snapshot()
12061 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12062 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12063 let transpose_end = display_map
12064 .buffer_snapshot()
12065 .clip_offset(transpose_offset + 1, Bias::Right);
12066 if let Some(ch) = display_map
12067 .buffer_snapshot()
12068 .chars_at(transpose_start)
12069 .next()
12070 {
12071 edits.push((transpose_start..transpose_offset, String::new()));
12072 edits.push((transpose_end..transpose_end, ch.to_string()));
12073 }
12074 }
12075 });
12076 edits
12077 });
12078 this.buffer
12079 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12080 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12081 this.change_selections(Default::default(), window, cx, |s| {
12082 s.select(selections);
12083 });
12084 });
12085 }
12086
12087 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12088 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12089 if self.mode.is_single_line() {
12090 cx.propagate();
12091 return;
12092 }
12093
12094 self.rewrap_impl(RewrapOptions::default(), cx)
12095 }
12096
12097 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12098 let buffer = self.buffer.read(cx).snapshot(cx);
12099 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12100
12101 #[derive(Clone, Debug, PartialEq)]
12102 enum CommentFormat {
12103 /// single line comment, with prefix for line
12104 Line(String),
12105 /// single line within a block comment, with prefix for line
12106 BlockLine(String),
12107 /// a single line of a block comment that includes the initial delimiter
12108 BlockCommentWithStart(BlockCommentConfig),
12109 /// a single line of a block comment that includes the ending delimiter
12110 BlockCommentWithEnd(BlockCommentConfig),
12111 }
12112
12113 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12114 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12115 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12116 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12117 .peekable();
12118
12119 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12120 row
12121 } else {
12122 return Vec::new();
12123 };
12124
12125 let language_settings = buffer.language_settings_at(selection.head(), cx);
12126 let language_scope = buffer.language_scope_at(selection.head());
12127
12128 let indent_and_prefix_for_row =
12129 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12130 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12131 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12132 &language_scope
12133 {
12134 let indent_end = Point::new(row, indent.len);
12135 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12136 let line_text_after_indent = buffer
12137 .text_for_range(indent_end..line_end)
12138 .collect::<String>();
12139
12140 let is_within_comment_override = buffer
12141 .language_scope_at(indent_end)
12142 .is_some_and(|scope| scope.override_name() == Some("comment"));
12143 let comment_delimiters = if is_within_comment_override {
12144 // we are within a comment syntax node, but we don't
12145 // yet know what kind of comment: block, doc or line
12146 match (
12147 language_scope.documentation_comment(),
12148 language_scope.block_comment(),
12149 ) {
12150 (Some(config), _) | (_, Some(config))
12151 if buffer.contains_str_at(indent_end, &config.start) =>
12152 {
12153 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12154 }
12155 (Some(config), _) | (_, Some(config))
12156 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12157 {
12158 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12159 }
12160 (Some(config), _) | (_, Some(config))
12161 if buffer.contains_str_at(indent_end, &config.prefix) =>
12162 {
12163 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12164 }
12165 (_, _) => language_scope
12166 .line_comment_prefixes()
12167 .iter()
12168 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12169 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12170 }
12171 } else {
12172 // we not in an overridden comment node, but we may
12173 // be within a non-overridden line comment node
12174 language_scope
12175 .line_comment_prefixes()
12176 .iter()
12177 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12178 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12179 };
12180
12181 let rewrap_prefix = language_scope
12182 .rewrap_prefixes()
12183 .iter()
12184 .find_map(|prefix_regex| {
12185 prefix_regex.find(&line_text_after_indent).map(|mat| {
12186 if mat.start() == 0 {
12187 Some(mat.as_str().to_string())
12188 } else {
12189 None
12190 }
12191 })
12192 })
12193 .flatten();
12194 (comment_delimiters, rewrap_prefix)
12195 } else {
12196 (None, None)
12197 };
12198 (indent, comment_prefix, rewrap_prefix)
12199 };
12200
12201 let mut ranges = Vec::new();
12202 let from_empty_selection = selection.is_empty();
12203
12204 let mut current_range_start = first_row;
12205 let mut prev_row = first_row;
12206 let (
12207 mut current_range_indent,
12208 mut current_range_comment_delimiters,
12209 mut current_range_rewrap_prefix,
12210 ) = indent_and_prefix_for_row(first_row);
12211
12212 for row in non_blank_rows_iter.skip(1) {
12213 let has_paragraph_break = row > prev_row + 1;
12214
12215 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12216 indent_and_prefix_for_row(row);
12217
12218 let has_indent_change = row_indent != current_range_indent;
12219 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12220
12221 let has_boundary_change = has_comment_change
12222 || row_rewrap_prefix.is_some()
12223 || (has_indent_change && current_range_comment_delimiters.is_some());
12224
12225 if has_paragraph_break || has_boundary_change {
12226 ranges.push((
12227 language_settings.clone(),
12228 Point::new(current_range_start, 0)
12229 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12230 current_range_indent,
12231 current_range_comment_delimiters.clone(),
12232 current_range_rewrap_prefix.clone(),
12233 from_empty_selection,
12234 ));
12235 current_range_start = row;
12236 current_range_indent = row_indent;
12237 current_range_comment_delimiters = row_comment_delimiters;
12238 current_range_rewrap_prefix = row_rewrap_prefix;
12239 }
12240 prev_row = row;
12241 }
12242
12243 ranges.push((
12244 language_settings.clone(),
12245 Point::new(current_range_start, 0)
12246 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12247 current_range_indent,
12248 current_range_comment_delimiters,
12249 current_range_rewrap_prefix,
12250 from_empty_selection,
12251 ));
12252
12253 ranges
12254 });
12255
12256 let mut edits = Vec::new();
12257 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12258
12259 for (
12260 language_settings,
12261 wrap_range,
12262 mut indent_size,
12263 comment_prefix,
12264 rewrap_prefix,
12265 from_empty_selection,
12266 ) in wrap_ranges
12267 {
12268 let mut start_row = wrap_range.start.row;
12269 let mut end_row = wrap_range.end.row;
12270
12271 // Skip selections that overlap with a range that has already been rewrapped.
12272 let selection_range = start_row..end_row;
12273 if rewrapped_row_ranges
12274 .iter()
12275 .any(|range| range.overlaps(&selection_range))
12276 {
12277 continue;
12278 }
12279
12280 let tab_size = language_settings.tab_size;
12281
12282 let (line_prefix, inside_comment) = match &comment_prefix {
12283 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12284 (Some(prefix.as_str()), true)
12285 }
12286 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12287 (Some(prefix.as_ref()), true)
12288 }
12289 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12290 start: _,
12291 end: _,
12292 prefix,
12293 tab_size,
12294 })) => {
12295 indent_size.len += tab_size;
12296 (Some(prefix.as_ref()), true)
12297 }
12298 None => (None, false),
12299 };
12300 let indent_prefix = indent_size.chars().collect::<String>();
12301 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12302
12303 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12304 RewrapBehavior::InComments => inside_comment,
12305 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12306 RewrapBehavior::Anywhere => true,
12307 };
12308
12309 let should_rewrap = options.override_language_settings
12310 || allow_rewrap_based_on_language
12311 || self.hard_wrap.is_some();
12312 if !should_rewrap {
12313 continue;
12314 }
12315
12316 if from_empty_selection {
12317 'expand_upwards: while start_row > 0 {
12318 let prev_row = start_row - 1;
12319 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12320 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12321 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12322 {
12323 start_row = prev_row;
12324 } else {
12325 break 'expand_upwards;
12326 }
12327 }
12328
12329 'expand_downwards: while end_row < buffer.max_point().row {
12330 let next_row = end_row + 1;
12331 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12332 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12333 && !buffer.is_line_blank(MultiBufferRow(next_row))
12334 {
12335 end_row = next_row;
12336 } else {
12337 break 'expand_downwards;
12338 }
12339 }
12340 }
12341
12342 let start = Point::new(start_row, 0);
12343 let start_offset = ToOffset::to_offset(&start, &buffer);
12344 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12345 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12346 let mut first_line_delimiter = None;
12347 let mut last_line_delimiter = None;
12348 let Some(lines_without_prefixes) = selection_text
12349 .lines()
12350 .enumerate()
12351 .map(|(ix, line)| {
12352 let line_trimmed = line.trim_start();
12353 if rewrap_prefix.is_some() && ix > 0 {
12354 Ok(line_trimmed)
12355 } else if let Some(
12356 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12357 start,
12358 prefix,
12359 end,
12360 tab_size,
12361 })
12362 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12363 start,
12364 prefix,
12365 end,
12366 tab_size,
12367 }),
12368 ) = &comment_prefix
12369 {
12370 let line_trimmed = line_trimmed
12371 .strip_prefix(start.as_ref())
12372 .map(|s| {
12373 let mut indent_size = indent_size;
12374 indent_size.len -= tab_size;
12375 let indent_prefix: String = indent_size.chars().collect();
12376 first_line_delimiter = Some((indent_prefix, start));
12377 s.trim_start()
12378 })
12379 .unwrap_or(line_trimmed);
12380 let line_trimmed = line_trimmed
12381 .strip_suffix(end.as_ref())
12382 .map(|s| {
12383 last_line_delimiter = Some(end);
12384 s.trim_end()
12385 })
12386 .unwrap_or(line_trimmed);
12387 let line_trimmed = line_trimmed
12388 .strip_prefix(prefix.as_ref())
12389 .unwrap_or(line_trimmed);
12390 Ok(line_trimmed)
12391 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12392 line_trimmed.strip_prefix(prefix).with_context(|| {
12393 format!("line did not start with prefix {prefix:?}: {line:?}")
12394 })
12395 } else {
12396 line_trimmed
12397 .strip_prefix(&line_prefix.trim_start())
12398 .with_context(|| {
12399 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12400 })
12401 }
12402 })
12403 .collect::<Result<Vec<_>, _>>()
12404 .log_err()
12405 else {
12406 continue;
12407 };
12408
12409 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12410 buffer
12411 .language_settings_at(Point::new(start_row, 0), cx)
12412 .preferred_line_length as usize
12413 });
12414
12415 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12416 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12417 } else {
12418 line_prefix.clone()
12419 };
12420
12421 let wrapped_text = {
12422 let mut wrapped_text = wrap_with_prefix(
12423 line_prefix,
12424 subsequent_lines_prefix,
12425 lines_without_prefixes.join("\n"),
12426 wrap_column,
12427 tab_size,
12428 options.preserve_existing_whitespace,
12429 );
12430
12431 if let Some((indent, delimiter)) = first_line_delimiter {
12432 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12433 }
12434 if let Some(last_line) = last_line_delimiter {
12435 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12436 }
12437
12438 wrapped_text
12439 };
12440
12441 // TODO: should always use char-based diff while still supporting cursor behavior that
12442 // matches vim.
12443 let mut diff_options = DiffOptions::default();
12444 if options.override_language_settings {
12445 diff_options.max_word_diff_len = 0;
12446 diff_options.max_word_diff_line_count = 0;
12447 } else {
12448 diff_options.max_word_diff_len = usize::MAX;
12449 diff_options.max_word_diff_line_count = usize::MAX;
12450 }
12451
12452 for (old_range, new_text) in
12453 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12454 {
12455 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12456 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12457 edits.push((edit_start..edit_end, new_text));
12458 }
12459
12460 rewrapped_row_ranges.push(start_row..=end_row);
12461 }
12462
12463 self.buffer
12464 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12465 }
12466
12467 pub fn cut_common(
12468 &mut self,
12469 cut_no_selection_line: bool,
12470 window: &mut Window,
12471 cx: &mut Context<Self>,
12472 ) -> ClipboardItem {
12473 let mut text = String::new();
12474 let buffer = self.buffer.read(cx).snapshot(cx);
12475 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12476 let mut clipboard_selections = Vec::with_capacity(selections.len());
12477 {
12478 let max_point = buffer.max_point();
12479 let mut is_first = true;
12480 for selection in &mut selections {
12481 let is_entire_line =
12482 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12483 if is_entire_line {
12484 selection.start = Point::new(selection.start.row, 0);
12485 if !selection.is_empty() && selection.end.column == 0 {
12486 selection.end = cmp::min(max_point, selection.end);
12487 } else {
12488 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12489 }
12490 selection.goal = SelectionGoal::None;
12491 }
12492 if is_first {
12493 is_first = false;
12494 } else {
12495 text += "\n";
12496 }
12497 let mut len = 0;
12498 for chunk in buffer.text_for_range(selection.start..selection.end) {
12499 text.push_str(chunk);
12500 len += chunk.len();
12501 }
12502 clipboard_selections.push(ClipboardSelection {
12503 len,
12504 is_entire_line,
12505 first_line_indent: buffer
12506 .indent_size_for_line(MultiBufferRow(selection.start.row))
12507 .len,
12508 });
12509 }
12510 }
12511
12512 self.transact(window, cx, |this, window, cx| {
12513 this.change_selections(Default::default(), window, cx, |s| {
12514 s.select(selections);
12515 });
12516 this.insert("", window, cx);
12517 });
12518 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12519 }
12520
12521 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12522 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12523 let item = self.cut_common(true, window, cx);
12524 cx.write_to_clipboard(item);
12525 }
12526
12527 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12528 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12529 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12530 s.move_with(|snapshot, sel| {
12531 if sel.is_empty() {
12532 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12533 }
12534 if sel.is_empty() {
12535 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12536 }
12537 });
12538 });
12539 let item = self.cut_common(false, window, cx);
12540 cx.set_global(KillRing(item))
12541 }
12542
12543 pub fn kill_ring_yank(
12544 &mut self,
12545 _: &KillRingYank,
12546 window: &mut Window,
12547 cx: &mut Context<Self>,
12548 ) {
12549 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12550 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12551 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12552 (kill_ring.text().to_string(), kill_ring.metadata_json())
12553 } else {
12554 return;
12555 }
12556 } else {
12557 return;
12558 };
12559 self.do_paste(&text, metadata, false, window, cx);
12560 }
12561
12562 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12563 self.do_copy(true, cx);
12564 }
12565
12566 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12567 self.do_copy(false, cx);
12568 }
12569
12570 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12571 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12572 let buffer = self.buffer.read(cx).read(cx);
12573 let mut text = String::new();
12574
12575 let mut clipboard_selections = Vec::with_capacity(selections.len());
12576 {
12577 let max_point = buffer.max_point();
12578 let mut is_first = true;
12579 for selection in &selections {
12580 let mut start = selection.start;
12581 let mut end = selection.end;
12582 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12583 let mut add_trailing_newline = false;
12584 if is_entire_line {
12585 start = Point::new(start.row, 0);
12586 let next_line_start = Point::new(end.row + 1, 0);
12587 if next_line_start <= max_point {
12588 end = next_line_start;
12589 } else {
12590 // We're on the last line without a trailing newline.
12591 // Copy to the end of the line and add a newline afterwards.
12592 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12593 add_trailing_newline = true;
12594 }
12595 }
12596
12597 let mut trimmed_selections = Vec::new();
12598 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12599 let row = MultiBufferRow(start.row);
12600 let first_indent = buffer.indent_size_for_line(row);
12601 if first_indent.len == 0 || start.column > first_indent.len {
12602 trimmed_selections.push(start..end);
12603 } else {
12604 trimmed_selections.push(
12605 Point::new(row.0, first_indent.len)
12606 ..Point::new(row.0, buffer.line_len(row)),
12607 );
12608 for row in start.row + 1..=end.row {
12609 let mut line_len = buffer.line_len(MultiBufferRow(row));
12610 if row == end.row {
12611 line_len = end.column;
12612 }
12613 if line_len == 0 {
12614 trimmed_selections
12615 .push(Point::new(row, 0)..Point::new(row, line_len));
12616 continue;
12617 }
12618 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12619 if row_indent_size.len >= first_indent.len {
12620 trimmed_selections.push(
12621 Point::new(row, first_indent.len)..Point::new(row, line_len),
12622 );
12623 } else {
12624 trimmed_selections.clear();
12625 trimmed_selections.push(start..end);
12626 break;
12627 }
12628 }
12629 }
12630 } else {
12631 trimmed_selections.push(start..end);
12632 }
12633
12634 for trimmed_range in trimmed_selections {
12635 if is_first {
12636 is_first = false;
12637 } else {
12638 text += "\n";
12639 }
12640 let mut len = 0;
12641 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12642 text.push_str(chunk);
12643 len += chunk.len();
12644 }
12645 if add_trailing_newline {
12646 text.push('\n');
12647 len += 1;
12648 }
12649 clipboard_selections.push(ClipboardSelection {
12650 len,
12651 is_entire_line,
12652 first_line_indent: buffer
12653 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12654 .len,
12655 });
12656 }
12657 }
12658 }
12659
12660 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12661 text,
12662 clipboard_selections,
12663 ));
12664 }
12665
12666 pub fn do_paste(
12667 &mut self,
12668 text: &String,
12669 clipboard_selections: Option<Vec<ClipboardSelection>>,
12670 handle_entire_lines: bool,
12671 window: &mut Window,
12672 cx: &mut Context<Self>,
12673 ) {
12674 if self.read_only(cx) {
12675 return;
12676 }
12677
12678 let clipboard_text = Cow::Borrowed(text.as_str());
12679
12680 self.transact(window, cx, |this, window, cx| {
12681 let had_active_edit_prediction = this.has_active_edit_prediction();
12682 let display_map = this.display_snapshot(cx);
12683 let old_selections = this.selections.all::<usize>(&display_map);
12684 let cursor_offset = this.selections.last::<usize>(&display_map).head();
12685
12686 if let Some(mut clipboard_selections) = clipboard_selections {
12687 let all_selections_were_entire_line =
12688 clipboard_selections.iter().all(|s| s.is_entire_line);
12689 let first_selection_indent_column =
12690 clipboard_selections.first().map(|s| s.first_line_indent);
12691 if clipboard_selections.len() != old_selections.len() {
12692 clipboard_selections.drain(..);
12693 }
12694 let mut auto_indent_on_paste = true;
12695
12696 this.buffer.update(cx, |buffer, cx| {
12697 let snapshot = buffer.read(cx);
12698 auto_indent_on_paste = snapshot
12699 .language_settings_at(cursor_offset, cx)
12700 .auto_indent_on_paste;
12701
12702 let mut start_offset = 0;
12703 let mut edits = Vec::new();
12704 let mut original_indent_columns = Vec::new();
12705 for (ix, selection) in old_selections.iter().enumerate() {
12706 let to_insert;
12707 let entire_line;
12708 let original_indent_column;
12709 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12710 let end_offset = start_offset + clipboard_selection.len;
12711 to_insert = &clipboard_text[start_offset..end_offset];
12712 entire_line = clipboard_selection.is_entire_line;
12713 start_offset = end_offset + 1;
12714 original_indent_column = Some(clipboard_selection.first_line_indent);
12715 } else {
12716 to_insert = &*clipboard_text;
12717 entire_line = all_selections_were_entire_line;
12718 original_indent_column = first_selection_indent_column
12719 }
12720
12721 let (range, to_insert) =
12722 if selection.is_empty() && handle_entire_lines && entire_line {
12723 // If the corresponding selection was empty when this slice of the
12724 // clipboard text was written, then the entire line containing the
12725 // selection was copied. If this selection is also currently empty,
12726 // then paste the line before the current line of the buffer.
12727 let column = selection.start.to_point(&snapshot).column as usize;
12728 let line_start = selection.start - column;
12729 (line_start..line_start, Cow::Borrowed(to_insert))
12730 } else {
12731 let language = snapshot.language_at(selection.head());
12732 let range = selection.range();
12733 if let Some(language) = language
12734 && language.name() == "Markdown".into()
12735 {
12736 edit_for_markdown_paste(
12737 &snapshot,
12738 range,
12739 to_insert,
12740 url::Url::parse(to_insert).ok(),
12741 )
12742 } else {
12743 (range, Cow::Borrowed(to_insert))
12744 }
12745 };
12746
12747 edits.push((range, to_insert));
12748 original_indent_columns.push(original_indent_column);
12749 }
12750 drop(snapshot);
12751
12752 buffer.edit(
12753 edits,
12754 if auto_indent_on_paste {
12755 Some(AutoindentMode::Block {
12756 original_indent_columns,
12757 })
12758 } else {
12759 None
12760 },
12761 cx,
12762 );
12763 });
12764
12765 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12766 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12767 } else {
12768 let url = url::Url::parse(&clipboard_text).ok();
12769
12770 let auto_indent_mode = if !clipboard_text.is_empty() {
12771 Some(AutoindentMode::Block {
12772 original_indent_columns: Vec::new(),
12773 })
12774 } else {
12775 None
12776 };
12777
12778 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12779 let snapshot = buffer.snapshot(cx);
12780
12781 let anchors = old_selections
12782 .iter()
12783 .map(|s| {
12784 let anchor = snapshot.anchor_after(s.head());
12785 s.map(|_| anchor)
12786 })
12787 .collect::<Vec<_>>();
12788
12789 let mut edits = Vec::new();
12790
12791 for selection in old_selections.iter() {
12792 let language = snapshot.language_at(selection.head());
12793 let range = selection.range();
12794
12795 let (edit_range, edit_text) = if let Some(language) = language
12796 && language.name() == "Markdown".into()
12797 {
12798 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12799 } else {
12800 (range, clipboard_text.clone())
12801 };
12802
12803 edits.push((edit_range, edit_text));
12804 }
12805
12806 drop(snapshot);
12807 buffer.edit(edits, auto_indent_mode, cx);
12808
12809 anchors
12810 });
12811
12812 this.change_selections(Default::default(), window, cx, |s| {
12813 s.select_anchors(selection_anchors);
12814 });
12815 }
12816
12817 let trigger_in_words =
12818 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12819
12820 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12821 });
12822 }
12823
12824 pub fn diff_clipboard_with_selection(
12825 &mut self,
12826 _: &DiffClipboardWithSelection,
12827 window: &mut Window,
12828 cx: &mut Context<Self>,
12829 ) {
12830 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
12831
12832 if selections.is_empty() {
12833 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12834 return;
12835 };
12836
12837 let clipboard_text = match cx.read_from_clipboard() {
12838 Some(item) => match item.entries().first() {
12839 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12840 _ => None,
12841 },
12842 None => None,
12843 };
12844
12845 let Some(clipboard_text) = clipboard_text else {
12846 log::warn!("Clipboard doesn't contain text.");
12847 return;
12848 };
12849
12850 window.dispatch_action(
12851 Box::new(DiffClipboardWithSelectionData {
12852 clipboard_text,
12853 editor: cx.entity(),
12854 }),
12855 cx,
12856 );
12857 }
12858
12859 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12860 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12861 if let Some(item) = cx.read_from_clipboard() {
12862 let entries = item.entries();
12863
12864 match entries.first() {
12865 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12866 // of all the pasted entries.
12867 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12868 .do_paste(
12869 clipboard_string.text(),
12870 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12871 true,
12872 window,
12873 cx,
12874 ),
12875 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12876 }
12877 }
12878 }
12879
12880 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12881 if self.read_only(cx) {
12882 return;
12883 }
12884
12885 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12886
12887 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12888 if let Some((selections, _)) =
12889 self.selection_history.transaction(transaction_id).cloned()
12890 {
12891 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12892 s.select_anchors(selections.to_vec());
12893 });
12894 } else {
12895 log::error!(
12896 "No entry in selection_history found for undo. \
12897 This may correspond to a bug where undo does not update the selection. \
12898 If this is occurring, please add details to \
12899 https://github.com/zed-industries/zed/issues/22692"
12900 );
12901 }
12902 self.request_autoscroll(Autoscroll::fit(), cx);
12903 self.unmark_text(window, cx);
12904 self.refresh_edit_prediction(true, false, window, cx);
12905 cx.emit(EditorEvent::Edited { transaction_id });
12906 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12907 }
12908 }
12909
12910 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12911 if self.read_only(cx) {
12912 return;
12913 }
12914
12915 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12916
12917 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12918 if let Some((_, Some(selections))) =
12919 self.selection_history.transaction(transaction_id).cloned()
12920 {
12921 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12922 s.select_anchors(selections.to_vec());
12923 });
12924 } else {
12925 log::error!(
12926 "No entry in selection_history found for redo. \
12927 This may correspond to a bug where undo does not update the selection. \
12928 If this is occurring, please add details to \
12929 https://github.com/zed-industries/zed/issues/22692"
12930 );
12931 }
12932 self.request_autoscroll(Autoscroll::fit(), cx);
12933 self.unmark_text(window, cx);
12934 self.refresh_edit_prediction(true, false, window, cx);
12935 cx.emit(EditorEvent::Edited { transaction_id });
12936 }
12937 }
12938
12939 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12940 self.buffer
12941 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12942 }
12943
12944 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12945 self.buffer
12946 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12947 }
12948
12949 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12950 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12951 self.change_selections(Default::default(), window, cx, |s| {
12952 s.move_with(|map, selection| {
12953 let cursor = if selection.is_empty() {
12954 movement::left(map, selection.start)
12955 } else {
12956 selection.start
12957 };
12958 selection.collapse_to(cursor, SelectionGoal::None);
12959 });
12960 })
12961 }
12962
12963 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12964 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12965 self.change_selections(Default::default(), window, cx, |s| {
12966 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12967 })
12968 }
12969
12970 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12971 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12972 self.change_selections(Default::default(), window, cx, |s| {
12973 s.move_with(|map, selection| {
12974 let cursor = if selection.is_empty() {
12975 movement::right(map, selection.end)
12976 } else {
12977 selection.end
12978 };
12979 selection.collapse_to(cursor, SelectionGoal::None)
12980 });
12981 })
12982 }
12983
12984 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12985 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12986 self.change_selections(Default::default(), window, cx, |s| {
12987 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12988 });
12989 }
12990
12991 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12992 if self.take_rename(true, window, cx).is_some() {
12993 return;
12994 }
12995
12996 if self.mode.is_single_line() {
12997 cx.propagate();
12998 return;
12999 }
13000
13001 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13002
13003 let text_layout_details = &self.text_layout_details(window);
13004 let selection_count = self.selections.count();
13005 let first_selection = self.selections.first_anchor();
13006
13007 self.change_selections(Default::default(), window, cx, |s| {
13008 s.move_with(|map, selection| {
13009 if !selection.is_empty() {
13010 selection.goal = SelectionGoal::None;
13011 }
13012 let (cursor, goal) = movement::up(
13013 map,
13014 selection.start,
13015 selection.goal,
13016 false,
13017 text_layout_details,
13018 );
13019 selection.collapse_to(cursor, goal);
13020 });
13021 });
13022
13023 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13024 {
13025 cx.propagate();
13026 }
13027 }
13028
13029 pub fn move_up_by_lines(
13030 &mut self,
13031 action: &MoveUpByLines,
13032 window: &mut Window,
13033 cx: &mut Context<Self>,
13034 ) {
13035 if self.take_rename(true, window, cx).is_some() {
13036 return;
13037 }
13038
13039 if self.mode.is_single_line() {
13040 cx.propagate();
13041 return;
13042 }
13043
13044 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13045
13046 let text_layout_details = &self.text_layout_details(window);
13047
13048 self.change_selections(Default::default(), window, cx, |s| {
13049 s.move_with(|map, selection| {
13050 if !selection.is_empty() {
13051 selection.goal = SelectionGoal::None;
13052 }
13053 let (cursor, goal) = movement::up_by_rows(
13054 map,
13055 selection.start,
13056 action.lines,
13057 selection.goal,
13058 false,
13059 text_layout_details,
13060 );
13061 selection.collapse_to(cursor, goal);
13062 });
13063 })
13064 }
13065
13066 pub fn move_down_by_lines(
13067 &mut self,
13068 action: &MoveDownByLines,
13069 window: &mut Window,
13070 cx: &mut Context<Self>,
13071 ) {
13072 if self.take_rename(true, window, cx).is_some() {
13073 return;
13074 }
13075
13076 if self.mode.is_single_line() {
13077 cx.propagate();
13078 return;
13079 }
13080
13081 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13082
13083 let text_layout_details = &self.text_layout_details(window);
13084
13085 self.change_selections(Default::default(), window, cx, |s| {
13086 s.move_with(|map, selection| {
13087 if !selection.is_empty() {
13088 selection.goal = SelectionGoal::None;
13089 }
13090 let (cursor, goal) = movement::down_by_rows(
13091 map,
13092 selection.start,
13093 action.lines,
13094 selection.goal,
13095 false,
13096 text_layout_details,
13097 );
13098 selection.collapse_to(cursor, goal);
13099 });
13100 })
13101 }
13102
13103 pub fn select_down_by_lines(
13104 &mut self,
13105 action: &SelectDownByLines,
13106 window: &mut Window,
13107 cx: &mut Context<Self>,
13108 ) {
13109 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13110 let text_layout_details = &self.text_layout_details(window);
13111 self.change_selections(Default::default(), window, cx, |s| {
13112 s.move_heads_with(|map, head, goal| {
13113 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13114 })
13115 })
13116 }
13117
13118 pub fn select_up_by_lines(
13119 &mut self,
13120 action: &SelectUpByLines,
13121 window: &mut Window,
13122 cx: &mut Context<Self>,
13123 ) {
13124 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13125 let text_layout_details = &self.text_layout_details(window);
13126 self.change_selections(Default::default(), window, cx, |s| {
13127 s.move_heads_with(|map, head, goal| {
13128 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13129 })
13130 })
13131 }
13132
13133 pub fn select_page_up(
13134 &mut self,
13135 _: &SelectPageUp,
13136 window: &mut Window,
13137 cx: &mut Context<Self>,
13138 ) {
13139 let Some(row_count) = self.visible_row_count() else {
13140 return;
13141 };
13142
13143 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13144
13145 let text_layout_details = &self.text_layout_details(window);
13146
13147 self.change_selections(Default::default(), window, cx, |s| {
13148 s.move_heads_with(|map, head, goal| {
13149 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13150 })
13151 })
13152 }
13153
13154 pub fn move_page_up(
13155 &mut self,
13156 action: &MovePageUp,
13157 window: &mut Window,
13158 cx: &mut Context<Self>,
13159 ) {
13160 if self.take_rename(true, window, cx).is_some() {
13161 return;
13162 }
13163
13164 if self
13165 .context_menu
13166 .borrow_mut()
13167 .as_mut()
13168 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13169 .unwrap_or(false)
13170 {
13171 return;
13172 }
13173
13174 if matches!(self.mode, EditorMode::SingleLine) {
13175 cx.propagate();
13176 return;
13177 }
13178
13179 let Some(row_count) = self.visible_row_count() else {
13180 return;
13181 };
13182
13183 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13184
13185 let effects = if action.center_cursor {
13186 SelectionEffects::scroll(Autoscroll::center())
13187 } else {
13188 SelectionEffects::default()
13189 };
13190
13191 let text_layout_details = &self.text_layout_details(window);
13192
13193 self.change_selections(effects, window, cx, |s| {
13194 s.move_with(|map, selection| {
13195 if !selection.is_empty() {
13196 selection.goal = SelectionGoal::None;
13197 }
13198 let (cursor, goal) = movement::up_by_rows(
13199 map,
13200 selection.end,
13201 row_count,
13202 selection.goal,
13203 false,
13204 text_layout_details,
13205 );
13206 selection.collapse_to(cursor, goal);
13207 });
13208 });
13209 }
13210
13211 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13212 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13213 let text_layout_details = &self.text_layout_details(window);
13214 self.change_selections(Default::default(), window, cx, |s| {
13215 s.move_heads_with(|map, head, goal| {
13216 movement::up(map, head, goal, false, text_layout_details)
13217 })
13218 })
13219 }
13220
13221 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13222 self.take_rename(true, window, cx);
13223
13224 if self.mode.is_single_line() {
13225 cx.propagate();
13226 return;
13227 }
13228
13229 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13230
13231 let text_layout_details = &self.text_layout_details(window);
13232 let selection_count = self.selections.count();
13233 let first_selection = self.selections.first_anchor();
13234
13235 self.change_selections(Default::default(), window, cx, |s| {
13236 s.move_with(|map, selection| {
13237 if !selection.is_empty() {
13238 selection.goal = SelectionGoal::None;
13239 }
13240 let (cursor, goal) = movement::down(
13241 map,
13242 selection.end,
13243 selection.goal,
13244 false,
13245 text_layout_details,
13246 );
13247 selection.collapse_to(cursor, goal);
13248 });
13249 });
13250
13251 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13252 {
13253 cx.propagate();
13254 }
13255 }
13256
13257 pub fn select_page_down(
13258 &mut self,
13259 _: &SelectPageDown,
13260 window: &mut Window,
13261 cx: &mut Context<Self>,
13262 ) {
13263 let Some(row_count) = self.visible_row_count() else {
13264 return;
13265 };
13266
13267 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13268
13269 let text_layout_details = &self.text_layout_details(window);
13270
13271 self.change_selections(Default::default(), window, cx, |s| {
13272 s.move_heads_with(|map, head, goal| {
13273 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13274 })
13275 })
13276 }
13277
13278 pub fn move_page_down(
13279 &mut self,
13280 action: &MovePageDown,
13281 window: &mut Window,
13282 cx: &mut Context<Self>,
13283 ) {
13284 if self.take_rename(true, window, cx).is_some() {
13285 return;
13286 }
13287
13288 if self
13289 .context_menu
13290 .borrow_mut()
13291 .as_mut()
13292 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13293 .unwrap_or(false)
13294 {
13295 return;
13296 }
13297
13298 if matches!(self.mode, EditorMode::SingleLine) {
13299 cx.propagate();
13300 return;
13301 }
13302
13303 let Some(row_count) = self.visible_row_count() else {
13304 return;
13305 };
13306
13307 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13308
13309 let effects = if action.center_cursor {
13310 SelectionEffects::scroll(Autoscroll::center())
13311 } else {
13312 SelectionEffects::default()
13313 };
13314
13315 let text_layout_details = &self.text_layout_details(window);
13316 self.change_selections(effects, window, cx, |s| {
13317 s.move_with(|map, selection| {
13318 if !selection.is_empty() {
13319 selection.goal = SelectionGoal::None;
13320 }
13321 let (cursor, goal) = movement::down_by_rows(
13322 map,
13323 selection.end,
13324 row_count,
13325 selection.goal,
13326 false,
13327 text_layout_details,
13328 );
13329 selection.collapse_to(cursor, goal);
13330 });
13331 });
13332 }
13333
13334 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13335 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13336 let text_layout_details = &self.text_layout_details(window);
13337 self.change_selections(Default::default(), window, cx, |s| {
13338 s.move_heads_with(|map, head, goal| {
13339 movement::down(map, head, goal, false, text_layout_details)
13340 })
13341 });
13342 }
13343
13344 pub fn context_menu_first(
13345 &mut self,
13346 _: &ContextMenuFirst,
13347 window: &mut Window,
13348 cx: &mut Context<Self>,
13349 ) {
13350 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13351 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13352 }
13353 }
13354
13355 pub fn context_menu_prev(
13356 &mut self,
13357 _: &ContextMenuPrevious,
13358 window: &mut Window,
13359 cx: &mut Context<Self>,
13360 ) {
13361 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13362 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13363 }
13364 }
13365
13366 pub fn context_menu_next(
13367 &mut self,
13368 _: &ContextMenuNext,
13369 window: &mut Window,
13370 cx: &mut Context<Self>,
13371 ) {
13372 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13373 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13374 }
13375 }
13376
13377 pub fn context_menu_last(
13378 &mut self,
13379 _: &ContextMenuLast,
13380 window: &mut Window,
13381 cx: &mut Context<Self>,
13382 ) {
13383 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13384 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13385 }
13386 }
13387
13388 pub fn signature_help_prev(
13389 &mut self,
13390 _: &SignatureHelpPrevious,
13391 _: &mut Window,
13392 cx: &mut Context<Self>,
13393 ) {
13394 if let Some(popover) = self.signature_help_state.popover_mut() {
13395 if popover.current_signature == 0 {
13396 popover.current_signature = popover.signatures.len() - 1;
13397 } else {
13398 popover.current_signature -= 1;
13399 }
13400 cx.notify();
13401 }
13402 }
13403
13404 pub fn signature_help_next(
13405 &mut self,
13406 _: &SignatureHelpNext,
13407 _: &mut Window,
13408 cx: &mut Context<Self>,
13409 ) {
13410 if let Some(popover) = self.signature_help_state.popover_mut() {
13411 if popover.current_signature + 1 == popover.signatures.len() {
13412 popover.current_signature = 0;
13413 } else {
13414 popover.current_signature += 1;
13415 }
13416 cx.notify();
13417 }
13418 }
13419
13420 pub fn move_to_previous_word_start(
13421 &mut self,
13422 _: &MoveToPreviousWordStart,
13423 window: &mut Window,
13424 cx: &mut Context<Self>,
13425 ) {
13426 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13427 self.change_selections(Default::default(), window, cx, |s| {
13428 s.move_cursors_with(|map, head, _| {
13429 (
13430 movement::previous_word_start(map, head),
13431 SelectionGoal::None,
13432 )
13433 });
13434 })
13435 }
13436
13437 pub fn move_to_previous_subword_start(
13438 &mut self,
13439 _: &MoveToPreviousSubwordStart,
13440 window: &mut Window,
13441 cx: &mut Context<Self>,
13442 ) {
13443 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13444 self.change_selections(Default::default(), window, cx, |s| {
13445 s.move_cursors_with(|map, head, _| {
13446 (
13447 movement::previous_subword_start(map, head),
13448 SelectionGoal::None,
13449 )
13450 });
13451 })
13452 }
13453
13454 pub fn select_to_previous_word_start(
13455 &mut self,
13456 _: &SelectToPreviousWordStart,
13457 window: &mut Window,
13458 cx: &mut Context<Self>,
13459 ) {
13460 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13461 self.change_selections(Default::default(), window, cx, |s| {
13462 s.move_heads_with(|map, head, _| {
13463 (
13464 movement::previous_word_start(map, head),
13465 SelectionGoal::None,
13466 )
13467 });
13468 })
13469 }
13470
13471 pub fn select_to_previous_subword_start(
13472 &mut self,
13473 _: &SelectToPreviousSubwordStart,
13474 window: &mut Window,
13475 cx: &mut Context<Self>,
13476 ) {
13477 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13478 self.change_selections(Default::default(), window, cx, |s| {
13479 s.move_heads_with(|map, head, _| {
13480 (
13481 movement::previous_subword_start(map, head),
13482 SelectionGoal::None,
13483 )
13484 });
13485 })
13486 }
13487
13488 pub fn delete_to_previous_word_start(
13489 &mut self,
13490 action: &DeleteToPreviousWordStart,
13491 window: &mut Window,
13492 cx: &mut Context<Self>,
13493 ) {
13494 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13495 self.transact(window, cx, |this, window, cx| {
13496 this.select_autoclose_pair(window, cx);
13497 this.change_selections(Default::default(), window, cx, |s| {
13498 s.move_with(|map, selection| {
13499 if selection.is_empty() {
13500 let mut cursor = if action.ignore_newlines {
13501 movement::previous_word_start(map, selection.head())
13502 } else {
13503 movement::previous_word_start_or_newline(map, selection.head())
13504 };
13505 cursor = movement::adjust_greedy_deletion(
13506 map,
13507 selection.head(),
13508 cursor,
13509 action.ignore_brackets,
13510 );
13511 selection.set_head(cursor, SelectionGoal::None);
13512 }
13513 });
13514 });
13515 this.insert("", window, cx);
13516 });
13517 }
13518
13519 pub fn delete_to_previous_subword_start(
13520 &mut self,
13521 _: &DeleteToPreviousSubwordStart,
13522 window: &mut Window,
13523 cx: &mut Context<Self>,
13524 ) {
13525 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13526 self.transact(window, cx, |this, window, cx| {
13527 this.select_autoclose_pair(window, cx);
13528 this.change_selections(Default::default(), window, cx, |s| {
13529 s.move_with(|map, selection| {
13530 if selection.is_empty() {
13531 let mut cursor = movement::previous_subword_start(map, selection.head());
13532 cursor =
13533 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13534 selection.set_head(cursor, SelectionGoal::None);
13535 }
13536 });
13537 });
13538 this.insert("", window, cx);
13539 });
13540 }
13541
13542 pub fn move_to_next_word_end(
13543 &mut self,
13544 _: &MoveToNextWordEnd,
13545 window: &mut Window,
13546 cx: &mut Context<Self>,
13547 ) {
13548 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13549 self.change_selections(Default::default(), window, cx, |s| {
13550 s.move_cursors_with(|map, head, _| {
13551 (movement::next_word_end(map, head), SelectionGoal::None)
13552 });
13553 })
13554 }
13555
13556 pub fn move_to_next_subword_end(
13557 &mut self,
13558 _: &MoveToNextSubwordEnd,
13559 window: &mut Window,
13560 cx: &mut Context<Self>,
13561 ) {
13562 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13563 self.change_selections(Default::default(), window, cx, |s| {
13564 s.move_cursors_with(|map, head, _| {
13565 (movement::next_subword_end(map, head), SelectionGoal::None)
13566 });
13567 })
13568 }
13569
13570 pub fn select_to_next_word_end(
13571 &mut self,
13572 _: &SelectToNextWordEnd,
13573 window: &mut Window,
13574 cx: &mut Context<Self>,
13575 ) {
13576 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13577 self.change_selections(Default::default(), window, cx, |s| {
13578 s.move_heads_with(|map, head, _| {
13579 (movement::next_word_end(map, head), SelectionGoal::None)
13580 });
13581 })
13582 }
13583
13584 pub fn select_to_next_subword_end(
13585 &mut self,
13586 _: &SelectToNextSubwordEnd,
13587 window: &mut Window,
13588 cx: &mut Context<Self>,
13589 ) {
13590 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13591 self.change_selections(Default::default(), window, cx, |s| {
13592 s.move_heads_with(|map, head, _| {
13593 (movement::next_subword_end(map, head), SelectionGoal::None)
13594 });
13595 })
13596 }
13597
13598 pub fn delete_to_next_word_end(
13599 &mut self,
13600 action: &DeleteToNextWordEnd,
13601 window: &mut Window,
13602 cx: &mut Context<Self>,
13603 ) {
13604 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13605 self.transact(window, cx, |this, window, cx| {
13606 this.change_selections(Default::default(), window, cx, |s| {
13607 s.move_with(|map, selection| {
13608 if selection.is_empty() {
13609 let mut cursor = if action.ignore_newlines {
13610 movement::next_word_end(map, selection.head())
13611 } else {
13612 movement::next_word_end_or_newline(map, selection.head())
13613 };
13614 cursor = movement::adjust_greedy_deletion(
13615 map,
13616 selection.head(),
13617 cursor,
13618 action.ignore_brackets,
13619 );
13620 selection.set_head(cursor, SelectionGoal::None);
13621 }
13622 });
13623 });
13624 this.insert("", window, cx);
13625 });
13626 }
13627
13628 pub fn delete_to_next_subword_end(
13629 &mut self,
13630 _: &DeleteToNextSubwordEnd,
13631 window: &mut Window,
13632 cx: &mut Context<Self>,
13633 ) {
13634 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13635 self.transact(window, cx, |this, window, cx| {
13636 this.change_selections(Default::default(), window, cx, |s| {
13637 s.move_with(|map, selection| {
13638 if selection.is_empty() {
13639 let mut cursor = movement::next_subword_end(map, selection.head());
13640 cursor =
13641 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13642 selection.set_head(cursor, SelectionGoal::None);
13643 }
13644 });
13645 });
13646 this.insert("", window, cx);
13647 });
13648 }
13649
13650 pub fn move_to_beginning_of_line(
13651 &mut self,
13652 action: &MoveToBeginningOfLine,
13653 window: &mut Window,
13654 cx: &mut Context<Self>,
13655 ) {
13656 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13657 self.change_selections(Default::default(), window, cx, |s| {
13658 s.move_cursors_with(|map, head, _| {
13659 (
13660 movement::indented_line_beginning(
13661 map,
13662 head,
13663 action.stop_at_soft_wraps,
13664 action.stop_at_indent,
13665 ),
13666 SelectionGoal::None,
13667 )
13668 });
13669 })
13670 }
13671
13672 pub fn select_to_beginning_of_line(
13673 &mut self,
13674 action: &SelectToBeginningOfLine,
13675 window: &mut Window,
13676 cx: &mut Context<Self>,
13677 ) {
13678 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13679 self.change_selections(Default::default(), window, cx, |s| {
13680 s.move_heads_with(|map, head, _| {
13681 (
13682 movement::indented_line_beginning(
13683 map,
13684 head,
13685 action.stop_at_soft_wraps,
13686 action.stop_at_indent,
13687 ),
13688 SelectionGoal::None,
13689 )
13690 });
13691 });
13692 }
13693
13694 pub fn delete_to_beginning_of_line(
13695 &mut self,
13696 action: &DeleteToBeginningOfLine,
13697 window: &mut Window,
13698 cx: &mut Context<Self>,
13699 ) {
13700 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13701 self.transact(window, cx, |this, window, cx| {
13702 this.change_selections(Default::default(), window, cx, |s| {
13703 s.move_with(|_, selection| {
13704 selection.reversed = true;
13705 });
13706 });
13707
13708 this.select_to_beginning_of_line(
13709 &SelectToBeginningOfLine {
13710 stop_at_soft_wraps: false,
13711 stop_at_indent: action.stop_at_indent,
13712 },
13713 window,
13714 cx,
13715 );
13716 this.backspace(&Backspace, window, cx);
13717 });
13718 }
13719
13720 pub fn move_to_end_of_line(
13721 &mut self,
13722 action: &MoveToEndOfLine,
13723 window: &mut Window,
13724 cx: &mut Context<Self>,
13725 ) {
13726 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13727 self.change_selections(Default::default(), window, cx, |s| {
13728 s.move_cursors_with(|map, head, _| {
13729 (
13730 movement::line_end(map, head, action.stop_at_soft_wraps),
13731 SelectionGoal::None,
13732 )
13733 });
13734 })
13735 }
13736
13737 pub fn select_to_end_of_line(
13738 &mut self,
13739 action: &SelectToEndOfLine,
13740 window: &mut Window,
13741 cx: &mut Context<Self>,
13742 ) {
13743 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13744 self.change_selections(Default::default(), window, cx, |s| {
13745 s.move_heads_with(|map, head, _| {
13746 (
13747 movement::line_end(map, head, action.stop_at_soft_wraps),
13748 SelectionGoal::None,
13749 )
13750 });
13751 })
13752 }
13753
13754 pub fn delete_to_end_of_line(
13755 &mut self,
13756 _: &DeleteToEndOfLine,
13757 window: &mut Window,
13758 cx: &mut Context<Self>,
13759 ) {
13760 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13761 self.transact(window, cx, |this, window, cx| {
13762 this.select_to_end_of_line(
13763 &SelectToEndOfLine {
13764 stop_at_soft_wraps: false,
13765 },
13766 window,
13767 cx,
13768 );
13769 this.delete(&Delete, window, cx);
13770 });
13771 }
13772
13773 pub fn cut_to_end_of_line(
13774 &mut self,
13775 action: &CutToEndOfLine,
13776 window: &mut Window,
13777 cx: &mut Context<Self>,
13778 ) {
13779 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13780 self.transact(window, cx, |this, window, cx| {
13781 this.select_to_end_of_line(
13782 &SelectToEndOfLine {
13783 stop_at_soft_wraps: false,
13784 },
13785 window,
13786 cx,
13787 );
13788 if !action.stop_at_newlines {
13789 this.change_selections(Default::default(), window, cx, |s| {
13790 s.move_with(|_, sel| {
13791 if sel.is_empty() {
13792 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13793 }
13794 });
13795 });
13796 }
13797 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13798 let item = this.cut_common(false, window, cx);
13799 cx.write_to_clipboard(item);
13800 });
13801 }
13802
13803 pub fn move_to_start_of_paragraph(
13804 &mut self,
13805 _: &MoveToStartOfParagraph,
13806 window: &mut Window,
13807 cx: &mut Context<Self>,
13808 ) {
13809 if matches!(self.mode, EditorMode::SingleLine) {
13810 cx.propagate();
13811 return;
13812 }
13813 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13814 self.change_selections(Default::default(), window, cx, |s| {
13815 s.move_with(|map, selection| {
13816 selection.collapse_to(
13817 movement::start_of_paragraph(map, selection.head(), 1),
13818 SelectionGoal::None,
13819 )
13820 });
13821 })
13822 }
13823
13824 pub fn move_to_end_of_paragraph(
13825 &mut self,
13826 _: &MoveToEndOfParagraph,
13827 window: &mut Window,
13828 cx: &mut Context<Self>,
13829 ) {
13830 if matches!(self.mode, EditorMode::SingleLine) {
13831 cx.propagate();
13832 return;
13833 }
13834 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13835 self.change_selections(Default::default(), window, cx, |s| {
13836 s.move_with(|map, selection| {
13837 selection.collapse_to(
13838 movement::end_of_paragraph(map, selection.head(), 1),
13839 SelectionGoal::None,
13840 )
13841 });
13842 })
13843 }
13844
13845 pub fn select_to_start_of_paragraph(
13846 &mut self,
13847 _: &SelectToStartOfParagraph,
13848 window: &mut Window,
13849 cx: &mut Context<Self>,
13850 ) {
13851 if matches!(self.mode, EditorMode::SingleLine) {
13852 cx.propagate();
13853 return;
13854 }
13855 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13856 self.change_selections(Default::default(), window, cx, |s| {
13857 s.move_heads_with(|map, head, _| {
13858 (
13859 movement::start_of_paragraph(map, head, 1),
13860 SelectionGoal::None,
13861 )
13862 });
13863 })
13864 }
13865
13866 pub fn select_to_end_of_paragraph(
13867 &mut self,
13868 _: &SelectToEndOfParagraph,
13869 window: &mut Window,
13870 cx: &mut Context<Self>,
13871 ) {
13872 if matches!(self.mode, EditorMode::SingleLine) {
13873 cx.propagate();
13874 return;
13875 }
13876 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13877 self.change_selections(Default::default(), window, cx, |s| {
13878 s.move_heads_with(|map, head, _| {
13879 (
13880 movement::end_of_paragraph(map, head, 1),
13881 SelectionGoal::None,
13882 )
13883 });
13884 })
13885 }
13886
13887 pub fn move_to_start_of_excerpt(
13888 &mut self,
13889 _: &MoveToStartOfExcerpt,
13890 window: &mut Window,
13891 cx: &mut Context<Self>,
13892 ) {
13893 if matches!(self.mode, EditorMode::SingleLine) {
13894 cx.propagate();
13895 return;
13896 }
13897 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13898 self.change_selections(Default::default(), window, cx, |s| {
13899 s.move_with(|map, selection| {
13900 selection.collapse_to(
13901 movement::start_of_excerpt(
13902 map,
13903 selection.head(),
13904 workspace::searchable::Direction::Prev,
13905 ),
13906 SelectionGoal::None,
13907 )
13908 });
13909 })
13910 }
13911
13912 pub fn move_to_start_of_next_excerpt(
13913 &mut self,
13914 _: &MoveToStartOfNextExcerpt,
13915 window: &mut Window,
13916 cx: &mut Context<Self>,
13917 ) {
13918 if matches!(self.mode, EditorMode::SingleLine) {
13919 cx.propagate();
13920 return;
13921 }
13922
13923 self.change_selections(Default::default(), window, cx, |s| {
13924 s.move_with(|map, selection| {
13925 selection.collapse_to(
13926 movement::start_of_excerpt(
13927 map,
13928 selection.head(),
13929 workspace::searchable::Direction::Next,
13930 ),
13931 SelectionGoal::None,
13932 )
13933 });
13934 })
13935 }
13936
13937 pub fn move_to_end_of_excerpt(
13938 &mut self,
13939 _: &MoveToEndOfExcerpt,
13940 window: &mut Window,
13941 cx: &mut Context<Self>,
13942 ) {
13943 if matches!(self.mode, EditorMode::SingleLine) {
13944 cx.propagate();
13945 return;
13946 }
13947 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13948 self.change_selections(Default::default(), window, cx, |s| {
13949 s.move_with(|map, selection| {
13950 selection.collapse_to(
13951 movement::end_of_excerpt(
13952 map,
13953 selection.head(),
13954 workspace::searchable::Direction::Next,
13955 ),
13956 SelectionGoal::None,
13957 )
13958 });
13959 })
13960 }
13961
13962 pub fn move_to_end_of_previous_excerpt(
13963 &mut self,
13964 _: &MoveToEndOfPreviousExcerpt,
13965 window: &mut Window,
13966 cx: &mut Context<Self>,
13967 ) {
13968 if matches!(self.mode, EditorMode::SingleLine) {
13969 cx.propagate();
13970 return;
13971 }
13972 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13973 self.change_selections(Default::default(), window, cx, |s| {
13974 s.move_with(|map, selection| {
13975 selection.collapse_to(
13976 movement::end_of_excerpt(
13977 map,
13978 selection.head(),
13979 workspace::searchable::Direction::Prev,
13980 ),
13981 SelectionGoal::None,
13982 )
13983 });
13984 })
13985 }
13986
13987 pub fn select_to_start_of_excerpt(
13988 &mut self,
13989 _: &SelectToStartOfExcerpt,
13990 window: &mut Window,
13991 cx: &mut Context<Self>,
13992 ) {
13993 if matches!(self.mode, EditorMode::SingleLine) {
13994 cx.propagate();
13995 return;
13996 }
13997 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13998 self.change_selections(Default::default(), window, cx, |s| {
13999 s.move_heads_with(|map, head, _| {
14000 (
14001 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14002 SelectionGoal::None,
14003 )
14004 });
14005 })
14006 }
14007
14008 pub fn select_to_start_of_next_excerpt(
14009 &mut self,
14010 _: &SelectToStartOfNextExcerpt,
14011 window: &mut Window,
14012 cx: &mut Context<Self>,
14013 ) {
14014 if matches!(self.mode, EditorMode::SingleLine) {
14015 cx.propagate();
14016 return;
14017 }
14018 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14019 self.change_selections(Default::default(), window, cx, |s| {
14020 s.move_heads_with(|map, head, _| {
14021 (
14022 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14023 SelectionGoal::None,
14024 )
14025 });
14026 })
14027 }
14028
14029 pub fn select_to_end_of_excerpt(
14030 &mut self,
14031 _: &SelectToEndOfExcerpt,
14032 window: &mut Window,
14033 cx: &mut Context<Self>,
14034 ) {
14035 if matches!(self.mode, EditorMode::SingleLine) {
14036 cx.propagate();
14037 return;
14038 }
14039 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14040 self.change_selections(Default::default(), window, cx, |s| {
14041 s.move_heads_with(|map, head, _| {
14042 (
14043 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14044 SelectionGoal::None,
14045 )
14046 });
14047 })
14048 }
14049
14050 pub fn select_to_end_of_previous_excerpt(
14051 &mut self,
14052 _: &SelectToEndOfPreviousExcerpt,
14053 window: &mut Window,
14054 cx: &mut Context<Self>,
14055 ) {
14056 if matches!(self.mode, EditorMode::SingleLine) {
14057 cx.propagate();
14058 return;
14059 }
14060 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14061 self.change_selections(Default::default(), window, cx, |s| {
14062 s.move_heads_with(|map, head, _| {
14063 (
14064 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14065 SelectionGoal::None,
14066 )
14067 });
14068 })
14069 }
14070
14071 pub fn move_to_beginning(
14072 &mut self,
14073 _: &MoveToBeginning,
14074 window: &mut Window,
14075 cx: &mut Context<Self>,
14076 ) {
14077 if matches!(self.mode, EditorMode::SingleLine) {
14078 cx.propagate();
14079 return;
14080 }
14081 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14082 self.change_selections(Default::default(), window, cx, |s| {
14083 s.select_ranges(vec![0..0]);
14084 });
14085 }
14086
14087 pub fn select_to_beginning(
14088 &mut self,
14089 _: &SelectToBeginning,
14090 window: &mut Window,
14091 cx: &mut Context<Self>,
14092 ) {
14093 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14094 selection.set_head(Point::zero(), SelectionGoal::None);
14095 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14096 self.change_selections(Default::default(), window, cx, |s| {
14097 s.select(vec![selection]);
14098 });
14099 }
14100
14101 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14102 if matches!(self.mode, EditorMode::SingleLine) {
14103 cx.propagate();
14104 return;
14105 }
14106 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14107 let cursor = self.buffer.read(cx).read(cx).len();
14108 self.change_selections(Default::default(), window, cx, |s| {
14109 s.select_ranges(vec![cursor..cursor])
14110 });
14111 }
14112
14113 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14114 self.nav_history = nav_history;
14115 }
14116
14117 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14118 self.nav_history.as_ref()
14119 }
14120
14121 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14122 self.push_to_nav_history(
14123 self.selections.newest_anchor().head(),
14124 None,
14125 false,
14126 true,
14127 cx,
14128 );
14129 }
14130
14131 fn push_to_nav_history(
14132 &mut self,
14133 cursor_anchor: Anchor,
14134 new_position: Option<Point>,
14135 is_deactivate: bool,
14136 always: bool,
14137 cx: &mut Context<Self>,
14138 ) {
14139 if let Some(nav_history) = self.nav_history.as_mut() {
14140 let buffer = self.buffer.read(cx).read(cx);
14141 let cursor_position = cursor_anchor.to_point(&buffer);
14142 let scroll_state = self.scroll_manager.anchor();
14143 let scroll_top_row = scroll_state.top_row(&buffer);
14144 drop(buffer);
14145
14146 if let Some(new_position) = new_position {
14147 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14148 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14149 return;
14150 }
14151 }
14152
14153 nav_history.push(
14154 Some(NavigationData {
14155 cursor_anchor,
14156 cursor_position,
14157 scroll_anchor: scroll_state,
14158 scroll_top_row,
14159 }),
14160 cx,
14161 );
14162 cx.emit(EditorEvent::PushedToNavHistory {
14163 anchor: cursor_anchor,
14164 is_deactivate,
14165 })
14166 }
14167 }
14168
14169 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14170 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14171 let buffer = self.buffer.read(cx).snapshot(cx);
14172 let mut selection = self.selections.first::<usize>(&self.display_snapshot(cx));
14173 selection.set_head(buffer.len(), SelectionGoal::None);
14174 self.change_selections(Default::default(), window, cx, |s| {
14175 s.select(vec![selection]);
14176 });
14177 }
14178
14179 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14180 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14181 let end = self.buffer.read(cx).read(cx).len();
14182 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14183 s.select_ranges(vec![0..end]);
14184 });
14185 }
14186
14187 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14188 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14189 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14190 let mut selections = self.selections.all::<Point>(&display_map);
14191 let max_point = display_map.buffer_snapshot().max_point();
14192 for selection in &mut selections {
14193 let rows = selection.spanned_rows(true, &display_map);
14194 selection.start = Point::new(rows.start.0, 0);
14195 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14196 selection.reversed = false;
14197 }
14198 self.change_selections(Default::default(), window, cx, |s| {
14199 s.select(selections);
14200 });
14201 }
14202
14203 pub fn split_selection_into_lines(
14204 &mut self,
14205 action: &SplitSelectionIntoLines,
14206 window: &mut Window,
14207 cx: &mut Context<Self>,
14208 ) {
14209 let selections = self
14210 .selections
14211 .all::<Point>(&self.display_snapshot(cx))
14212 .into_iter()
14213 .map(|selection| selection.start..selection.end)
14214 .collect::<Vec<_>>();
14215 self.unfold_ranges(&selections, true, true, cx);
14216
14217 let mut new_selection_ranges = Vec::new();
14218 {
14219 let buffer = self.buffer.read(cx).read(cx);
14220 for selection in selections {
14221 for row in selection.start.row..selection.end.row {
14222 let line_start = Point::new(row, 0);
14223 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14224
14225 if action.keep_selections {
14226 // Keep the selection range for each line
14227 let selection_start = if row == selection.start.row {
14228 selection.start
14229 } else {
14230 line_start
14231 };
14232 new_selection_ranges.push(selection_start..line_end);
14233 } else {
14234 // Collapse to cursor at end of line
14235 new_selection_ranges.push(line_end..line_end);
14236 }
14237 }
14238
14239 let is_multiline_selection = selection.start.row != selection.end.row;
14240 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14241 // so this action feels more ergonomic when paired with other selection operations
14242 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14243 if !should_skip_last {
14244 if action.keep_selections {
14245 if is_multiline_selection {
14246 let line_start = Point::new(selection.end.row, 0);
14247 new_selection_ranges.push(line_start..selection.end);
14248 } else {
14249 new_selection_ranges.push(selection.start..selection.end);
14250 }
14251 } else {
14252 new_selection_ranges.push(selection.end..selection.end);
14253 }
14254 }
14255 }
14256 }
14257 self.change_selections(Default::default(), window, cx, |s| {
14258 s.select_ranges(new_selection_ranges);
14259 });
14260 }
14261
14262 pub fn add_selection_above(
14263 &mut self,
14264 action: &AddSelectionAbove,
14265 window: &mut Window,
14266 cx: &mut Context<Self>,
14267 ) {
14268 self.add_selection(true, action.skip_soft_wrap, window, cx);
14269 }
14270
14271 pub fn add_selection_below(
14272 &mut self,
14273 action: &AddSelectionBelow,
14274 window: &mut Window,
14275 cx: &mut Context<Self>,
14276 ) {
14277 self.add_selection(false, action.skip_soft_wrap, window, cx);
14278 }
14279
14280 fn add_selection(
14281 &mut self,
14282 above: bool,
14283 skip_soft_wrap: bool,
14284 window: &mut Window,
14285 cx: &mut Context<Self>,
14286 ) {
14287 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14288
14289 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14290 let all_selections = self.selections.all::<Point>(&display_map);
14291 let text_layout_details = self.text_layout_details(window);
14292
14293 let (mut columnar_selections, new_selections_to_columnarize) = {
14294 if let Some(state) = self.add_selections_state.as_ref() {
14295 let columnar_selection_ids: HashSet<_> = state
14296 .groups
14297 .iter()
14298 .flat_map(|group| group.stack.iter())
14299 .copied()
14300 .collect();
14301
14302 all_selections
14303 .into_iter()
14304 .partition(|s| columnar_selection_ids.contains(&s.id))
14305 } else {
14306 (Vec::new(), all_selections)
14307 }
14308 };
14309
14310 let mut state = self
14311 .add_selections_state
14312 .take()
14313 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14314
14315 for selection in new_selections_to_columnarize {
14316 let range = selection.display_range(&display_map).sorted();
14317 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14318 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14319 let positions = start_x.min(end_x)..start_x.max(end_x);
14320 let mut stack = Vec::new();
14321 for row in range.start.row().0..=range.end.row().0 {
14322 if let Some(selection) = self.selections.build_columnar_selection(
14323 &display_map,
14324 DisplayRow(row),
14325 &positions,
14326 selection.reversed,
14327 &text_layout_details,
14328 ) {
14329 stack.push(selection.id);
14330 columnar_selections.push(selection);
14331 }
14332 }
14333 if !stack.is_empty() {
14334 if above {
14335 stack.reverse();
14336 }
14337 state.groups.push(AddSelectionsGroup { above, stack });
14338 }
14339 }
14340
14341 let mut final_selections = Vec::new();
14342 let end_row = if above {
14343 DisplayRow(0)
14344 } else {
14345 display_map.max_point().row()
14346 };
14347
14348 let mut last_added_item_per_group = HashMap::default();
14349 for group in state.groups.iter_mut() {
14350 if let Some(last_id) = group.stack.last() {
14351 last_added_item_per_group.insert(*last_id, group);
14352 }
14353 }
14354
14355 for selection in columnar_selections {
14356 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14357 if above == group.above {
14358 let range = selection.display_range(&display_map).sorted();
14359 debug_assert_eq!(range.start.row(), range.end.row());
14360 let mut row = range.start.row();
14361 let positions =
14362 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14363 Pixels::from(start)..Pixels::from(end)
14364 } else {
14365 let start_x =
14366 display_map.x_for_display_point(range.start, &text_layout_details);
14367 let end_x =
14368 display_map.x_for_display_point(range.end, &text_layout_details);
14369 start_x.min(end_x)..start_x.max(end_x)
14370 };
14371
14372 let mut maybe_new_selection = None;
14373 let direction = if above { -1 } else { 1 };
14374
14375 while row != end_row {
14376 if skip_soft_wrap {
14377 row = display_map
14378 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14379 .row();
14380 } else if above {
14381 row.0 -= 1;
14382 } else {
14383 row.0 += 1;
14384 }
14385
14386 if let Some(new_selection) = self.selections.build_columnar_selection(
14387 &display_map,
14388 row,
14389 &positions,
14390 selection.reversed,
14391 &text_layout_details,
14392 ) {
14393 maybe_new_selection = Some(new_selection);
14394 break;
14395 }
14396 }
14397
14398 if let Some(new_selection) = maybe_new_selection {
14399 group.stack.push(new_selection.id);
14400 if above {
14401 final_selections.push(new_selection);
14402 final_selections.push(selection);
14403 } else {
14404 final_selections.push(selection);
14405 final_selections.push(new_selection);
14406 }
14407 } else {
14408 final_selections.push(selection);
14409 }
14410 } else {
14411 group.stack.pop();
14412 }
14413 } else {
14414 final_selections.push(selection);
14415 }
14416 }
14417
14418 self.change_selections(Default::default(), window, cx, |s| {
14419 s.select(final_selections);
14420 });
14421
14422 let final_selection_ids: HashSet<_> = self
14423 .selections
14424 .all::<Point>(&display_map)
14425 .iter()
14426 .map(|s| s.id)
14427 .collect();
14428 state.groups.retain_mut(|group| {
14429 // selections might get merged above so we remove invalid items from stacks
14430 group.stack.retain(|id| final_selection_ids.contains(id));
14431
14432 // single selection in stack can be treated as initial state
14433 group.stack.len() > 1
14434 });
14435
14436 if !state.groups.is_empty() {
14437 self.add_selections_state = Some(state);
14438 }
14439 }
14440
14441 fn select_match_ranges(
14442 &mut self,
14443 range: Range<usize>,
14444 reversed: bool,
14445 replace_newest: bool,
14446 auto_scroll: Option<Autoscroll>,
14447 window: &mut Window,
14448 cx: &mut Context<Editor>,
14449 ) {
14450 self.unfold_ranges(
14451 std::slice::from_ref(&range),
14452 false,
14453 auto_scroll.is_some(),
14454 cx,
14455 );
14456 let effects = if let Some(scroll) = auto_scroll {
14457 SelectionEffects::scroll(scroll)
14458 } else {
14459 SelectionEffects::no_scroll()
14460 };
14461 self.change_selections(effects, window, cx, |s| {
14462 if replace_newest {
14463 s.delete(s.newest_anchor().id);
14464 }
14465 if reversed {
14466 s.insert_range(range.end..range.start);
14467 } else {
14468 s.insert_range(range);
14469 }
14470 });
14471 }
14472
14473 pub fn select_next_match_internal(
14474 &mut self,
14475 display_map: &DisplaySnapshot,
14476 replace_newest: bool,
14477 autoscroll: Option<Autoscroll>,
14478 window: &mut Window,
14479 cx: &mut Context<Self>,
14480 ) -> Result<()> {
14481 let buffer = display_map.buffer_snapshot();
14482 let mut selections = self.selections.all::<usize>(&display_map);
14483 if let Some(mut select_next_state) = self.select_next_state.take() {
14484 let query = &select_next_state.query;
14485 if !select_next_state.done {
14486 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14487 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14488 let mut next_selected_range = None;
14489
14490 // Collect and sort selection ranges for efficient overlap checking
14491 let mut selection_ranges: Vec<_> = selections.iter().map(|s| s.range()).collect();
14492 selection_ranges.sort_by_key(|r| r.start);
14493
14494 let bytes_after_last_selection =
14495 buffer.bytes_in_range(last_selection.end..buffer.len());
14496 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14497 let query_matches = query
14498 .stream_find_iter(bytes_after_last_selection)
14499 .map(|result| (last_selection.end, result))
14500 .chain(
14501 query
14502 .stream_find_iter(bytes_before_first_selection)
14503 .map(|result| (0, result)),
14504 );
14505
14506 for (start_offset, query_match) in query_matches {
14507 let query_match = query_match.unwrap(); // can only fail due to I/O
14508 let offset_range =
14509 start_offset + query_match.start()..start_offset + query_match.end();
14510
14511 if !select_next_state.wordwise
14512 || (!buffer.is_inside_word(offset_range.start, None)
14513 && !buffer.is_inside_word(offset_range.end, None))
14514 {
14515 // Use binary search to check for overlap (O(log n))
14516 let overlaps = selection_ranges
14517 .binary_search_by(|range| {
14518 if range.end <= offset_range.start {
14519 std::cmp::Ordering::Less
14520 } else if range.start >= offset_range.end {
14521 std::cmp::Ordering::Greater
14522 } else {
14523 std::cmp::Ordering::Equal
14524 }
14525 })
14526 .is_ok();
14527
14528 if !overlaps {
14529 next_selected_range = Some(offset_range);
14530 break;
14531 }
14532 }
14533 }
14534
14535 if let Some(next_selected_range) = next_selected_range {
14536 self.select_match_ranges(
14537 next_selected_range,
14538 last_selection.reversed,
14539 replace_newest,
14540 autoscroll,
14541 window,
14542 cx,
14543 );
14544 } else {
14545 select_next_state.done = true;
14546 }
14547 }
14548
14549 self.select_next_state = Some(select_next_state);
14550 } else {
14551 let mut only_carets = true;
14552 let mut same_text_selected = true;
14553 let mut selected_text = None;
14554
14555 let mut selections_iter = selections.iter().peekable();
14556 while let Some(selection) = selections_iter.next() {
14557 if selection.start != selection.end {
14558 only_carets = false;
14559 }
14560
14561 if same_text_selected {
14562 if selected_text.is_none() {
14563 selected_text =
14564 Some(buffer.text_for_range(selection.range()).collect::<String>());
14565 }
14566
14567 if let Some(next_selection) = selections_iter.peek() {
14568 if next_selection.range().len() == selection.range().len() {
14569 let next_selected_text = buffer
14570 .text_for_range(next_selection.range())
14571 .collect::<String>();
14572 if Some(next_selected_text) != selected_text {
14573 same_text_selected = false;
14574 selected_text = None;
14575 }
14576 } else {
14577 same_text_selected = false;
14578 selected_text = None;
14579 }
14580 }
14581 }
14582 }
14583
14584 if only_carets {
14585 for selection in &mut selections {
14586 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14587 selection.start = word_range.start;
14588 selection.end = word_range.end;
14589 selection.goal = SelectionGoal::None;
14590 selection.reversed = false;
14591 self.select_match_ranges(
14592 selection.start..selection.end,
14593 selection.reversed,
14594 replace_newest,
14595 autoscroll,
14596 window,
14597 cx,
14598 );
14599 }
14600
14601 if selections.len() == 1 {
14602 let selection = selections
14603 .last()
14604 .expect("ensured that there's only one selection");
14605 let query = buffer
14606 .text_for_range(selection.start..selection.end)
14607 .collect::<String>();
14608 let is_empty = query.is_empty();
14609 let select_state = SelectNextState {
14610 query: AhoCorasick::new(&[query])?,
14611 wordwise: true,
14612 done: is_empty,
14613 };
14614 self.select_next_state = Some(select_state);
14615 } else {
14616 self.select_next_state = None;
14617 }
14618 } else if let Some(selected_text) = selected_text {
14619 self.select_next_state = Some(SelectNextState {
14620 query: AhoCorasick::new(&[selected_text])?,
14621 wordwise: false,
14622 done: false,
14623 });
14624 self.select_next_match_internal(
14625 display_map,
14626 replace_newest,
14627 autoscroll,
14628 window,
14629 cx,
14630 )?;
14631 }
14632 }
14633 Ok(())
14634 }
14635
14636 pub fn select_all_matches(
14637 &mut self,
14638 _action: &SelectAllMatches,
14639 window: &mut Window,
14640 cx: &mut Context<Self>,
14641 ) -> Result<()> {
14642 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14643
14644 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14645
14646 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14647 let Some(select_next_state) = self.select_next_state.as_mut() else {
14648 return Ok(());
14649 };
14650 if select_next_state.done {
14651 return Ok(());
14652 }
14653
14654 let mut new_selections = Vec::new();
14655
14656 let reversed = self.selections.oldest::<usize>(&display_map).reversed;
14657 let buffer = display_map.buffer_snapshot();
14658 let query_matches = select_next_state
14659 .query
14660 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14661
14662 for query_match in query_matches.into_iter() {
14663 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14664 let offset_range = if reversed {
14665 query_match.end()..query_match.start()
14666 } else {
14667 query_match.start()..query_match.end()
14668 };
14669
14670 if !select_next_state.wordwise
14671 || (!buffer.is_inside_word(offset_range.start, None)
14672 && !buffer.is_inside_word(offset_range.end, None))
14673 {
14674 new_selections.push(offset_range.start..offset_range.end);
14675 }
14676 }
14677
14678 select_next_state.done = true;
14679
14680 if new_selections.is_empty() {
14681 log::error!("bug: new_selections is empty in select_all_matches");
14682 return Ok(());
14683 }
14684
14685 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14686 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14687 selections.select_ranges(new_selections)
14688 });
14689
14690 Ok(())
14691 }
14692
14693 pub fn select_next(
14694 &mut self,
14695 action: &SelectNext,
14696 window: &mut Window,
14697 cx: &mut Context<Self>,
14698 ) -> Result<()> {
14699 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14700 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14701 self.select_next_match_internal(
14702 &display_map,
14703 action.replace_newest,
14704 Some(Autoscroll::newest()),
14705 window,
14706 cx,
14707 )?;
14708 Ok(())
14709 }
14710
14711 pub fn select_previous(
14712 &mut self,
14713 action: &SelectPrevious,
14714 window: &mut Window,
14715 cx: &mut Context<Self>,
14716 ) -> Result<()> {
14717 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14718 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14719 let buffer = display_map.buffer_snapshot();
14720 let mut selections = self.selections.all::<usize>(&display_map);
14721 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14722 let query = &select_prev_state.query;
14723 if !select_prev_state.done {
14724 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14725 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14726 let mut next_selected_range = None;
14727 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14728 let bytes_before_last_selection =
14729 buffer.reversed_bytes_in_range(0..last_selection.start);
14730 let bytes_after_first_selection =
14731 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14732 let query_matches = query
14733 .stream_find_iter(bytes_before_last_selection)
14734 .map(|result| (last_selection.start, result))
14735 .chain(
14736 query
14737 .stream_find_iter(bytes_after_first_selection)
14738 .map(|result| (buffer.len(), result)),
14739 );
14740 for (end_offset, query_match) in query_matches {
14741 let query_match = query_match.unwrap(); // can only fail due to I/O
14742 let offset_range =
14743 end_offset - query_match.end()..end_offset - query_match.start();
14744
14745 if !select_prev_state.wordwise
14746 || (!buffer.is_inside_word(offset_range.start, None)
14747 && !buffer.is_inside_word(offset_range.end, None))
14748 {
14749 next_selected_range = Some(offset_range);
14750 break;
14751 }
14752 }
14753
14754 if let Some(next_selected_range) = next_selected_range {
14755 self.select_match_ranges(
14756 next_selected_range,
14757 last_selection.reversed,
14758 action.replace_newest,
14759 Some(Autoscroll::newest()),
14760 window,
14761 cx,
14762 );
14763 } else {
14764 select_prev_state.done = true;
14765 }
14766 }
14767
14768 self.select_prev_state = Some(select_prev_state);
14769 } else {
14770 let mut only_carets = true;
14771 let mut same_text_selected = true;
14772 let mut selected_text = None;
14773
14774 let mut selections_iter = selections.iter().peekable();
14775 while let Some(selection) = selections_iter.next() {
14776 if selection.start != selection.end {
14777 only_carets = false;
14778 }
14779
14780 if same_text_selected {
14781 if selected_text.is_none() {
14782 selected_text =
14783 Some(buffer.text_for_range(selection.range()).collect::<String>());
14784 }
14785
14786 if let Some(next_selection) = selections_iter.peek() {
14787 if next_selection.range().len() == selection.range().len() {
14788 let next_selected_text = buffer
14789 .text_for_range(next_selection.range())
14790 .collect::<String>();
14791 if Some(next_selected_text) != selected_text {
14792 same_text_selected = false;
14793 selected_text = None;
14794 }
14795 } else {
14796 same_text_selected = false;
14797 selected_text = None;
14798 }
14799 }
14800 }
14801 }
14802
14803 if only_carets {
14804 for selection in &mut selections {
14805 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14806 selection.start = word_range.start;
14807 selection.end = word_range.end;
14808 selection.goal = SelectionGoal::None;
14809 selection.reversed = false;
14810 self.select_match_ranges(
14811 selection.start..selection.end,
14812 selection.reversed,
14813 action.replace_newest,
14814 Some(Autoscroll::newest()),
14815 window,
14816 cx,
14817 );
14818 }
14819 if selections.len() == 1 {
14820 let selection = selections
14821 .last()
14822 .expect("ensured that there's only one selection");
14823 let query = buffer
14824 .text_for_range(selection.start..selection.end)
14825 .collect::<String>();
14826 let is_empty = query.is_empty();
14827 let select_state = SelectNextState {
14828 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14829 wordwise: true,
14830 done: is_empty,
14831 };
14832 self.select_prev_state = Some(select_state);
14833 } else {
14834 self.select_prev_state = None;
14835 }
14836 } else if let Some(selected_text) = selected_text {
14837 self.select_prev_state = Some(SelectNextState {
14838 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14839 wordwise: false,
14840 done: false,
14841 });
14842 self.select_previous(action, window, cx)?;
14843 }
14844 }
14845 Ok(())
14846 }
14847
14848 pub fn find_next_match(
14849 &mut self,
14850 _: &FindNextMatch,
14851 window: &mut Window,
14852 cx: &mut Context<Self>,
14853 ) -> Result<()> {
14854 let selections = self.selections.disjoint_anchors_arc();
14855 match selections.first() {
14856 Some(first) if selections.len() >= 2 => {
14857 self.change_selections(Default::default(), window, cx, |s| {
14858 s.select_ranges([first.range()]);
14859 });
14860 }
14861 _ => self.select_next(
14862 &SelectNext {
14863 replace_newest: true,
14864 },
14865 window,
14866 cx,
14867 )?,
14868 }
14869 Ok(())
14870 }
14871
14872 pub fn find_previous_match(
14873 &mut self,
14874 _: &FindPreviousMatch,
14875 window: &mut Window,
14876 cx: &mut Context<Self>,
14877 ) -> Result<()> {
14878 let selections = self.selections.disjoint_anchors_arc();
14879 match selections.last() {
14880 Some(last) if selections.len() >= 2 => {
14881 self.change_selections(Default::default(), window, cx, |s| {
14882 s.select_ranges([last.range()]);
14883 });
14884 }
14885 _ => self.select_previous(
14886 &SelectPrevious {
14887 replace_newest: true,
14888 },
14889 window,
14890 cx,
14891 )?,
14892 }
14893 Ok(())
14894 }
14895
14896 pub fn toggle_comments(
14897 &mut self,
14898 action: &ToggleComments,
14899 window: &mut Window,
14900 cx: &mut Context<Self>,
14901 ) {
14902 if self.read_only(cx) {
14903 return;
14904 }
14905 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14906 let text_layout_details = &self.text_layout_details(window);
14907 self.transact(window, cx, |this, window, cx| {
14908 let mut selections = this
14909 .selections
14910 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
14911 let mut edits = Vec::new();
14912 let mut selection_edit_ranges = Vec::new();
14913 let mut last_toggled_row = None;
14914 let snapshot = this.buffer.read(cx).read(cx);
14915 let empty_str: Arc<str> = Arc::default();
14916 let mut suffixes_inserted = Vec::new();
14917 let ignore_indent = action.ignore_indent;
14918
14919 fn comment_prefix_range(
14920 snapshot: &MultiBufferSnapshot,
14921 row: MultiBufferRow,
14922 comment_prefix: &str,
14923 comment_prefix_whitespace: &str,
14924 ignore_indent: bool,
14925 ) -> Range<Point> {
14926 let indent_size = if ignore_indent {
14927 0
14928 } else {
14929 snapshot.indent_size_for_line(row).len
14930 };
14931
14932 let start = Point::new(row.0, indent_size);
14933
14934 let mut line_bytes = snapshot
14935 .bytes_in_range(start..snapshot.max_point())
14936 .flatten()
14937 .copied();
14938
14939 // If this line currently begins with the line comment prefix, then record
14940 // the range containing the prefix.
14941 if line_bytes
14942 .by_ref()
14943 .take(comment_prefix.len())
14944 .eq(comment_prefix.bytes())
14945 {
14946 // Include any whitespace that matches the comment prefix.
14947 let matching_whitespace_len = line_bytes
14948 .zip(comment_prefix_whitespace.bytes())
14949 .take_while(|(a, b)| a == b)
14950 .count() as u32;
14951 let end = Point::new(
14952 start.row,
14953 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14954 );
14955 start..end
14956 } else {
14957 start..start
14958 }
14959 }
14960
14961 fn comment_suffix_range(
14962 snapshot: &MultiBufferSnapshot,
14963 row: MultiBufferRow,
14964 comment_suffix: &str,
14965 comment_suffix_has_leading_space: bool,
14966 ) -> Range<Point> {
14967 let end = Point::new(row.0, snapshot.line_len(row));
14968 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14969
14970 let mut line_end_bytes = snapshot
14971 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14972 .flatten()
14973 .copied();
14974
14975 let leading_space_len = if suffix_start_column > 0
14976 && line_end_bytes.next() == Some(b' ')
14977 && comment_suffix_has_leading_space
14978 {
14979 1
14980 } else {
14981 0
14982 };
14983
14984 // If this line currently begins with the line comment prefix, then record
14985 // the range containing the prefix.
14986 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14987 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14988 start..end
14989 } else {
14990 end..end
14991 }
14992 }
14993
14994 // TODO: Handle selections that cross excerpts
14995 for selection in &mut selections {
14996 let start_column = snapshot
14997 .indent_size_for_line(MultiBufferRow(selection.start.row))
14998 .len;
14999 let language = if let Some(language) =
15000 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15001 {
15002 language
15003 } else {
15004 continue;
15005 };
15006
15007 selection_edit_ranges.clear();
15008
15009 // If multiple selections contain a given row, avoid processing that
15010 // row more than once.
15011 let mut start_row = MultiBufferRow(selection.start.row);
15012 if last_toggled_row == Some(start_row) {
15013 start_row = start_row.next_row();
15014 }
15015 let end_row =
15016 if selection.end.row > selection.start.row && selection.end.column == 0 {
15017 MultiBufferRow(selection.end.row - 1)
15018 } else {
15019 MultiBufferRow(selection.end.row)
15020 };
15021 last_toggled_row = Some(end_row);
15022
15023 if start_row > end_row {
15024 continue;
15025 }
15026
15027 // If the language has line comments, toggle those.
15028 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15029
15030 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15031 if ignore_indent {
15032 full_comment_prefixes = full_comment_prefixes
15033 .into_iter()
15034 .map(|s| Arc::from(s.trim_end()))
15035 .collect();
15036 }
15037
15038 if !full_comment_prefixes.is_empty() {
15039 let first_prefix = full_comment_prefixes
15040 .first()
15041 .expect("prefixes is non-empty");
15042 let prefix_trimmed_lengths = full_comment_prefixes
15043 .iter()
15044 .map(|p| p.trim_end_matches(' ').len())
15045 .collect::<SmallVec<[usize; 4]>>();
15046
15047 let mut all_selection_lines_are_comments = true;
15048
15049 for row in start_row.0..=end_row.0 {
15050 let row = MultiBufferRow(row);
15051 if start_row < end_row && snapshot.is_line_blank(row) {
15052 continue;
15053 }
15054
15055 let prefix_range = full_comment_prefixes
15056 .iter()
15057 .zip(prefix_trimmed_lengths.iter().copied())
15058 .map(|(prefix, trimmed_prefix_len)| {
15059 comment_prefix_range(
15060 snapshot.deref(),
15061 row,
15062 &prefix[..trimmed_prefix_len],
15063 &prefix[trimmed_prefix_len..],
15064 ignore_indent,
15065 )
15066 })
15067 .max_by_key(|range| range.end.column - range.start.column)
15068 .expect("prefixes is non-empty");
15069
15070 if prefix_range.is_empty() {
15071 all_selection_lines_are_comments = false;
15072 }
15073
15074 selection_edit_ranges.push(prefix_range);
15075 }
15076
15077 if all_selection_lines_are_comments {
15078 edits.extend(
15079 selection_edit_ranges
15080 .iter()
15081 .cloned()
15082 .map(|range| (range, empty_str.clone())),
15083 );
15084 } else {
15085 let min_column = selection_edit_ranges
15086 .iter()
15087 .map(|range| range.start.column)
15088 .min()
15089 .unwrap_or(0);
15090 edits.extend(selection_edit_ranges.iter().map(|range| {
15091 let position = Point::new(range.start.row, min_column);
15092 (position..position, first_prefix.clone())
15093 }));
15094 }
15095 } else if let Some(BlockCommentConfig {
15096 start: full_comment_prefix,
15097 end: comment_suffix,
15098 ..
15099 }) = language.block_comment()
15100 {
15101 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15102 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15103 let prefix_range = comment_prefix_range(
15104 snapshot.deref(),
15105 start_row,
15106 comment_prefix,
15107 comment_prefix_whitespace,
15108 ignore_indent,
15109 );
15110 let suffix_range = comment_suffix_range(
15111 snapshot.deref(),
15112 end_row,
15113 comment_suffix.trim_start_matches(' '),
15114 comment_suffix.starts_with(' '),
15115 );
15116
15117 if prefix_range.is_empty() || suffix_range.is_empty() {
15118 edits.push((
15119 prefix_range.start..prefix_range.start,
15120 full_comment_prefix.clone(),
15121 ));
15122 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15123 suffixes_inserted.push((end_row, comment_suffix.len()));
15124 } else {
15125 edits.push((prefix_range, empty_str.clone()));
15126 edits.push((suffix_range, empty_str.clone()));
15127 }
15128 } else {
15129 continue;
15130 }
15131 }
15132
15133 drop(snapshot);
15134 this.buffer.update(cx, |buffer, cx| {
15135 buffer.edit(edits, None, cx);
15136 });
15137
15138 // Adjust selections so that they end before any comment suffixes that
15139 // were inserted.
15140 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15141 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15142 let snapshot = this.buffer.read(cx).read(cx);
15143 for selection in &mut selections {
15144 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15145 match row.cmp(&MultiBufferRow(selection.end.row)) {
15146 Ordering::Less => {
15147 suffixes_inserted.next();
15148 continue;
15149 }
15150 Ordering::Greater => break,
15151 Ordering::Equal => {
15152 if selection.end.column == snapshot.line_len(row) {
15153 if selection.is_empty() {
15154 selection.start.column -= suffix_len as u32;
15155 }
15156 selection.end.column -= suffix_len as u32;
15157 }
15158 break;
15159 }
15160 }
15161 }
15162 }
15163
15164 drop(snapshot);
15165 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15166
15167 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15168 let selections_on_single_row = selections.windows(2).all(|selections| {
15169 selections[0].start.row == selections[1].start.row
15170 && selections[0].end.row == selections[1].end.row
15171 && selections[0].start.row == selections[0].end.row
15172 });
15173 let selections_selecting = selections
15174 .iter()
15175 .any(|selection| selection.start != selection.end);
15176 let advance_downwards = action.advance_downwards
15177 && selections_on_single_row
15178 && !selections_selecting
15179 && !matches!(this.mode, EditorMode::SingleLine);
15180
15181 if advance_downwards {
15182 let snapshot = this.buffer.read(cx).snapshot(cx);
15183
15184 this.change_selections(Default::default(), window, cx, |s| {
15185 s.move_cursors_with(|display_snapshot, display_point, _| {
15186 let mut point = display_point.to_point(display_snapshot);
15187 point.row += 1;
15188 point = snapshot.clip_point(point, Bias::Left);
15189 let display_point = point.to_display_point(display_snapshot);
15190 let goal = SelectionGoal::HorizontalPosition(
15191 display_snapshot
15192 .x_for_display_point(display_point, text_layout_details)
15193 .into(),
15194 );
15195 (display_point, goal)
15196 })
15197 });
15198 }
15199 });
15200 }
15201
15202 pub fn select_enclosing_symbol(
15203 &mut self,
15204 _: &SelectEnclosingSymbol,
15205 window: &mut Window,
15206 cx: &mut Context<Self>,
15207 ) {
15208 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15209
15210 let buffer = self.buffer.read(cx).snapshot(cx);
15211 let old_selections = self
15212 .selections
15213 .all::<usize>(&self.display_snapshot(cx))
15214 .into_boxed_slice();
15215
15216 fn update_selection(
15217 selection: &Selection<usize>,
15218 buffer_snap: &MultiBufferSnapshot,
15219 ) -> Option<Selection<usize>> {
15220 let cursor = selection.head();
15221 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15222 for symbol in symbols.iter().rev() {
15223 let start = symbol.range.start.to_offset(buffer_snap);
15224 let end = symbol.range.end.to_offset(buffer_snap);
15225 let new_range = start..end;
15226 if start < selection.start || end > selection.end {
15227 return Some(Selection {
15228 id: selection.id,
15229 start: new_range.start,
15230 end: new_range.end,
15231 goal: SelectionGoal::None,
15232 reversed: selection.reversed,
15233 });
15234 }
15235 }
15236 None
15237 }
15238
15239 let mut selected_larger_symbol = false;
15240 let new_selections = old_selections
15241 .iter()
15242 .map(|selection| match update_selection(selection, &buffer) {
15243 Some(new_selection) => {
15244 if new_selection.range() != selection.range() {
15245 selected_larger_symbol = true;
15246 }
15247 new_selection
15248 }
15249 None => selection.clone(),
15250 })
15251 .collect::<Vec<_>>();
15252
15253 if selected_larger_symbol {
15254 self.change_selections(Default::default(), window, cx, |s| {
15255 s.select(new_selections);
15256 });
15257 }
15258 }
15259
15260 pub fn select_larger_syntax_node(
15261 &mut self,
15262 _: &SelectLargerSyntaxNode,
15263 window: &mut Window,
15264 cx: &mut Context<Self>,
15265 ) {
15266 let Some(visible_row_count) = self.visible_row_count() else {
15267 return;
15268 };
15269 let old_selections: Box<[_]> = self
15270 .selections
15271 .all::<usize>(&self.display_snapshot(cx))
15272 .into();
15273 if old_selections.is_empty() {
15274 return;
15275 }
15276
15277 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15278
15279 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15280 let buffer = self.buffer.read(cx).snapshot(cx);
15281
15282 let mut selected_larger_node = false;
15283 let mut new_selections = old_selections
15284 .iter()
15285 .map(|selection| {
15286 let old_range = selection.start..selection.end;
15287
15288 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15289 // manually select word at selection
15290 if ["string_content", "inline"].contains(&node.kind()) {
15291 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15292 // ignore if word is already selected
15293 if !word_range.is_empty() && old_range != word_range {
15294 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15295 // only select word if start and end point belongs to same word
15296 if word_range == last_word_range {
15297 selected_larger_node = true;
15298 return Selection {
15299 id: selection.id,
15300 start: word_range.start,
15301 end: word_range.end,
15302 goal: SelectionGoal::None,
15303 reversed: selection.reversed,
15304 };
15305 }
15306 }
15307 }
15308 }
15309
15310 let mut new_range = old_range.clone();
15311 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15312 new_range = range;
15313 if !node.is_named() {
15314 continue;
15315 }
15316 if !display_map.intersects_fold(new_range.start)
15317 && !display_map.intersects_fold(new_range.end)
15318 {
15319 break;
15320 }
15321 }
15322
15323 selected_larger_node |= new_range != old_range;
15324 Selection {
15325 id: selection.id,
15326 start: new_range.start,
15327 end: new_range.end,
15328 goal: SelectionGoal::None,
15329 reversed: selection.reversed,
15330 }
15331 })
15332 .collect::<Vec<_>>();
15333
15334 if !selected_larger_node {
15335 return; // don't put this call in the history
15336 }
15337
15338 // scroll based on transformation done to the last selection created by the user
15339 let (last_old, last_new) = old_selections
15340 .last()
15341 .zip(new_selections.last().cloned())
15342 .expect("old_selections isn't empty");
15343
15344 // revert selection
15345 let is_selection_reversed = {
15346 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15347 new_selections.last_mut().expect("checked above").reversed =
15348 should_newest_selection_be_reversed;
15349 should_newest_selection_be_reversed
15350 };
15351
15352 if selected_larger_node {
15353 self.select_syntax_node_history.disable_clearing = true;
15354 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15355 s.select(new_selections.clone());
15356 });
15357 self.select_syntax_node_history.disable_clearing = false;
15358 }
15359
15360 let start_row = last_new.start.to_display_point(&display_map).row().0;
15361 let end_row = last_new.end.to_display_point(&display_map).row().0;
15362 let selection_height = end_row - start_row + 1;
15363 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15364
15365 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15366 let scroll_behavior = if fits_on_the_screen {
15367 self.request_autoscroll(Autoscroll::fit(), cx);
15368 SelectSyntaxNodeScrollBehavior::FitSelection
15369 } else if is_selection_reversed {
15370 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15371 SelectSyntaxNodeScrollBehavior::CursorTop
15372 } else {
15373 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15374 SelectSyntaxNodeScrollBehavior::CursorBottom
15375 };
15376
15377 self.select_syntax_node_history.push((
15378 old_selections,
15379 scroll_behavior,
15380 is_selection_reversed,
15381 ));
15382 }
15383
15384 pub fn select_smaller_syntax_node(
15385 &mut self,
15386 _: &SelectSmallerSyntaxNode,
15387 window: &mut Window,
15388 cx: &mut Context<Self>,
15389 ) {
15390 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15391
15392 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15393 self.select_syntax_node_history.pop()
15394 {
15395 if let Some(selection) = selections.last_mut() {
15396 selection.reversed = is_selection_reversed;
15397 }
15398
15399 self.select_syntax_node_history.disable_clearing = true;
15400 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15401 s.select(selections.to_vec());
15402 });
15403 self.select_syntax_node_history.disable_clearing = false;
15404
15405 match scroll_behavior {
15406 SelectSyntaxNodeScrollBehavior::CursorTop => {
15407 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15408 }
15409 SelectSyntaxNodeScrollBehavior::FitSelection => {
15410 self.request_autoscroll(Autoscroll::fit(), cx);
15411 }
15412 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15413 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15414 }
15415 }
15416 }
15417 }
15418
15419 pub fn unwrap_syntax_node(
15420 &mut self,
15421 _: &UnwrapSyntaxNode,
15422 window: &mut Window,
15423 cx: &mut Context<Self>,
15424 ) {
15425 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15426
15427 let buffer = self.buffer.read(cx).snapshot(cx);
15428 let selections = self
15429 .selections
15430 .all::<usize>(&self.display_snapshot(cx))
15431 .into_iter()
15432 // subtracting the offset requires sorting
15433 .sorted_by_key(|i| i.start);
15434
15435 let full_edits = selections
15436 .into_iter()
15437 .filter_map(|selection| {
15438 let child = if selection.is_empty()
15439 && let Some((_, ancestor_range)) =
15440 buffer.syntax_ancestor(selection.start..selection.end)
15441 {
15442 ancestor_range
15443 } else {
15444 selection.range()
15445 };
15446
15447 let mut parent = child.clone();
15448 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15449 parent = ancestor_range;
15450 if parent.start < child.start || parent.end > child.end {
15451 break;
15452 }
15453 }
15454
15455 if parent == child {
15456 return None;
15457 }
15458 let text = buffer.text_for_range(child).collect::<String>();
15459 Some((selection.id, parent, text))
15460 })
15461 .collect::<Vec<_>>();
15462 if full_edits.is_empty() {
15463 return;
15464 }
15465
15466 self.transact(window, cx, |this, window, cx| {
15467 this.buffer.update(cx, |buffer, cx| {
15468 buffer.edit(
15469 full_edits
15470 .iter()
15471 .map(|(_, p, t)| (p.clone(), t.clone()))
15472 .collect::<Vec<_>>(),
15473 None,
15474 cx,
15475 );
15476 });
15477 this.change_selections(Default::default(), window, cx, |s| {
15478 let mut offset = 0;
15479 let mut selections = vec![];
15480 for (id, parent, text) in full_edits {
15481 let start = parent.start - offset;
15482 offset += parent.len() - text.len();
15483 selections.push(Selection {
15484 id,
15485 start,
15486 end: start + text.len(),
15487 reversed: false,
15488 goal: Default::default(),
15489 });
15490 }
15491 s.select(selections);
15492 });
15493 });
15494 }
15495
15496 pub fn select_next_syntax_node(
15497 &mut self,
15498 _: &SelectNextSyntaxNode,
15499 window: &mut Window,
15500 cx: &mut Context<Self>,
15501 ) {
15502 let old_selections: Box<[_]> = self
15503 .selections
15504 .all::<usize>(&self.display_snapshot(cx))
15505 .into();
15506 if old_selections.is_empty() {
15507 return;
15508 }
15509
15510 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15511
15512 let buffer = self.buffer.read(cx).snapshot(cx);
15513 let mut selected_sibling = false;
15514
15515 let new_selections = old_selections
15516 .iter()
15517 .map(|selection| {
15518 let old_range = selection.start..selection.end;
15519
15520 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15521 let new_range = node.byte_range();
15522 selected_sibling = true;
15523 Selection {
15524 id: selection.id,
15525 start: new_range.start,
15526 end: new_range.end,
15527 goal: SelectionGoal::None,
15528 reversed: selection.reversed,
15529 }
15530 } else {
15531 selection.clone()
15532 }
15533 })
15534 .collect::<Vec<_>>();
15535
15536 if selected_sibling {
15537 self.change_selections(
15538 SelectionEffects::scroll(Autoscroll::fit()),
15539 window,
15540 cx,
15541 |s| {
15542 s.select(new_selections);
15543 },
15544 );
15545 }
15546 }
15547
15548 pub fn select_prev_syntax_node(
15549 &mut self,
15550 _: &SelectPreviousSyntaxNode,
15551 window: &mut Window,
15552 cx: &mut Context<Self>,
15553 ) {
15554 let old_selections: Box<[_]> = self
15555 .selections
15556 .all::<usize>(&self.display_snapshot(cx))
15557 .into();
15558 if old_selections.is_empty() {
15559 return;
15560 }
15561
15562 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15563
15564 let buffer = self.buffer.read(cx).snapshot(cx);
15565 let mut selected_sibling = false;
15566
15567 let new_selections = old_selections
15568 .iter()
15569 .map(|selection| {
15570 let old_range = selection.start..selection.end;
15571
15572 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15573 let new_range = node.byte_range();
15574 selected_sibling = true;
15575 Selection {
15576 id: selection.id,
15577 start: new_range.start,
15578 end: new_range.end,
15579 goal: SelectionGoal::None,
15580 reversed: selection.reversed,
15581 }
15582 } else {
15583 selection.clone()
15584 }
15585 })
15586 .collect::<Vec<_>>();
15587
15588 if selected_sibling {
15589 self.change_selections(
15590 SelectionEffects::scroll(Autoscroll::fit()),
15591 window,
15592 cx,
15593 |s| {
15594 s.select(new_selections);
15595 },
15596 );
15597 }
15598 }
15599
15600 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15601 if !EditorSettings::get_global(cx).gutter.runnables {
15602 self.clear_tasks();
15603 return Task::ready(());
15604 }
15605 let project = self.project().map(Entity::downgrade);
15606 let task_sources = self.lsp_task_sources(cx);
15607 let multi_buffer = self.buffer.downgrade();
15608 cx.spawn_in(window, async move |editor, cx| {
15609 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15610 let Some(project) = project.and_then(|p| p.upgrade()) else {
15611 return;
15612 };
15613 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15614 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15615 }) else {
15616 return;
15617 };
15618
15619 let hide_runnables = project
15620 .update(cx, |project, _| project.is_via_collab())
15621 .unwrap_or(true);
15622 if hide_runnables {
15623 return;
15624 }
15625 let new_rows =
15626 cx.background_spawn({
15627 let snapshot = display_snapshot.clone();
15628 async move {
15629 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15630 }
15631 })
15632 .await;
15633 let Ok(lsp_tasks) =
15634 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15635 else {
15636 return;
15637 };
15638 let lsp_tasks = lsp_tasks.await;
15639
15640 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15641 lsp_tasks
15642 .into_iter()
15643 .flat_map(|(kind, tasks)| {
15644 tasks.into_iter().filter_map(move |(location, task)| {
15645 Some((kind.clone(), location?, task))
15646 })
15647 })
15648 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15649 let buffer = location.target.buffer;
15650 let buffer_snapshot = buffer.read(cx).snapshot();
15651 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15652 |(excerpt_id, snapshot, _)| {
15653 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15654 display_snapshot
15655 .buffer_snapshot()
15656 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15657 } else {
15658 None
15659 }
15660 },
15661 );
15662 if let Some(offset) = offset {
15663 let task_buffer_range =
15664 location.target.range.to_point(&buffer_snapshot);
15665 let context_buffer_range =
15666 task_buffer_range.to_offset(&buffer_snapshot);
15667 let context_range = BufferOffset(context_buffer_range.start)
15668 ..BufferOffset(context_buffer_range.end);
15669
15670 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15671 .or_insert_with(|| RunnableTasks {
15672 templates: Vec::new(),
15673 offset,
15674 column: task_buffer_range.start.column,
15675 extra_variables: HashMap::default(),
15676 context_range,
15677 })
15678 .templates
15679 .push((kind, task.original_task().clone()));
15680 }
15681
15682 acc
15683 })
15684 }) else {
15685 return;
15686 };
15687
15688 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15689 buffer.language_settings(cx).tasks.prefer_lsp
15690 }) else {
15691 return;
15692 };
15693
15694 let rows = Self::runnable_rows(
15695 project,
15696 display_snapshot,
15697 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15698 new_rows,
15699 cx.clone(),
15700 )
15701 .await;
15702 editor
15703 .update(cx, |editor, _| {
15704 editor.clear_tasks();
15705 for (key, mut value) in rows {
15706 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15707 value.templates.extend(lsp_tasks.templates);
15708 }
15709
15710 editor.insert_tasks(key, value);
15711 }
15712 for (key, value) in lsp_tasks_by_rows {
15713 editor.insert_tasks(key, value);
15714 }
15715 })
15716 .ok();
15717 })
15718 }
15719 fn fetch_runnable_ranges(
15720 snapshot: &DisplaySnapshot,
15721 range: Range<Anchor>,
15722 ) -> Vec<language::RunnableRange> {
15723 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15724 }
15725
15726 fn runnable_rows(
15727 project: Entity<Project>,
15728 snapshot: DisplaySnapshot,
15729 prefer_lsp: bool,
15730 runnable_ranges: Vec<RunnableRange>,
15731 cx: AsyncWindowContext,
15732 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15733 cx.spawn(async move |cx| {
15734 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15735 for mut runnable in runnable_ranges {
15736 let Some(tasks) = cx
15737 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15738 .ok()
15739 else {
15740 continue;
15741 };
15742 let mut tasks = tasks.await;
15743
15744 if prefer_lsp {
15745 tasks.retain(|(task_kind, _)| {
15746 !matches!(task_kind, TaskSourceKind::Language { .. })
15747 });
15748 }
15749 if tasks.is_empty() {
15750 continue;
15751 }
15752
15753 let point = runnable
15754 .run_range
15755 .start
15756 .to_point(&snapshot.buffer_snapshot());
15757 let Some(row) = snapshot
15758 .buffer_snapshot()
15759 .buffer_line_for_row(MultiBufferRow(point.row))
15760 .map(|(_, range)| range.start.row)
15761 else {
15762 continue;
15763 };
15764
15765 let context_range =
15766 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15767 runnable_rows.push((
15768 (runnable.buffer_id, row),
15769 RunnableTasks {
15770 templates: tasks,
15771 offset: snapshot
15772 .buffer_snapshot()
15773 .anchor_before(runnable.run_range.start),
15774 context_range,
15775 column: point.column,
15776 extra_variables: runnable.extra_captures,
15777 },
15778 ));
15779 }
15780 runnable_rows
15781 })
15782 }
15783
15784 fn templates_with_tags(
15785 project: &Entity<Project>,
15786 runnable: &mut Runnable,
15787 cx: &mut App,
15788 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15789 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15790 let (worktree_id, file) = project
15791 .buffer_for_id(runnable.buffer, cx)
15792 .and_then(|buffer| buffer.read(cx).file())
15793 .map(|file| (file.worktree_id(cx), file.clone()))
15794 .unzip();
15795
15796 (
15797 project.task_store().read(cx).task_inventory().cloned(),
15798 worktree_id,
15799 file,
15800 )
15801 });
15802
15803 let tags = mem::take(&mut runnable.tags);
15804 let language = runnable.language.clone();
15805 cx.spawn(async move |cx| {
15806 let mut templates_with_tags = Vec::new();
15807 if let Some(inventory) = inventory {
15808 for RunnableTag(tag) in tags {
15809 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15810 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15811 }) else {
15812 return templates_with_tags;
15813 };
15814 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15815 move |(_, template)| {
15816 template.tags.iter().any(|source_tag| source_tag == &tag)
15817 },
15818 ));
15819 }
15820 }
15821 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15822
15823 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15824 // Strongest source wins; if we have worktree tag binding, prefer that to
15825 // global and language bindings;
15826 // if we have a global binding, prefer that to language binding.
15827 let first_mismatch = templates_with_tags
15828 .iter()
15829 .position(|(tag_source, _)| tag_source != leading_tag_source);
15830 if let Some(index) = first_mismatch {
15831 templates_with_tags.truncate(index);
15832 }
15833 }
15834
15835 templates_with_tags
15836 })
15837 }
15838
15839 pub fn move_to_enclosing_bracket(
15840 &mut self,
15841 _: &MoveToEnclosingBracket,
15842 window: &mut Window,
15843 cx: &mut Context<Self>,
15844 ) {
15845 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15846 self.change_selections(Default::default(), window, cx, |s| {
15847 s.move_offsets_with(|snapshot, selection| {
15848 let Some(enclosing_bracket_ranges) =
15849 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15850 else {
15851 return;
15852 };
15853
15854 let mut best_length = usize::MAX;
15855 let mut best_inside = false;
15856 let mut best_in_bracket_range = false;
15857 let mut best_destination = None;
15858 for (open, close) in enclosing_bracket_ranges {
15859 let close = close.to_inclusive();
15860 let length = close.end() - open.start;
15861 let inside = selection.start >= open.end && selection.end <= *close.start();
15862 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15863 || close.contains(&selection.head());
15864
15865 // If best is next to a bracket and current isn't, skip
15866 if !in_bracket_range && best_in_bracket_range {
15867 continue;
15868 }
15869
15870 // Prefer smaller lengths unless best is inside and current isn't
15871 if length > best_length && (best_inside || !inside) {
15872 continue;
15873 }
15874
15875 best_length = length;
15876 best_inside = inside;
15877 best_in_bracket_range = in_bracket_range;
15878 best_destination = Some(
15879 if close.contains(&selection.start) && close.contains(&selection.end) {
15880 if inside { open.end } else { open.start }
15881 } else if inside {
15882 *close.start()
15883 } else {
15884 *close.end()
15885 },
15886 );
15887 }
15888
15889 if let Some(destination) = best_destination {
15890 selection.collapse_to(destination, SelectionGoal::None);
15891 }
15892 })
15893 });
15894 }
15895
15896 pub fn undo_selection(
15897 &mut self,
15898 _: &UndoSelection,
15899 window: &mut Window,
15900 cx: &mut Context<Self>,
15901 ) {
15902 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15903 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15904 self.selection_history.mode = SelectionHistoryMode::Undoing;
15905 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15906 this.end_selection(window, cx);
15907 this.change_selections(
15908 SelectionEffects::scroll(Autoscroll::newest()),
15909 window,
15910 cx,
15911 |s| s.select_anchors(entry.selections.to_vec()),
15912 );
15913 });
15914 self.selection_history.mode = SelectionHistoryMode::Normal;
15915
15916 self.select_next_state = entry.select_next_state;
15917 self.select_prev_state = entry.select_prev_state;
15918 self.add_selections_state = entry.add_selections_state;
15919 }
15920 }
15921
15922 pub fn redo_selection(
15923 &mut self,
15924 _: &RedoSelection,
15925 window: &mut Window,
15926 cx: &mut Context<Self>,
15927 ) {
15928 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15929 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15930 self.selection_history.mode = SelectionHistoryMode::Redoing;
15931 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15932 this.end_selection(window, cx);
15933 this.change_selections(
15934 SelectionEffects::scroll(Autoscroll::newest()),
15935 window,
15936 cx,
15937 |s| s.select_anchors(entry.selections.to_vec()),
15938 );
15939 });
15940 self.selection_history.mode = SelectionHistoryMode::Normal;
15941
15942 self.select_next_state = entry.select_next_state;
15943 self.select_prev_state = entry.select_prev_state;
15944 self.add_selections_state = entry.add_selections_state;
15945 }
15946 }
15947
15948 pub fn expand_excerpts(
15949 &mut self,
15950 action: &ExpandExcerpts,
15951 _: &mut Window,
15952 cx: &mut Context<Self>,
15953 ) {
15954 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15955 }
15956
15957 pub fn expand_excerpts_down(
15958 &mut self,
15959 action: &ExpandExcerptsDown,
15960 _: &mut Window,
15961 cx: &mut Context<Self>,
15962 ) {
15963 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15964 }
15965
15966 pub fn expand_excerpts_up(
15967 &mut self,
15968 action: &ExpandExcerptsUp,
15969 _: &mut Window,
15970 cx: &mut Context<Self>,
15971 ) {
15972 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15973 }
15974
15975 pub fn expand_excerpts_for_direction(
15976 &mut self,
15977 lines: u32,
15978 direction: ExpandExcerptDirection,
15979
15980 cx: &mut Context<Self>,
15981 ) {
15982 let selections = self.selections.disjoint_anchors_arc();
15983
15984 let lines = if lines == 0 {
15985 EditorSettings::get_global(cx).expand_excerpt_lines
15986 } else {
15987 lines
15988 };
15989
15990 self.buffer.update(cx, |buffer, cx| {
15991 let snapshot = buffer.snapshot(cx);
15992 let mut excerpt_ids = selections
15993 .iter()
15994 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15995 .collect::<Vec<_>>();
15996 excerpt_ids.sort();
15997 excerpt_ids.dedup();
15998 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15999 })
16000 }
16001
16002 pub fn expand_excerpt(
16003 &mut self,
16004 excerpt: ExcerptId,
16005 direction: ExpandExcerptDirection,
16006 window: &mut Window,
16007 cx: &mut Context<Self>,
16008 ) {
16009 let current_scroll_position = self.scroll_position(cx);
16010 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16011 let mut should_scroll_up = false;
16012
16013 if direction == ExpandExcerptDirection::Down {
16014 let multi_buffer = self.buffer.read(cx);
16015 let snapshot = multi_buffer.snapshot(cx);
16016 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16017 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16018 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16019 {
16020 let buffer_snapshot = buffer.read(cx).snapshot();
16021 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16022 let last_row = buffer_snapshot.max_point().row;
16023 let lines_below = last_row.saturating_sub(excerpt_end_row);
16024 should_scroll_up = lines_below >= lines_to_expand;
16025 }
16026 }
16027
16028 self.buffer.update(cx, |buffer, cx| {
16029 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16030 });
16031
16032 if should_scroll_up {
16033 let new_scroll_position =
16034 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as ScrollOffset);
16035 self.set_scroll_position(new_scroll_position, window, cx);
16036 }
16037 }
16038
16039 pub fn go_to_singleton_buffer_point(
16040 &mut self,
16041 point: Point,
16042 window: &mut Window,
16043 cx: &mut Context<Self>,
16044 ) {
16045 self.go_to_singleton_buffer_range(point..point, window, cx);
16046 }
16047
16048 pub fn go_to_singleton_buffer_range(
16049 &mut self,
16050 range: Range<Point>,
16051 window: &mut Window,
16052 cx: &mut Context<Self>,
16053 ) {
16054 let multibuffer = self.buffer().read(cx);
16055 let Some(buffer) = multibuffer.as_singleton() else {
16056 return;
16057 };
16058 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16059 return;
16060 };
16061 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16062 return;
16063 };
16064 self.change_selections(
16065 SelectionEffects::default().nav_history(true),
16066 window,
16067 cx,
16068 |s| s.select_anchor_ranges([start..end]),
16069 );
16070 }
16071
16072 pub fn go_to_diagnostic(
16073 &mut self,
16074 action: &GoToDiagnostic,
16075 window: &mut Window,
16076 cx: &mut Context<Self>,
16077 ) {
16078 if !self.diagnostics_enabled() {
16079 return;
16080 }
16081 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16082 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16083 }
16084
16085 pub fn go_to_prev_diagnostic(
16086 &mut self,
16087 action: &GoToPreviousDiagnostic,
16088 window: &mut Window,
16089 cx: &mut Context<Self>,
16090 ) {
16091 if !self.diagnostics_enabled() {
16092 return;
16093 }
16094 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16095 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16096 }
16097
16098 pub fn go_to_diagnostic_impl(
16099 &mut self,
16100 direction: Direction,
16101 severity: GoToDiagnosticSeverityFilter,
16102 window: &mut Window,
16103 cx: &mut Context<Self>,
16104 ) {
16105 let buffer = self.buffer.read(cx).snapshot(cx);
16106 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16107
16108 let mut active_group_id = None;
16109 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16110 && active_group.active_range.start.to_offset(&buffer) == selection.start
16111 {
16112 active_group_id = Some(active_group.group_id);
16113 }
16114
16115 fn filtered<'a>(
16116 snapshot: EditorSnapshot,
16117 severity: GoToDiagnosticSeverityFilter,
16118 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
16119 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
16120 diagnostics
16121 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16122 .filter(|entry| entry.range.start != entry.range.end)
16123 .filter(|entry| !entry.diagnostic.is_unnecessary)
16124 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
16125 }
16126
16127 let snapshot = self.snapshot(window, cx);
16128 let before = filtered(
16129 snapshot.clone(),
16130 severity,
16131 buffer
16132 .diagnostics_in_range(0..selection.start)
16133 .filter(|entry| entry.range.start <= selection.start),
16134 );
16135 let after = filtered(
16136 snapshot,
16137 severity,
16138 buffer
16139 .diagnostics_in_range(selection.start..buffer.len())
16140 .filter(|entry| entry.range.start >= selection.start),
16141 );
16142
16143 let mut found: Option<DiagnosticEntryRef<usize>> = None;
16144 if direction == Direction::Prev {
16145 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16146 {
16147 for diagnostic in prev_diagnostics.into_iter().rev() {
16148 if diagnostic.range.start != selection.start
16149 || active_group_id
16150 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16151 {
16152 found = Some(diagnostic);
16153 break 'outer;
16154 }
16155 }
16156 }
16157 } else {
16158 for diagnostic in after.chain(before) {
16159 if diagnostic.range.start != selection.start
16160 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16161 {
16162 found = Some(diagnostic);
16163 break;
16164 }
16165 }
16166 }
16167 let Some(next_diagnostic) = found else {
16168 return;
16169 };
16170
16171 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16172 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16173 return;
16174 };
16175 self.change_selections(Default::default(), window, cx, |s| {
16176 s.select_ranges(vec![
16177 next_diagnostic.range.start..next_diagnostic.range.start,
16178 ])
16179 });
16180 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16181 self.refresh_edit_prediction(false, true, window, cx);
16182 }
16183
16184 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16185 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16186 let snapshot = self.snapshot(window, cx);
16187 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16188 self.go_to_hunk_before_or_after_position(
16189 &snapshot,
16190 selection.head(),
16191 Direction::Next,
16192 window,
16193 cx,
16194 );
16195 }
16196
16197 pub fn go_to_hunk_before_or_after_position(
16198 &mut self,
16199 snapshot: &EditorSnapshot,
16200 position: Point,
16201 direction: Direction,
16202 window: &mut Window,
16203 cx: &mut Context<Editor>,
16204 ) {
16205 let row = if direction == Direction::Next {
16206 self.hunk_after_position(snapshot, position)
16207 .map(|hunk| hunk.row_range.start)
16208 } else {
16209 self.hunk_before_position(snapshot, position)
16210 };
16211
16212 if let Some(row) = row {
16213 let destination = Point::new(row.0, 0);
16214 let autoscroll = Autoscroll::center();
16215
16216 self.unfold_ranges(&[destination..destination], false, false, cx);
16217 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16218 s.select_ranges([destination..destination]);
16219 });
16220 }
16221 }
16222
16223 fn hunk_after_position(
16224 &mut self,
16225 snapshot: &EditorSnapshot,
16226 position: Point,
16227 ) -> Option<MultiBufferDiffHunk> {
16228 snapshot
16229 .buffer_snapshot()
16230 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16231 .find(|hunk| hunk.row_range.start.0 > position.row)
16232 .or_else(|| {
16233 snapshot
16234 .buffer_snapshot()
16235 .diff_hunks_in_range(Point::zero()..position)
16236 .find(|hunk| hunk.row_range.end.0 < position.row)
16237 })
16238 }
16239
16240 fn go_to_prev_hunk(
16241 &mut self,
16242 _: &GoToPreviousHunk,
16243 window: &mut Window,
16244 cx: &mut Context<Self>,
16245 ) {
16246 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16247 let snapshot = self.snapshot(window, cx);
16248 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16249 self.go_to_hunk_before_or_after_position(
16250 &snapshot,
16251 selection.head(),
16252 Direction::Prev,
16253 window,
16254 cx,
16255 );
16256 }
16257
16258 fn hunk_before_position(
16259 &mut self,
16260 snapshot: &EditorSnapshot,
16261 position: Point,
16262 ) -> Option<MultiBufferRow> {
16263 snapshot
16264 .buffer_snapshot()
16265 .diff_hunk_before(position)
16266 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16267 }
16268
16269 fn go_to_next_change(
16270 &mut self,
16271 _: &GoToNextChange,
16272 window: &mut Window,
16273 cx: &mut Context<Self>,
16274 ) {
16275 if let Some(selections) = self
16276 .change_list
16277 .next_change(1, Direction::Next)
16278 .map(|s| s.to_vec())
16279 {
16280 self.change_selections(Default::default(), window, cx, |s| {
16281 let map = s.display_map();
16282 s.select_display_ranges(selections.iter().map(|a| {
16283 let point = a.to_display_point(&map);
16284 point..point
16285 }))
16286 })
16287 }
16288 }
16289
16290 fn go_to_previous_change(
16291 &mut self,
16292 _: &GoToPreviousChange,
16293 window: &mut Window,
16294 cx: &mut Context<Self>,
16295 ) {
16296 if let Some(selections) = self
16297 .change_list
16298 .next_change(1, Direction::Prev)
16299 .map(|s| s.to_vec())
16300 {
16301 self.change_selections(Default::default(), window, cx, |s| {
16302 let map = s.display_map();
16303 s.select_display_ranges(selections.iter().map(|a| {
16304 let point = a.to_display_point(&map);
16305 point..point
16306 }))
16307 })
16308 }
16309 }
16310
16311 pub fn go_to_next_document_highlight(
16312 &mut self,
16313 _: &GoToNextDocumentHighlight,
16314 window: &mut Window,
16315 cx: &mut Context<Self>,
16316 ) {
16317 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16318 }
16319
16320 pub fn go_to_prev_document_highlight(
16321 &mut self,
16322 _: &GoToPreviousDocumentHighlight,
16323 window: &mut Window,
16324 cx: &mut Context<Self>,
16325 ) {
16326 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16327 }
16328
16329 pub fn go_to_document_highlight_before_or_after_position(
16330 &mut self,
16331 direction: Direction,
16332 window: &mut Window,
16333 cx: &mut Context<Editor>,
16334 ) {
16335 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16336 let snapshot = self.snapshot(window, cx);
16337 let buffer = &snapshot.buffer_snapshot();
16338 let position = self
16339 .selections
16340 .newest::<Point>(&snapshot.display_snapshot)
16341 .head();
16342 let anchor_position = buffer.anchor_after(position);
16343
16344 // Get all document highlights (both read and write)
16345 let mut all_highlights = Vec::new();
16346
16347 if let Some((_, read_highlights)) = self
16348 .background_highlights
16349 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16350 {
16351 all_highlights.extend(read_highlights.iter());
16352 }
16353
16354 if let Some((_, write_highlights)) = self
16355 .background_highlights
16356 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16357 {
16358 all_highlights.extend(write_highlights.iter());
16359 }
16360
16361 if all_highlights.is_empty() {
16362 return;
16363 }
16364
16365 // Sort highlights by position
16366 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16367
16368 let target_highlight = match direction {
16369 Direction::Next => {
16370 // Find the first highlight after the current position
16371 all_highlights
16372 .iter()
16373 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16374 }
16375 Direction::Prev => {
16376 // Find the last highlight before the current position
16377 all_highlights
16378 .iter()
16379 .rev()
16380 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16381 }
16382 };
16383
16384 if let Some(highlight) = target_highlight {
16385 let destination = highlight.start.to_point(buffer);
16386 let autoscroll = Autoscroll::center();
16387
16388 self.unfold_ranges(&[destination..destination], false, false, cx);
16389 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16390 s.select_ranges([destination..destination]);
16391 });
16392 }
16393 }
16394
16395 fn go_to_line<T: 'static>(
16396 &mut self,
16397 position: Anchor,
16398 highlight_color: Option<Hsla>,
16399 window: &mut Window,
16400 cx: &mut Context<Self>,
16401 ) {
16402 let snapshot = self.snapshot(window, cx).display_snapshot;
16403 let position = position.to_point(&snapshot.buffer_snapshot());
16404 let start = snapshot
16405 .buffer_snapshot()
16406 .clip_point(Point::new(position.row, 0), Bias::Left);
16407 let end = start + Point::new(1, 0);
16408 let start = snapshot.buffer_snapshot().anchor_before(start);
16409 let end = snapshot.buffer_snapshot().anchor_before(end);
16410
16411 self.highlight_rows::<T>(
16412 start..end,
16413 highlight_color
16414 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16415 Default::default(),
16416 cx,
16417 );
16418
16419 if self.buffer.read(cx).is_singleton() {
16420 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16421 }
16422 }
16423
16424 pub fn go_to_definition(
16425 &mut self,
16426 _: &GoToDefinition,
16427 window: &mut Window,
16428 cx: &mut Context<Self>,
16429 ) -> Task<Result<Navigated>> {
16430 let definition =
16431 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16432 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16433 cx.spawn_in(window, async move |editor, cx| {
16434 if definition.await? == Navigated::Yes {
16435 return Ok(Navigated::Yes);
16436 }
16437 match fallback_strategy {
16438 GoToDefinitionFallback::None => Ok(Navigated::No),
16439 GoToDefinitionFallback::FindAllReferences => {
16440 match editor.update_in(cx, |editor, window, cx| {
16441 editor.find_all_references(&FindAllReferences, window, cx)
16442 })? {
16443 Some(references) => references.await,
16444 None => Ok(Navigated::No),
16445 }
16446 }
16447 }
16448 })
16449 }
16450
16451 pub fn go_to_declaration(
16452 &mut self,
16453 _: &GoToDeclaration,
16454 window: &mut Window,
16455 cx: &mut Context<Self>,
16456 ) -> Task<Result<Navigated>> {
16457 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16458 }
16459
16460 pub fn go_to_declaration_split(
16461 &mut self,
16462 _: &GoToDeclaration,
16463 window: &mut Window,
16464 cx: &mut Context<Self>,
16465 ) -> Task<Result<Navigated>> {
16466 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16467 }
16468
16469 pub fn go_to_implementation(
16470 &mut self,
16471 _: &GoToImplementation,
16472 window: &mut Window,
16473 cx: &mut Context<Self>,
16474 ) -> Task<Result<Navigated>> {
16475 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16476 }
16477
16478 pub fn go_to_implementation_split(
16479 &mut self,
16480 _: &GoToImplementationSplit,
16481 window: &mut Window,
16482 cx: &mut Context<Self>,
16483 ) -> Task<Result<Navigated>> {
16484 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16485 }
16486
16487 pub fn go_to_type_definition(
16488 &mut self,
16489 _: &GoToTypeDefinition,
16490 window: &mut Window,
16491 cx: &mut Context<Self>,
16492 ) -> Task<Result<Navigated>> {
16493 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16494 }
16495
16496 pub fn go_to_definition_split(
16497 &mut self,
16498 _: &GoToDefinitionSplit,
16499 window: &mut Window,
16500 cx: &mut Context<Self>,
16501 ) -> Task<Result<Navigated>> {
16502 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16503 }
16504
16505 pub fn go_to_type_definition_split(
16506 &mut self,
16507 _: &GoToTypeDefinitionSplit,
16508 window: &mut Window,
16509 cx: &mut Context<Self>,
16510 ) -> Task<Result<Navigated>> {
16511 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16512 }
16513
16514 fn go_to_definition_of_kind(
16515 &mut self,
16516 kind: GotoDefinitionKind,
16517 split: bool,
16518 window: &mut Window,
16519 cx: &mut Context<Self>,
16520 ) -> Task<Result<Navigated>> {
16521 let Some(provider) = self.semantics_provider.clone() else {
16522 return Task::ready(Ok(Navigated::No));
16523 };
16524 let head = self
16525 .selections
16526 .newest::<usize>(&self.display_snapshot(cx))
16527 .head();
16528 let buffer = self.buffer.read(cx);
16529 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16530 return Task::ready(Ok(Navigated::No));
16531 };
16532 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16533 return Task::ready(Ok(Navigated::No));
16534 };
16535
16536 cx.spawn_in(window, async move |editor, cx| {
16537 let Some(definitions) = definitions.await? else {
16538 return Ok(Navigated::No);
16539 };
16540 let navigated = editor
16541 .update_in(cx, |editor, window, cx| {
16542 editor.navigate_to_hover_links(
16543 Some(kind),
16544 definitions
16545 .into_iter()
16546 .filter(|location| {
16547 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16548 })
16549 .map(HoverLink::Text)
16550 .collect::<Vec<_>>(),
16551 split,
16552 window,
16553 cx,
16554 )
16555 })?
16556 .await?;
16557 anyhow::Ok(navigated)
16558 })
16559 }
16560
16561 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16562 let selection = self.selections.newest_anchor();
16563 let head = selection.head();
16564 let tail = selection.tail();
16565
16566 let Some((buffer, start_position)) =
16567 self.buffer.read(cx).text_anchor_for_position(head, cx)
16568 else {
16569 return;
16570 };
16571
16572 let end_position = if head != tail {
16573 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16574 return;
16575 };
16576 Some(pos)
16577 } else {
16578 None
16579 };
16580
16581 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16582 let url = if let Some(end_pos) = end_position {
16583 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16584 } else {
16585 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16586 };
16587
16588 if let Some(url) = url {
16589 cx.update(|window, cx| {
16590 if parse_zed_link(&url, cx).is_some() {
16591 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16592 } else {
16593 cx.open_url(&url);
16594 }
16595 })?;
16596 }
16597
16598 anyhow::Ok(())
16599 });
16600
16601 url_finder.detach();
16602 }
16603
16604 pub fn open_selected_filename(
16605 &mut self,
16606 _: &OpenSelectedFilename,
16607 window: &mut Window,
16608 cx: &mut Context<Self>,
16609 ) {
16610 let Some(workspace) = self.workspace() else {
16611 return;
16612 };
16613
16614 let position = self.selections.newest_anchor().head();
16615
16616 let Some((buffer, buffer_position)) =
16617 self.buffer.read(cx).text_anchor_for_position(position, cx)
16618 else {
16619 return;
16620 };
16621
16622 let project = self.project.clone();
16623
16624 cx.spawn_in(window, async move |_, cx| {
16625 let result = find_file(&buffer, project, buffer_position, cx).await;
16626
16627 if let Some((_, path)) = result {
16628 workspace
16629 .update_in(cx, |workspace, window, cx| {
16630 workspace.open_resolved_path(path, window, cx)
16631 })?
16632 .await?;
16633 }
16634 anyhow::Ok(())
16635 })
16636 .detach();
16637 }
16638
16639 pub(crate) fn navigate_to_hover_links(
16640 &mut self,
16641 kind: Option<GotoDefinitionKind>,
16642 definitions: Vec<HoverLink>,
16643 split: bool,
16644 window: &mut Window,
16645 cx: &mut Context<Editor>,
16646 ) -> Task<Result<Navigated>> {
16647 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16648 let mut first_url_or_file = None;
16649 let definitions: Vec<_> = definitions
16650 .into_iter()
16651 .filter_map(|def| match def {
16652 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16653 HoverLink::InlayHint(lsp_location, server_id) => {
16654 let computation =
16655 self.compute_target_location(lsp_location, server_id, window, cx);
16656 Some(cx.background_spawn(computation))
16657 }
16658 HoverLink::Url(url) => {
16659 first_url_or_file = Some(Either::Left(url));
16660 None
16661 }
16662 HoverLink::File(path) => {
16663 first_url_or_file = Some(Either::Right(path));
16664 None
16665 }
16666 })
16667 .collect();
16668
16669 let workspace = self.workspace();
16670
16671 cx.spawn_in(window, async move |editor, cx| {
16672 let locations: Vec<Location> = future::join_all(definitions)
16673 .await
16674 .into_iter()
16675 .filter_map(|location| location.transpose())
16676 .collect::<Result<_>>()
16677 .context("location tasks")?;
16678 let mut locations = cx.update(|_, cx| {
16679 locations
16680 .into_iter()
16681 .map(|location| {
16682 let buffer = location.buffer.read(cx);
16683 (location.buffer, location.range.to_point(buffer))
16684 })
16685 .into_group_map()
16686 })?;
16687 let mut num_locations = 0;
16688 for ranges in locations.values_mut() {
16689 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16690 ranges.dedup();
16691 num_locations += ranges.len();
16692 }
16693
16694 if num_locations > 1 {
16695 let Some(workspace) = workspace else {
16696 return Ok(Navigated::No);
16697 };
16698
16699 let tab_kind = match kind {
16700 Some(GotoDefinitionKind::Implementation) => "Implementations",
16701 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16702 Some(GotoDefinitionKind::Declaration) => "Declarations",
16703 Some(GotoDefinitionKind::Type) => "Types",
16704 };
16705 let title = editor
16706 .update_in(cx, |_, _, cx| {
16707 let target = locations
16708 .iter()
16709 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16710 .map(|(buffer, location)| {
16711 buffer
16712 .read(cx)
16713 .text_for_range(location.clone())
16714 .collect::<String>()
16715 })
16716 .filter(|text| !text.contains('\n'))
16717 .unique()
16718 .take(3)
16719 .join(", ");
16720 if target.is_empty() {
16721 tab_kind.to_owned()
16722 } else {
16723 format!("{tab_kind} for {target}")
16724 }
16725 })
16726 .context("buffer title")?;
16727
16728 let opened = workspace
16729 .update_in(cx, |workspace, window, cx| {
16730 Self::open_locations_in_multibuffer(
16731 workspace,
16732 locations,
16733 title,
16734 split,
16735 MultibufferSelectionMode::First,
16736 window,
16737 cx,
16738 )
16739 })
16740 .is_ok();
16741
16742 anyhow::Ok(Navigated::from_bool(opened))
16743 } else if num_locations == 0 {
16744 // If there is one url or file, open it directly
16745 match first_url_or_file {
16746 Some(Either::Left(url)) => {
16747 cx.update(|_, cx| cx.open_url(&url))?;
16748 Ok(Navigated::Yes)
16749 }
16750 Some(Either::Right(path)) => {
16751 let Some(workspace) = workspace else {
16752 return Ok(Navigated::No);
16753 };
16754
16755 workspace
16756 .update_in(cx, |workspace, window, cx| {
16757 workspace.open_resolved_path(path, window, cx)
16758 })?
16759 .await?;
16760 Ok(Navigated::Yes)
16761 }
16762 None => Ok(Navigated::No),
16763 }
16764 } else {
16765 let Some(workspace) = workspace else {
16766 return Ok(Navigated::No);
16767 };
16768
16769 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16770 let target_range = target_ranges.first().unwrap().clone();
16771
16772 editor.update_in(cx, |editor, window, cx| {
16773 let range = target_range.to_point(target_buffer.read(cx));
16774 let range = editor.range_for_match(&range);
16775 let range = collapse_multiline_range(range);
16776
16777 if !split
16778 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16779 {
16780 editor.go_to_singleton_buffer_range(range, window, cx);
16781 } else {
16782 let pane = workspace.read(cx).active_pane().clone();
16783 window.defer(cx, move |window, cx| {
16784 let target_editor: Entity<Self> =
16785 workspace.update(cx, |workspace, cx| {
16786 let pane = if split {
16787 workspace.adjacent_pane(window, cx)
16788 } else {
16789 workspace.active_pane().clone()
16790 };
16791
16792 workspace.open_project_item(
16793 pane,
16794 target_buffer.clone(),
16795 true,
16796 true,
16797 window,
16798 cx,
16799 )
16800 });
16801 target_editor.update(cx, |target_editor, cx| {
16802 // When selecting a definition in a different buffer, disable the nav history
16803 // to avoid creating a history entry at the previous cursor location.
16804 pane.update(cx, |pane, _| pane.disable_history());
16805 target_editor.go_to_singleton_buffer_range(range, window, cx);
16806 pane.update(cx, |pane, _| pane.enable_history());
16807 });
16808 });
16809 }
16810 Navigated::Yes
16811 })
16812 }
16813 })
16814 }
16815
16816 fn compute_target_location(
16817 &self,
16818 lsp_location: lsp::Location,
16819 server_id: LanguageServerId,
16820 window: &mut Window,
16821 cx: &mut Context<Self>,
16822 ) -> Task<anyhow::Result<Option<Location>>> {
16823 let Some(project) = self.project.clone() else {
16824 return Task::ready(Ok(None));
16825 };
16826
16827 cx.spawn_in(window, async move |editor, cx| {
16828 let location_task = editor.update(cx, |_, cx| {
16829 project.update(cx, |project, cx| {
16830 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16831 })
16832 })?;
16833 let location = Some({
16834 let target_buffer_handle = location_task.await.context("open local buffer")?;
16835 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16836 let target_start = target_buffer
16837 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16838 let target_end = target_buffer
16839 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16840 target_buffer.anchor_after(target_start)
16841 ..target_buffer.anchor_before(target_end)
16842 })?;
16843 Location {
16844 buffer: target_buffer_handle,
16845 range,
16846 }
16847 });
16848 Ok(location)
16849 })
16850 }
16851
16852 pub fn find_all_references(
16853 &mut self,
16854 _: &FindAllReferences,
16855 window: &mut Window,
16856 cx: &mut Context<Self>,
16857 ) -> Option<Task<Result<Navigated>>> {
16858 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16859 let multi_buffer = self.buffer.read(cx);
16860 let head = selection.head();
16861
16862 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16863 let head_anchor = multi_buffer_snapshot.anchor_at(
16864 head,
16865 if head < selection.tail() {
16866 Bias::Right
16867 } else {
16868 Bias::Left
16869 },
16870 );
16871
16872 match self
16873 .find_all_references_task_sources
16874 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16875 {
16876 Ok(_) => {
16877 log::info!(
16878 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16879 );
16880 return None;
16881 }
16882 Err(i) => {
16883 self.find_all_references_task_sources.insert(i, head_anchor);
16884 }
16885 }
16886
16887 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16888 let workspace = self.workspace()?;
16889 let project = workspace.read(cx).project().clone();
16890 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16891 Some(cx.spawn_in(window, async move |editor, cx| {
16892 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16893 if let Ok(i) = editor
16894 .find_all_references_task_sources
16895 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16896 {
16897 editor.find_all_references_task_sources.remove(i);
16898 }
16899 });
16900
16901 let Some(locations) = references.await? else {
16902 return anyhow::Ok(Navigated::No);
16903 };
16904 let mut locations = cx.update(|_, cx| {
16905 locations
16906 .into_iter()
16907 .map(|location| {
16908 let buffer = location.buffer.read(cx);
16909 (location.buffer, location.range.to_point(buffer))
16910 })
16911 .into_group_map()
16912 })?;
16913 if locations.is_empty() {
16914 return anyhow::Ok(Navigated::No);
16915 }
16916 for ranges in locations.values_mut() {
16917 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16918 ranges.dedup();
16919 }
16920
16921 workspace.update_in(cx, |workspace, window, cx| {
16922 let target = locations
16923 .iter()
16924 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16925 .map(|(buffer, location)| {
16926 buffer
16927 .read(cx)
16928 .text_for_range(location.clone())
16929 .collect::<String>()
16930 })
16931 .filter(|text| !text.contains('\n'))
16932 .unique()
16933 .take(3)
16934 .join(", ");
16935 let title = if target.is_empty() {
16936 "References".to_owned()
16937 } else {
16938 format!("References to {target}")
16939 };
16940 Self::open_locations_in_multibuffer(
16941 workspace,
16942 locations,
16943 title,
16944 false,
16945 MultibufferSelectionMode::First,
16946 window,
16947 cx,
16948 );
16949 Navigated::Yes
16950 })
16951 }))
16952 }
16953
16954 /// Opens a multibuffer with the given project locations in it
16955 pub fn open_locations_in_multibuffer(
16956 workspace: &mut Workspace,
16957 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
16958 title: String,
16959 split: bool,
16960 multibuffer_selection_mode: MultibufferSelectionMode,
16961 window: &mut Window,
16962 cx: &mut Context<Workspace>,
16963 ) {
16964 if locations.is_empty() {
16965 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16966 return;
16967 }
16968
16969 let capability = workspace.project().read(cx).capability();
16970 let mut ranges = <Vec<Range<Anchor>>>::new();
16971
16972 // a key to find existing multibuffer editors with the same set of locations
16973 // to prevent us from opening more and more multibuffer tabs for searches and the like
16974 let mut key = (title.clone(), vec![]);
16975 let excerpt_buffer = cx.new(|cx| {
16976 let key = &mut key.1;
16977 let mut multibuffer = MultiBuffer::new(capability);
16978 for (buffer, mut ranges_for_buffer) in locations {
16979 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16980 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
16981 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16982 PathKey::for_buffer(&buffer, cx),
16983 buffer.clone(),
16984 ranges_for_buffer,
16985 multibuffer_context_lines(cx),
16986 cx,
16987 );
16988 ranges.extend(new_ranges)
16989 }
16990
16991 multibuffer.with_title(title)
16992 });
16993 let existing = workspace.active_pane().update(cx, |pane, cx| {
16994 pane.items()
16995 .filter_map(|item| item.downcast::<Editor>())
16996 .find(|editor| {
16997 editor
16998 .read(cx)
16999 .lookup_key
17000 .as_ref()
17001 .and_then(|it| {
17002 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17003 })
17004 .is_some_and(|it| *it == key)
17005 })
17006 });
17007 let editor = existing.unwrap_or_else(|| {
17008 cx.new(|cx| {
17009 let mut editor = Editor::for_multibuffer(
17010 excerpt_buffer,
17011 Some(workspace.project().clone()),
17012 window,
17013 cx,
17014 );
17015 editor.lookup_key = Some(Box::new(key));
17016 editor
17017 })
17018 });
17019 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17020 MultibufferSelectionMode::First => {
17021 if let Some(first_range) = ranges.first() {
17022 editor.change_selections(
17023 SelectionEffects::no_scroll(),
17024 window,
17025 cx,
17026 |selections| {
17027 selections.clear_disjoint();
17028 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17029 },
17030 );
17031 }
17032 editor.highlight_background::<Self>(
17033 &ranges,
17034 |theme| theme.colors().editor_highlighted_line_background,
17035 cx,
17036 );
17037 }
17038 MultibufferSelectionMode::All => {
17039 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17040 selections.clear_disjoint();
17041 selections.select_anchor_ranges(ranges);
17042 });
17043 }
17044 });
17045
17046 let item = Box::new(editor);
17047 let item_id = item.item_id();
17048
17049 if split {
17050 let pane = workspace.adjacent_pane(window, cx);
17051 workspace.add_item(pane, item, None, true, true, window, cx);
17052 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17053 let (preview_item_id, preview_item_idx) =
17054 workspace.active_pane().read_with(cx, |pane, _| {
17055 (pane.preview_item_id(), pane.preview_item_idx())
17056 });
17057
17058 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17059
17060 if let Some(preview_item_id) = preview_item_id {
17061 workspace.active_pane().update(cx, |pane, cx| {
17062 pane.remove_item(preview_item_id, false, false, window, cx);
17063 });
17064 }
17065 } else {
17066 workspace.add_item_to_active_pane(item, None, true, window, cx);
17067 }
17068 workspace.active_pane().update(cx, |pane, cx| {
17069 pane.set_preview_item_id(Some(item_id), cx);
17070 });
17071 }
17072
17073 pub fn rename(
17074 &mut self,
17075 _: &Rename,
17076 window: &mut Window,
17077 cx: &mut Context<Self>,
17078 ) -> Option<Task<Result<()>>> {
17079 use language::ToOffset as _;
17080
17081 let provider = self.semantics_provider.clone()?;
17082 let selection = self.selections.newest_anchor().clone();
17083 let (cursor_buffer, cursor_buffer_position) = self
17084 .buffer
17085 .read(cx)
17086 .text_anchor_for_position(selection.head(), cx)?;
17087 let (tail_buffer, cursor_buffer_position_end) = self
17088 .buffer
17089 .read(cx)
17090 .text_anchor_for_position(selection.tail(), cx)?;
17091 if tail_buffer != cursor_buffer {
17092 return None;
17093 }
17094
17095 let snapshot = cursor_buffer.read(cx).snapshot();
17096 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17097 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17098 let prepare_rename = provider
17099 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17100 .unwrap_or_else(|| Task::ready(Ok(None)));
17101 drop(snapshot);
17102
17103 Some(cx.spawn_in(window, async move |this, cx| {
17104 let rename_range = if let Some(range) = prepare_rename.await? {
17105 Some(range)
17106 } else {
17107 this.update(cx, |this, cx| {
17108 let buffer = this.buffer.read(cx).snapshot(cx);
17109 let mut buffer_highlights = this
17110 .document_highlights_for_position(selection.head(), &buffer)
17111 .filter(|highlight| {
17112 highlight.start.excerpt_id == selection.head().excerpt_id
17113 && highlight.end.excerpt_id == selection.head().excerpt_id
17114 });
17115 buffer_highlights
17116 .next()
17117 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17118 })?
17119 };
17120 if let Some(rename_range) = rename_range {
17121 this.update_in(cx, |this, window, cx| {
17122 let snapshot = cursor_buffer.read(cx).snapshot();
17123 let rename_buffer_range = rename_range.to_offset(&snapshot);
17124 let cursor_offset_in_rename_range =
17125 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17126 let cursor_offset_in_rename_range_end =
17127 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17128
17129 this.take_rename(false, window, cx);
17130 let buffer = this.buffer.read(cx).read(cx);
17131 let cursor_offset = selection.head().to_offset(&buffer);
17132 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17133 let rename_end = rename_start + rename_buffer_range.len();
17134 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17135 let mut old_highlight_id = None;
17136 let old_name: Arc<str> = buffer
17137 .chunks(rename_start..rename_end, true)
17138 .map(|chunk| {
17139 if old_highlight_id.is_none() {
17140 old_highlight_id = chunk.syntax_highlight_id;
17141 }
17142 chunk.text
17143 })
17144 .collect::<String>()
17145 .into();
17146
17147 drop(buffer);
17148
17149 // Position the selection in the rename editor so that it matches the current selection.
17150 this.show_local_selections = false;
17151 let rename_editor = cx.new(|cx| {
17152 let mut editor = Editor::single_line(window, cx);
17153 editor.buffer.update(cx, |buffer, cx| {
17154 buffer.edit([(0..0, old_name.clone())], None, cx)
17155 });
17156 let rename_selection_range = match cursor_offset_in_rename_range
17157 .cmp(&cursor_offset_in_rename_range_end)
17158 {
17159 Ordering::Equal => {
17160 editor.select_all(&SelectAll, window, cx);
17161 return editor;
17162 }
17163 Ordering::Less => {
17164 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17165 }
17166 Ordering::Greater => {
17167 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17168 }
17169 };
17170 if rename_selection_range.end > old_name.len() {
17171 editor.select_all(&SelectAll, window, cx);
17172 } else {
17173 editor.change_selections(Default::default(), window, cx, |s| {
17174 s.select_ranges([rename_selection_range]);
17175 });
17176 }
17177 editor
17178 });
17179 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17180 if e == &EditorEvent::Focused {
17181 cx.emit(EditorEvent::FocusedIn)
17182 }
17183 })
17184 .detach();
17185
17186 let write_highlights =
17187 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17188 let read_highlights =
17189 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17190 let ranges = write_highlights
17191 .iter()
17192 .flat_map(|(_, ranges)| ranges.iter())
17193 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17194 .cloned()
17195 .collect();
17196
17197 this.highlight_text::<Rename>(
17198 ranges,
17199 HighlightStyle {
17200 fade_out: Some(0.6),
17201 ..Default::default()
17202 },
17203 cx,
17204 );
17205 let rename_focus_handle = rename_editor.focus_handle(cx);
17206 window.focus(&rename_focus_handle);
17207 let block_id = this.insert_blocks(
17208 [BlockProperties {
17209 style: BlockStyle::Flex,
17210 placement: BlockPlacement::Below(range.start),
17211 height: Some(1),
17212 render: Arc::new({
17213 let rename_editor = rename_editor.clone();
17214 move |cx: &mut BlockContext| {
17215 let mut text_style = cx.editor_style.text.clone();
17216 if let Some(highlight_style) = old_highlight_id
17217 .and_then(|h| h.style(&cx.editor_style.syntax))
17218 {
17219 text_style = text_style.highlight(highlight_style);
17220 }
17221 div()
17222 .block_mouse_except_scroll()
17223 .pl(cx.anchor_x)
17224 .child(EditorElement::new(
17225 &rename_editor,
17226 EditorStyle {
17227 background: cx.theme().system().transparent,
17228 local_player: cx.editor_style.local_player,
17229 text: text_style,
17230 scrollbar_width: cx.editor_style.scrollbar_width,
17231 syntax: cx.editor_style.syntax.clone(),
17232 status: cx.editor_style.status.clone(),
17233 inlay_hints_style: HighlightStyle {
17234 font_weight: Some(FontWeight::BOLD),
17235 ..make_inlay_hints_style(cx.app)
17236 },
17237 edit_prediction_styles: make_suggestion_styles(
17238 cx.app,
17239 ),
17240 ..EditorStyle::default()
17241 },
17242 ))
17243 .into_any_element()
17244 }
17245 }),
17246 priority: 0,
17247 }],
17248 Some(Autoscroll::fit()),
17249 cx,
17250 )[0];
17251 this.pending_rename = Some(RenameState {
17252 range,
17253 old_name,
17254 editor: rename_editor,
17255 block_id,
17256 });
17257 })?;
17258 }
17259
17260 Ok(())
17261 }))
17262 }
17263
17264 pub fn confirm_rename(
17265 &mut self,
17266 _: &ConfirmRename,
17267 window: &mut Window,
17268 cx: &mut Context<Self>,
17269 ) -> Option<Task<Result<()>>> {
17270 let rename = self.take_rename(false, window, cx)?;
17271 let workspace = self.workspace()?.downgrade();
17272 let (buffer, start) = self
17273 .buffer
17274 .read(cx)
17275 .text_anchor_for_position(rename.range.start, cx)?;
17276 let (end_buffer, _) = self
17277 .buffer
17278 .read(cx)
17279 .text_anchor_for_position(rename.range.end, cx)?;
17280 if buffer != end_buffer {
17281 return None;
17282 }
17283
17284 let old_name = rename.old_name;
17285 let new_name = rename.editor.read(cx).text(cx);
17286
17287 let rename = self.semantics_provider.as_ref()?.perform_rename(
17288 &buffer,
17289 start,
17290 new_name.clone(),
17291 cx,
17292 )?;
17293
17294 Some(cx.spawn_in(window, async move |editor, cx| {
17295 let project_transaction = rename.await?;
17296 Self::open_project_transaction(
17297 &editor,
17298 workspace,
17299 project_transaction,
17300 format!("Rename: {} → {}", old_name, new_name),
17301 cx,
17302 )
17303 .await?;
17304
17305 editor.update(cx, |editor, cx| {
17306 editor.refresh_document_highlights(cx);
17307 })?;
17308 Ok(())
17309 }))
17310 }
17311
17312 fn take_rename(
17313 &mut self,
17314 moving_cursor: bool,
17315 window: &mut Window,
17316 cx: &mut Context<Self>,
17317 ) -> Option<RenameState> {
17318 let rename = self.pending_rename.take()?;
17319 if rename.editor.focus_handle(cx).is_focused(window) {
17320 window.focus(&self.focus_handle);
17321 }
17322
17323 self.remove_blocks(
17324 [rename.block_id].into_iter().collect(),
17325 Some(Autoscroll::fit()),
17326 cx,
17327 );
17328 self.clear_highlights::<Rename>(cx);
17329 self.show_local_selections = true;
17330
17331 if moving_cursor {
17332 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17333 editor
17334 .selections
17335 .newest::<usize>(&editor.display_snapshot(cx))
17336 .head()
17337 });
17338
17339 // Update the selection to match the position of the selection inside
17340 // the rename editor.
17341 let snapshot = self.buffer.read(cx).read(cx);
17342 let rename_range = rename.range.to_offset(&snapshot);
17343 let cursor_in_editor = snapshot
17344 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17345 .min(rename_range.end);
17346 drop(snapshot);
17347
17348 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17349 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17350 });
17351 } else {
17352 self.refresh_document_highlights(cx);
17353 }
17354
17355 Some(rename)
17356 }
17357
17358 pub fn pending_rename(&self) -> Option<&RenameState> {
17359 self.pending_rename.as_ref()
17360 }
17361
17362 fn format(
17363 &mut self,
17364 _: &Format,
17365 window: &mut Window,
17366 cx: &mut Context<Self>,
17367 ) -> Option<Task<Result<()>>> {
17368 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17369
17370 let project = match &self.project {
17371 Some(project) => project.clone(),
17372 None => return None,
17373 };
17374
17375 Some(self.perform_format(
17376 project,
17377 FormatTrigger::Manual,
17378 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17379 window,
17380 cx,
17381 ))
17382 }
17383
17384 fn format_selections(
17385 &mut self,
17386 _: &FormatSelections,
17387 window: &mut Window,
17388 cx: &mut Context<Self>,
17389 ) -> Option<Task<Result<()>>> {
17390 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17391
17392 let project = match &self.project {
17393 Some(project) => project.clone(),
17394 None => return None,
17395 };
17396
17397 let ranges = self
17398 .selections
17399 .all_adjusted(&self.display_snapshot(cx))
17400 .into_iter()
17401 .map(|selection| selection.range())
17402 .collect_vec();
17403
17404 Some(self.perform_format(
17405 project,
17406 FormatTrigger::Manual,
17407 FormatTarget::Ranges(ranges),
17408 window,
17409 cx,
17410 ))
17411 }
17412
17413 fn perform_format(
17414 &mut self,
17415 project: Entity<Project>,
17416 trigger: FormatTrigger,
17417 target: FormatTarget,
17418 window: &mut Window,
17419 cx: &mut Context<Self>,
17420 ) -> Task<Result<()>> {
17421 let buffer = self.buffer.clone();
17422 let (buffers, target) = match target {
17423 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17424 FormatTarget::Ranges(selection_ranges) => {
17425 let multi_buffer = buffer.read(cx);
17426 let snapshot = multi_buffer.read(cx);
17427 let mut buffers = HashSet::default();
17428 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17429 BTreeMap::new();
17430 for selection_range in selection_ranges {
17431 for (buffer, buffer_range, _) in
17432 snapshot.range_to_buffer_ranges(selection_range)
17433 {
17434 let buffer_id = buffer.remote_id();
17435 let start = buffer.anchor_before(buffer_range.start);
17436 let end = buffer.anchor_after(buffer_range.end);
17437 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17438 buffer_id_to_ranges
17439 .entry(buffer_id)
17440 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17441 .or_insert_with(|| vec![start..end]);
17442 }
17443 }
17444 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17445 }
17446 };
17447
17448 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17449 let selections_prev = transaction_id_prev
17450 .and_then(|transaction_id_prev| {
17451 // default to selections as they were after the last edit, if we have them,
17452 // instead of how they are now.
17453 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17454 // will take you back to where you made the last edit, instead of staying where you scrolled
17455 self.selection_history
17456 .transaction(transaction_id_prev)
17457 .map(|t| t.0.clone())
17458 })
17459 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17460
17461 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17462 let format = project.update(cx, |project, cx| {
17463 project.format(buffers, target, true, trigger, cx)
17464 });
17465
17466 cx.spawn_in(window, async move |editor, cx| {
17467 let transaction = futures::select_biased! {
17468 transaction = format.log_err().fuse() => transaction,
17469 () = timeout => {
17470 log::warn!("timed out waiting for formatting");
17471 None
17472 }
17473 };
17474
17475 buffer
17476 .update(cx, |buffer, cx| {
17477 if let Some(transaction) = transaction
17478 && !buffer.is_singleton()
17479 {
17480 buffer.push_transaction(&transaction.0, cx);
17481 }
17482 cx.notify();
17483 })
17484 .ok();
17485
17486 if let Some(transaction_id_now) =
17487 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17488 {
17489 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17490 if has_new_transaction {
17491 _ = editor.update(cx, |editor, _| {
17492 editor
17493 .selection_history
17494 .insert_transaction(transaction_id_now, selections_prev);
17495 });
17496 }
17497 }
17498
17499 Ok(())
17500 })
17501 }
17502
17503 fn organize_imports(
17504 &mut self,
17505 _: &OrganizeImports,
17506 window: &mut Window,
17507 cx: &mut Context<Self>,
17508 ) -> Option<Task<Result<()>>> {
17509 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17510 let project = match &self.project {
17511 Some(project) => project.clone(),
17512 None => return None,
17513 };
17514 Some(self.perform_code_action_kind(
17515 project,
17516 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17517 window,
17518 cx,
17519 ))
17520 }
17521
17522 fn perform_code_action_kind(
17523 &mut self,
17524 project: Entity<Project>,
17525 kind: CodeActionKind,
17526 window: &mut Window,
17527 cx: &mut Context<Self>,
17528 ) -> Task<Result<()>> {
17529 let buffer = self.buffer.clone();
17530 let buffers = buffer.read(cx).all_buffers();
17531 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17532 let apply_action = project.update(cx, |project, cx| {
17533 project.apply_code_action_kind(buffers, kind, true, cx)
17534 });
17535 cx.spawn_in(window, async move |_, cx| {
17536 let transaction = futures::select_biased! {
17537 () = timeout => {
17538 log::warn!("timed out waiting for executing code action");
17539 None
17540 }
17541 transaction = apply_action.log_err().fuse() => transaction,
17542 };
17543 buffer
17544 .update(cx, |buffer, cx| {
17545 // check if we need this
17546 if let Some(transaction) = transaction
17547 && !buffer.is_singleton()
17548 {
17549 buffer.push_transaction(&transaction.0, cx);
17550 }
17551 cx.notify();
17552 })
17553 .ok();
17554 Ok(())
17555 })
17556 }
17557
17558 pub fn restart_language_server(
17559 &mut self,
17560 _: &RestartLanguageServer,
17561 _: &mut Window,
17562 cx: &mut Context<Self>,
17563 ) {
17564 if let Some(project) = self.project.clone() {
17565 self.buffer.update(cx, |multi_buffer, cx| {
17566 project.update(cx, |project, cx| {
17567 project.restart_language_servers_for_buffers(
17568 multi_buffer.all_buffers().into_iter().collect(),
17569 HashSet::default(),
17570 cx,
17571 );
17572 });
17573 })
17574 }
17575 }
17576
17577 pub fn stop_language_server(
17578 &mut self,
17579 _: &StopLanguageServer,
17580 _: &mut Window,
17581 cx: &mut Context<Self>,
17582 ) {
17583 if let Some(project) = self.project.clone() {
17584 self.buffer.update(cx, |multi_buffer, cx| {
17585 project.update(cx, |project, cx| {
17586 project.stop_language_servers_for_buffers(
17587 multi_buffer.all_buffers().into_iter().collect(),
17588 HashSet::default(),
17589 cx,
17590 );
17591 cx.emit(project::Event::RefreshInlayHints);
17592 });
17593 });
17594 }
17595 }
17596
17597 fn cancel_language_server_work(
17598 workspace: &mut Workspace,
17599 _: &actions::CancelLanguageServerWork,
17600 _: &mut Window,
17601 cx: &mut Context<Workspace>,
17602 ) {
17603 let project = workspace.project();
17604 let buffers = workspace
17605 .active_item(cx)
17606 .and_then(|item| item.act_as::<Editor>(cx))
17607 .map_or(HashSet::default(), |editor| {
17608 editor.read(cx).buffer.read(cx).all_buffers()
17609 });
17610 project.update(cx, |project, cx| {
17611 project.cancel_language_server_work_for_buffers(buffers, cx);
17612 });
17613 }
17614
17615 fn show_character_palette(
17616 &mut self,
17617 _: &ShowCharacterPalette,
17618 window: &mut Window,
17619 _: &mut Context<Self>,
17620 ) {
17621 window.show_character_palette();
17622 }
17623
17624 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17625 if !self.diagnostics_enabled() {
17626 return;
17627 }
17628
17629 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17630 let buffer = self.buffer.read(cx).snapshot(cx);
17631 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17632 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17633 let is_valid = buffer
17634 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17635 .any(|entry| {
17636 entry.diagnostic.is_primary
17637 && !entry.range.is_empty()
17638 && entry.range.start == primary_range_start
17639 && entry.diagnostic.message == active_diagnostics.active_message
17640 });
17641
17642 if !is_valid {
17643 self.dismiss_diagnostics(cx);
17644 }
17645 }
17646 }
17647
17648 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17649 match &self.active_diagnostics {
17650 ActiveDiagnostic::Group(group) => Some(group),
17651 _ => None,
17652 }
17653 }
17654
17655 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17656 if !self.diagnostics_enabled() {
17657 return;
17658 }
17659 self.dismiss_diagnostics(cx);
17660 self.active_diagnostics = ActiveDiagnostic::All;
17661 }
17662
17663 fn activate_diagnostics(
17664 &mut self,
17665 buffer_id: BufferId,
17666 diagnostic: DiagnosticEntryRef<'_, usize>,
17667 window: &mut Window,
17668 cx: &mut Context<Self>,
17669 ) {
17670 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17671 return;
17672 }
17673 self.dismiss_diagnostics(cx);
17674 let snapshot = self.snapshot(window, cx);
17675 let buffer = self.buffer.read(cx).snapshot(cx);
17676 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17677 return;
17678 };
17679
17680 let diagnostic_group = buffer
17681 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17682 .collect::<Vec<_>>();
17683
17684 let blocks =
17685 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17686
17687 let blocks = self.display_map.update(cx, |display_map, cx| {
17688 display_map.insert_blocks(blocks, cx).into_iter().collect()
17689 });
17690 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17691 active_range: buffer.anchor_before(diagnostic.range.start)
17692 ..buffer.anchor_after(diagnostic.range.end),
17693 active_message: diagnostic.diagnostic.message.clone(),
17694 group_id: diagnostic.diagnostic.group_id,
17695 blocks,
17696 });
17697 cx.notify();
17698 }
17699
17700 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17701 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17702 return;
17703 };
17704
17705 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17706 if let ActiveDiagnostic::Group(group) = prev {
17707 self.display_map.update(cx, |display_map, cx| {
17708 display_map.remove_blocks(group.blocks, cx);
17709 });
17710 cx.notify();
17711 }
17712 }
17713
17714 /// Disable inline diagnostics rendering for this editor.
17715 pub fn disable_inline_diagnostics(&mut self) {
17716 self.inline_diagnostics_enabled = false;
17717 self.inline_diagnostics_update = Task::ready(());
17718 self.inline_diagnostics.clear();
17719 }
17720
17721 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17722 self.diagnostics_enabled = false;
17723 self.dismiss_diagnostics(cx);
17724 self.inline_diagnostics_update = Task::ready(());
17725 self.inline_diagnostics.clear();
17726 }
17727
17728 pub fn disable_word_completions(&mut self) {
17729 self.word_completions_enabled = false;
17730 }
17731
17732 pub fn diagnostics_enabled(&self) -> bool {
17733 self.diagnostics_enabled && self.mode.is_full()
17734 }
17735
17736 pub fn inline_diagnostics_enabled(&self) -> bool {
17737 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17738 }
17739
17740 pub fn show_inline_diagnostics(&self) -> bool {
17741 self.show_inline_diagnostics
17742 }
17743
17744 pub fn toggle_inline_diagnostics(
17745 &mut self,
17746 _: &ToggleInlineDiagnostics,
17747 window: &mut Window,
17748 cx: &mut Context<Editor>,
17749 ) {
17750 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17751 self.refresh_inline_diagnostics(false, window, cx);
17752 }
17753
17754 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17755 self.diagnostics_max_severity = severity;
17756 self.display_map.update(cx, |display_map, _| {
17757 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17758 });
17759 }
17760
17761 pub fn toggle_diagnostics(
17762 &mut self,
17763 _: &ToggleDiagnostics,
17764 window: &mut Window,
17765 cx: &mut Context<Editor>,
17766 ) {
17767 if !self.diagnostics_enabled() {
17768 return;
17769 }
17770
17771 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17772 EditorSettings::get_global(cx)
17773 .diagnostics_max_severity
17774 .filter(|severity| severity != &DiagnosticSeverity::Off)
17775 .unwrap_or(DiagnosticSeverity::Hint)
17776 } else {
17777 DiagnosticSeverity::Off
17778 };
17779 self.set_max_diagnostics_severity(new_severity, cx);
17780 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17781 self.active_diagnostics = ActiveDiagnostic::None;
17782 self.inline_diagnostics_update = Task::ready(());
17783 self.inline_diagnostics.clear();
17784 } else {
17785 self.refresh_inline_diagnostics(false, window, cx);
17786 }
17787
17788 cx.notify();
17789 }
17790
17791 pub fn toggle_minimap(
17792 &mut self,
17793 _: &ToggleMinimap,
17794 window: &mut Window,
17795 cx: &mut Context<Editor>,
17796 ) {
17797 if self.supports_minimap(cx) {
17798 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17799 }
17800 }
17801
17802 fn refresh_inline_diagnostics(
17803 &mut self,
17804 debounce: bool,
17805 window: &mut Window,
17806 cx: &mut Context<Self>,
17807 ) {
17808 let max_severity = ProjectSettings::get_global(cx)
17809 .diagnostics
17810 .inline
17811 .max_severity
17812 .unwrap_or(self.diagnostics_max_severity);
17813
17814 if !self.inline_diagnostics_enabled()
17815 || !self.show_inline_diagnostics
17816 || max_severity == DiagnosticSeverity::Off
17817 {
17818 self.inline_diagnostics_update = Task::ready(());
17819 self.inline_diagnostics.clear();
17820 return;
17821 }
17822
17823 let debounce_ms = ProjectSettings::get_global(cx)
17824 .diagnostics
17825 .inline
17826 .update_debounce_ms;
17827 let debounce = if debounce && debounce_ms > 0 {
17828 Some(Duration::from_millis(debounce_ms))
17829 } else {
17830 None
17831 };
17832 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17833 if let Some(debounce) = debounce {
17834 cx.background_executor().timer(debounce).await;
17835 }
17836 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17837 editor
17838 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17839 .ok()
17840 }) else {
17841 return;
17842 };
17843
17844 let new_inline_diagnostics = cx
17845 .background_spawn(async move {
17846 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17847 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17848 let message = diagnostic_entry
17849 .diagnostic
17850 .message
17851 .split_once('\n')
17852 .map(|(line, _)| line)
17853 .map(SharedString::new)
17854 .unwrap_or_else(|| {
17855 SharedString::new(&*diagnostic_entry.diagnostic.message)
17856 });
17857 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17858 let (Ok(i) | Err(i)) = inline_diagnostics
17859 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17860 inline_diagnostics.insert(
17861 i,
17862 (
17863 start_anchor,
17864 InlineDiagnostic {
17865 message,
17866 group_id: diagnostic_entry.diagnostic.group_id,
17867 start: diagnostic_entry.range.start.to_point(&snapshot),
17868 is_primary: diagnostic_entry.diagnostic.is_primary,
17869 severity: diagnostic_entry.diagnostic.severity,
17870 },
17871 ),
17872 );
17873 }
17874 inline_diagnostics
17875 })
17876 .await;
17877
17878 editor
17879 .update(cx, |editor, cx| {
17880 editor.inline_diagnostics = new_inline_diagnostics;
17881 cx.notify();
17882 })
17883 .ok();
17884 });
17885 }
17886
17887 fn pull_diagnostics(
17888 &mut self,
17889 buffer_id: Option<BufferId>,
17890 window: &Window,
17891 cx: &mut Context<Self>,
17892 ) -> Option<()> {
17893 if self.ignore_lsp_data() {
17894 return None;
17895 }
17896 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17897 .diagnostics
17898 .lsp_pull_diagnostics;
17899 if !pull_diagnostics_settings.enabled {
17900 return None;
17901 }
17902 let project = self.project()?.downgrade();
17903 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17904 let mut buffers = self.buffer.read(cx).all_buffers();
17905 buffers.retain(|buffer| {
17906 let buffer_id_to_retain = buffer.read(cx).remote_id();
17907 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
17908 && self.registered_buffers.contains_key(&buffer_id_to_retain)
17909 });
17910 if buffers.is_empty() {
17911 self.pull_diagnostics_task = Task::ready(());
17912 return None;
17913 }
17914
17915 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17916 cx.background_executor().timer(debounce).await;
17917
17918 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17919 buffers
17920 .into_iter()
17921 .filter_map(|buffer| {
17922 project
17923 .update(cx, |project, cx| {
17924 project.lsp_store().update(cx, |lsp_store, cx| {
17925 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17926 })
17927 })
17928 .ok()
17929 })
17930 .collect::<FuturesUnordered<_>>()
17931 }) else {
17932 return;
17933 };
17934
17935 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17936 match pull_task {
17937 Ok(()) => {
17938 if editor
17939 .update_in(cx, |editor, window, cx| {
17940 editor.update_diagnostics_state(window, cx);
17941 })
17942 .is_err()
17943 {
17944 return;
17945 }
17946 }
17947 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17948 }
17949 }
17950 });
17951
17952 Some(())
17953 }
17954
17955 pub fn set_selections_from_remote(
17956 &mut self,
17957 selections: Vec<Selection<Anchor>>,
17958 pending_selection: Option<Selection<Anchor>>,
17959 window: &mut Window,
17960 cx: &mut Context<Self>,
17961 ) {
17962 let old_cursor_position = self.selections.newest_anchor().head();
17963 self.selections.change_with(cx, |s| {
17964 s.select_anchors(selections);
17965 if let Some(pending_selection) = pending_selection {
17966 s.set_pending(pending_selection, SelectMode::Character);
17967 } else {
17968 s.clear_pending();
17969 }
17970 });
17971 self.selections_did_change(
17972 false,
17973 &old_cursor_position,
17974 SelectionEffects::default(),
17975 window,
17976 cx,
17977 );
17978 }
17979
17980 pub fn transact(
17981 &mut self,
17982 window: &mut Window,
17983 cx: &mut Context<Self>,
17984 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17985 ) -> Option<TransactionId> {
17986 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17987 this.start_transaction_at(Instant::now(), window, cx);
17988 update(this, window, cx);
17989 this.end_transaction_at(Instant::now(), cx)
17990 })
17991 }
17992
17993 pub fn start_transaction_at(
17994 &mut self,
17995 now: Instant,
17996 window: &mut Window,
17997 cx: &mut Context<Self>,
17998 ) -> Option<TransactionId> {
17999 self.end_selection(window, cx);
18000 if let Some(tx_id) = self
18001 .buffer
18002 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18003 {
18004 self.selection_history
18005 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18006 cx.emit(EditorEvent::TransactionBegun {
18007 transaction_id: tx_id,
18008 });
18009 Some(tx_id)
18010 } else {
18011 None
18012 }
18013 }
18014
18015 pub fn end_transaction_at(
18016 &mut self,
18017 now: Instant,
18018 cx: &mut Context<Self>,
18019 ) -> Option<TransactionId> {
18020 if let Some(transaction_id) = self
18021 .buffer
18022 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18023 {
18024 if let Some((_, end_selections)) =
18025 self.selection_history.transaction_mut(transaction_id)
18026 {
18027 *end_selections = Some(self.selections.disjoint_anchors_arc());
18028 } else {
18029 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18030 }
18031
18032 cx.emit(EditorEvent::Edited { transaction_id });
18033 Some(transaction_id)
18034 } else {
18035 None
18036 }
18037 }
18038
18039 pub fn modify_transaction_selection_history(
18040 &mut self,
18041 transaction_id: TransactionId,
18042 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18043 ) -> bool {
18044 self.selection_history
18045 .transaction_mut(transaction_id)
18046 .map(modify)
18047 .is_some()
18048 }
18049
18050 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18051 if self.selection_mark_mode {
18052 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18053 s.move_with(|_, sel| {
18054 sel.collapse_to(sel.head(), SelectionGoal::None);
18055 });
18056 })
18057 }
18058 self.selection_mark_mode = true;
18059 cx.notify();
18060 }
18061
18062 pub fn swap_selection_ends(
18063 &mut self,
18064 _: &actions::SwapSelectionEnds,
18065 window: &mut Window,
18066 cx: &mut Context<Self>,
18067 ) {
18068 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18069 s.move_with(|_, sel| {
18070 if sel.start != sel.end {
18071 sel.reversed = !sel.reversed
18072 }
18073 });
18074 });
18075 self.request_autoscroll(Autoscroll::newest(), cx);
18076 cx.notify();
18077 }
18078
18079 pub fn toggle_focus(
18080 workspace: &mut Workspace,
18081 _: &actions::ToggleFocus,
18082 window: &mut Window,
18083 cx: &mut Context<Workspace>,
18084 ) {
18085 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18086 return;
18087 };
18088 workspace.activate_item(&item, true, true, window, cx);
18089 }
18090
18091 pub fn toggle_fold(
18092 &mut self,
18093 _: &actions::ToggleFold,
18094 window: &mut Window,
18095 cx: &mut Context<Self>,
18096 ) {
18097 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18098 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18099 let selection = self.selections.newest::<Point>(&display_map);
18100
18101 let range = if selection.is_empty() {
18102 let point = selection.head().to_display_point(&display_map);
18103 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18104 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18105 .to_point(&display_map);
18106 start..end
18107 } else {
18108 selection.range()
18109 };
18110 if display_map.folds_in_range(range).next().is_some() {
18111 self.unfold_lines(&Default::default(), window, cx)
18112 } else {
18113 self.fold(&Default::default(), window, cx)
18114 }
18115 } else {
18116 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18117 let buffer_ids: HashSet<_> = self
18118 .selections
18119 .disjoint_anchor_ranges()
18120 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18121 .collect();
18122
18123 let should_unfold = buffer_ids
18124 .iter()
18125 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18126
18127 for buffer_id in buffer_ids {
18128 if should_unfold {
18129 self.unfold_buffer(buffer_id, cx);
18130 } else {
18131 self.fold_buffer(buffer_id, cx);
18132 }
18133 }
18134 }
18135 }
18136
18137 pub fn toggle_fold_recursive(
18138 &mut self,
18139 _: &actions::ToggleFoldRecursive,
18140 window: &mut Window,
18141 cx: &mut Context<Self>,
18142 ) {
18143 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18144
18145 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18146 let range = if selection.is_empty() {
18147 let point = selection.head().to_display_point(&display_map);
18148 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18149 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18150 .to_point(&display_map);
18151 start..end
18152 } else {
18153 selection.range()
18154 };
18155 if display_map.folds_in_range(range).next().is_some() {
18156 self.unfold_recursive(&Default::default(), window, cx)
18157 } else {
18158 self.fold_recursive(&Default::default(), window, cx)
18159 }
18160 }
18161
18162 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18163 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18164 let mut to_fold = Vec::new();
18165 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18166 let selections = self.selections.all_adjusted(&display_map);
18167
18168 for selection in selections {
18169 let range = selection.range().sorted();
18170 let buffer_start_row = range.start.row;
18171
18172 if range.start.row != range.end.row {
18173 let mut found = false;
18174 let mut row = range.start.row;
18175 while row <= range.end.row {
18176 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18177 {
18178 found = true;
18179 row = crease.range().end.row + 1;
18180 to_fold.push(crease);
18181 } else {
18182 row += 1
18183 }
18184 }
18185 if found {
18186 continue;
18187 }
18188 }
18189
18190 for row in (0..=range.start.row).rev() {
18191 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18192 && crease.range().end.row >= buffer_start_row
18193 {
18194 to_fold.push(crease);
18195 if row <= range.start.row {
18196 break;
18197 }
18198 }
18199 }
18200 }
18201
18202 self.fold_creases(to_fold, true, window, cx);
18203 } else {
18204 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18205 let buffer_ids = self
18206 .selections
18207 .disjoint_anchor_ranges()
18208 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18209 .collect::<HashSet<_>>();
18210 for buffer_id in buffer_ids {
18211 self.fold_buffer(buffer_id, cx);
18212 }
18213 }
18214 }
18215
18216 pub fn toggle_fold_all(
18217 &mut self,
18218 _: &actions::ToggleFoldAll,
18219 window: &mut Window,
18220 cx: &mut Context<Self>,
18221 ) {
18222 if self.buffer.read(cx).is_singleton() {
18223 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18224 let has_folds = display_map
18225 .folds_in_range(0..display_map.buffer_snapshot().len())
18226 .next()
18227 .is_some();
18228
18229 if has_folds {
18230 self.unfold_all(&actions::UnfoldAll, window, cx);
18231 } else {
18232 self.fold_all(&actions::FoldAll, window, cx);
18233 }
18234 } else {
18235 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18236 let should_unfold = buffer_ids
18237 .iter()
18238 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18239
18240 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18241 editor
18242 .update_in(cx, |editor, _, cx| {
18243 for buffer_id in buffer_ids {
18244 if should_unfold {
18245 editor.unfold_buffer(buffer_id, cx);
18246 } else {
18247 editor.fold_buffer(buffer_id, cx);
18248 }
18249 }
18250 })
18251 .ok();
18252 });
18253 }
18254 }
18255
18256 fn fold_at_level(
18257 &mut self,
18258 fold_at: &FoldAtLevel,
18259 window: &mut Window,
18260 cx: &mut Context<Self>,
18261 ) {
18262 if !self.buffer.read(cx).is_singleton() {
18263 return;
18264 }
18265
18266 let fold_at_level = fold_at.0;
18267 let snapshot = self.buffer.read(cx).snapshot(cx);
18268 let mut to_fold = Vec::new();
18269 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18270
18271 let row_ranges_to_keep: Vec<Range<u32>> = self
18272 .selections
18273 .all::<Point>(&self.display_snapshot(cx))
18274 .into_iter()
18275 .map(|sel| sel.start.row..sel.end.row)
18276 .collect();
18277
18278 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18279 while start_row < end_row {
18280 match self
18281 .snapshot(window, cx)
18282 .crease_for_buffer_row(MultiBufferRow(start_row))
18283 {
18284 Some(crease) => {
18285 let nested_start_row = crease.range().start.row + 1;
18286 let nested_end_row = crease.range().end.row;
18287
18288 if current_level < fold_at_level {
18289 stack.push((nested_start_row, nested_end_row, current_level + 1));
18290 } else if current_level == fold_at_level {
18291 // Fold iff there is no selection completely contained within the fold region
18292 if !row_ranges_to_keep.iter().any(|selection| {
18293 selection.end >= nested_start_row
18294 && selection.start <= nested_end_row
18295 }) {
18296 to_fold.push(crease);
18297 }
18298 }
18299
18300 start_row = nested_end_row + 1;
18301 }
18302 None => start_row += 1,
18303 }
18304 }
18305 }
18306
18307 self.fold_creases(to_fold, true, window, cx);
18308 }
18309
18310 pub fn fold_at_level_1(
18311 &mut self,
18312 _: &actions::FoldAtLevel1,
18313 window: &mut Window,
18314 cx: &mut Context<Self>,
18315 ) {
18316 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18317 }
18318
18319 pub fn fold_at_level_2(
18320 &mut self,
18321 _: &actions::FoldAtLevel2,
18322 window: &mut Window,
18323 cx: &mut Context<Self>,
18324 ) {
18325 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18326 }
18327
18328 pub fn fold_at_level_3(
18329 &mut self,
18330 _: &actions::FoldAtLevel3,
18331 window: &mut Window,
18332 cx: &mut Context<Self>,
18333 ) {
18334 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18335 }
18336
18337 pub fn fold_at_level_4(
18338 &mut self,
18339 _: &actions::FoldAtLevel4,
18340 window: &mut Window,
18341 cx: &mut Context<Self>,
18342 ) {
18343 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18344 }
18345
18346 pub fn fold_at_level_5(
18347 &mut self,
18348 _: &actions::FoldAtLevel5,
18349 window: &mut Window,
18350 cx: &mut Context<Self>,
18351 ) {
18352 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18353 }
18354
18355 pub fn fold_at_level_6(
18356 &mut self,
18357 _: &actions::FoldAtLevel6,
18358 window: &mut Window,
18359 cx: &mut Context<Self>,
18360 ) {
18361 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18362 }
18363
18364 pub fn fold_at_level_7(
18365 &mut self,
18366 _: &actions::FoldAtLevel7,
18367 window: &mut Window,
18368 cx: &mut Context<Self>,
18369 ) {
18370 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18371 }
18372
18373 pub fn fold_at_level_8(
18374 &mut self,
18375 _: &actions::FoldAtLevel8,
18376 window: &mut Window,
18377 cx: &mut Context<Self>,
18378 ) {
18379 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18380 }
18381
18382 pub fn fold_at_level_9(
18383 &mut self,
18384 _: &actions::FoldAtLevel9,
18385 window: &mut Window,
18386 cx: &mut Context<Self>,
18387 ) {
18388 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18389 }
18390
18391 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18392 if self.buffer.read(cx).is_singleton() {
18393 let mut fold_ranges = Vec::new();
18394 let snapshot = self.buffer.read(cx).snapshot(cx);
18395
18396 for row in 0..snapshot.max_row().0 {
18397 if let Some(foldable_range) = self
18398 .snapshot(window, cx)
18399 .crease_for_buffer_row(MultiBufferRow(row))
18400 {
18401 fold_ranges.push(foldable_range);
18402 }
18403 }
18404
18405 self.fold_creases(fold_ranges, true, window, cx);
18406 } else {
18407 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18408 editor
18409 .update_in(cx, |editor, _, cx| {
18410 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18411 editor.fold_buffer(buffer_id, cx);
18412 }
18413 })
18414 .ok();
18415 });
18416 }
18417 }
18418
18419 pub fn fold_function_bodies(
18420 &mut self,
18421 _: &actions::FoldFunctionBodies,
18422 window: &mut Window,
18423 cx: &mut Context<Self>,
18424 ) {
18425 let snapshot = self.buffer.read(cx).snapshot(cx);
18426
18427 let ranges = snapshot
18428 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18429 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18430 .collect::<Vec<_>>();
18431
18432 let creases = ranges
18433 .into_iter()
18434 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18435 .collect();
18436
18437 self.fold_creases(creases, true, window, cx);
18438 }
18439
18440 pub fn fold_recursive(
18441 &mut self,
18442 _: &actions::FoldRecursive,
18443 window: &mut Window,
18444 cx: &mut Context<Self>,
18445 ) {
18446 let mut to_fold = Vec::new();
18447 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18448 let selections = self.selections.all_adjusted(&display_map);
18449
18450 for selection in selections {
18451 let range = selection.range().sorted();
18452 let buffer_start_row = range.start.row;
18453
18454 if range.start.row != range.end.row {
18455 let mut found = false;
18456 for row in range.start.row..=range.end.row {
18457 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18458 found = true;
18459 to_fold.push(crease);
18460 }
18461 }
18462 if found {
18463 continue;
18464 }
18465 }
18466
18467 for row in (0..=range.start.row).rev() {
18468 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18469 if crease.range().end.row >= buffer_start_row {
18470 to_fold.push(crease);
18471 } else {
18472 break;
18473 }
18474 }
18475 }
18476 }
18477
18478 self.fold_creases(to_fold, true, window, cx);
18479 }
18480
18481 pub fn fold_at(
18482 &mut self,
18483 buffer_row: MultiBufferRow,
18484 window: &mut Window,
18485 cx: &mut Context<Self>,
18486 ) {
18487 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18488
18489 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18490 let autoscroll = self
18491 .selections
18492 .all::<Point>(&display_map)
18493 .iter()
18494 .any(|selection| crease.range().overlaps(&selection.range()));
18495
18496 self.fold_creases(vec![crease], autoscroll, window, cx);
18497 }
18498 }
18499
18500 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18501 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18502 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18503 let buffer = display_map.buffer_snapshot();
18504 let selections = self.selections.all::<Point>(&display_map);
18505 let ranges = selections
18506 .iter()
18507 .map(|s| {
18508 let range = s.display_range(&display_map).sorted();
18509 let mut start = range.start.to_point(&display_map);
18510 let mut end = range.end.to_point(&display_map);
18511 start.column = 0;
18512 end.column = buffer.line_len(MultiBufferRow(end.row));
18513 start..end
18514 })
18515 .collect::<Vec<_>>();
18516
18517 self.unfold_ranges(&ranges, true, true, cx);
18518 } else {
18519 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18520 let buffer_ids = self
18521 .selections
18522 .disjoint_anchor_ranges()
18523 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18524 .collect::<HashSet<_>>();
18525 for buffer_id in buffer_ids {
18526 self.unfold_buffer(buffer_id, cx);
18527 }
18528 }
18529 }
18530
18531 pub fn unfold_recursive(
18532 &mut self,
18533 _: &UnfoldRecursive,
18534 _window: &mut Window,
18535 cx: &mut Context<Self>,
18536 ) {
18537 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18538 let selections = self.selections.all::<Point>(&display_map);
18539 let ranges = selections
18540 .iter()
18541 .map(|s| {
18542 let mut range = s.display_range(&display_map).sorted();
18543 *range.start.column_mut() = 0;
18544 *range.end.column_mut() = display_map.line_len(range.end.row());
18545 let start = range.start.to_point(&display_map);
18546 let end = range.end.to_point(&display_map);
18547 start..end
18548 })
18549 .collect::<Vec<_>>();
18550
18551 self.unfold_ranges(&ranges, true, true, cx);
18552 }
18553
18554 pub fn unfold_at(
18555 &mut self,
18556 buffer_row: MultiBufferRow,
18557 _window: &mut Window,
18558 cx: &mut Context<Self>,
18559 ) {
18560 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18561
18562 let intersection_range = Point::new(buffer_row.0, 0)
18563 ..Point::new(
18564 buffer_row.0,
18565 display_map.buffer_snapshot().line_len(buffer_row),
18566 );
18567
18568 let autoscroll = self
18569 .selections
18570 .all::<Point>(&display_map)
18571 .iter()
18572 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18573
18574 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18575 }
18576
18577 pub fn unfold_all(
18578 &mut self,
18579 _: &actions::UnfoldAll,
18580 _window: &mut Window,
18581 cx: &mut Context<Self>,
18582 ) {
18583 if self.buffer.read(cx).is_singleton() {
18584 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18585 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18586 } else {
18587 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18588 editor
18589 .update(cx, |editor, cx| {
18590 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18591 editor.unfold_buffer(buffer_id, cx);
18592 }
18593 })
18594 .ok();
18595 });
18596 }
18597 }
18598
18599 pub fn fold_selected_ranges(
18600 &mut self,
18601 _: &FoldSelectedRanges,
18602 window: &mut Window,
18603 cx: &mut Context<Self>,
18604 ) {
18605 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18606 let selections = self.selections.all_adjusted(&display_map);
18607 let ranges = selections
18608 .into_iter()
18609 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18610 .collect::<Vec<_>>();
18611 self.fold_creases(ranges, true, window, cx);
18612 }
18613
18614 pub fn fold_ranges<T: ToOffset + Clone>(
18615 &mut self,
18616 ranges: Vec<Range<T>>,
18617 auto_scroll: bool,
18618 window: &mut Window,
18619 cx: &mut Context<Self>,
18620 ) {
18621 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18622 let ranges = ranges
18623 .into_iter()
18624 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18625 .collect::<Vec<_>>();
18626 self.fold_creases(ranges, auto_scroll, window, cx);
18627 }
18628
18629 pub fn fold_creases<T: ToOffset + Clone>(
18630 &mut self,
18631 creases: Vec<Crease<T>>,
18632 auto_scroll: bool,
18633 _window: &mut Window,
18634 cx: &mut Context<Self>,
18635 ) {
18636 if creases.is_empty() {
18637 return;
18638 }
18639
18640 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18641
18642 if auto_scroll {
18643 self.request_autoscroll(Autoscroll::fit(), cx);
18644 }
18645
18646 cx.notify();
18647
18648 self.scrollbar_marker_state.dirty = true;
18649 self.folds_did_change(cx);
18650 }
18651
18652 /// Removes any folds whose ranges intersect any of the given ranges.
18653 pub fn unfold_ranges<T: ToOffset + Clone>(
18654 &mut self,
18655 ranges: &[Range<T>],
18656 inclusive: bool,
18657 auto_scroll: bool,
18658 cx: &mut Context<Self>,
18659 ) {
18660 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18661 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18662 });
18663 self.folds_did_change(cx);
18664 }
18665
18666 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18667 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18668 return;
18669 }
18670 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18671 self.display_map.update(cx, |display_map, cx| {
18672 display_map.fold_buffers([buffer_id], cx)
18673 });
18674 cx.emit(EditorEvent::BufferFoldToggled {
18675 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18676 folded: true,
18677 });
18678 cx.notify();
18679 }
18680
18681 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18682 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18683 return;
18684 }
18685 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18686 self.display_map.update(cx, |display_map, cx| {
18687 display_map.unfold_buffers([buffer_id], cx);
18688 });
18689 cx.emit(EditorEvent::BufferFoldToggled {
18690 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18691 folded: false,
18692 });
18693 cx.notify();
18694 }
18695
18696 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18697 self.display_map.read(cx).is_buffer_folded(buffer)
18698 }
18699
18700 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18701 self.display_map.read(cx).folded_buffers()
18702 }
18703
18704 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18705 self.display_map.update(cx, |display_map, cx| {
18706 display_map.disable_header_for_buffer(buffer_id, cx);
18707 });
18708 cx.notify();
18709 }
18710
18711 /// Removes any folds with the given ranges.
18712 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18713 &mut self,
18714 ranges: &[Range<T>],
18715 type_id: TypeId,
18716 auto_scroll: bool,
18717 cx: &mut Context<Self>,
18718 ) {
18719 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18720 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18721 });
18722 self.folds_did_change(cx);
18723 }
18724
18725 fn remove_folds_with<T: ToOffset + Clone>(
18726 &mut self,
18727 ranges: &[Range<T>],
18728 auto_scroll: bool,
18729 cx: &mut Context<Self>,
18730 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18731 ) {
18732 if ranges.is_empty() {
18733 return;
18734 }
18735
18736 let mut buffers_affected = HashSet::default();
18737 let multi_buffer = self.buffer().read(cx);
18738 for range in ranges {
18739 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18740 buffers_affected.insert(buffer.read(cx).remote_id());
18741 };
18742 }
18743
18744 self.display_map.update(cx, update);
18745
18746 if auto_scroll {
18747 self.request_autoscroll(Autoscroll::fit(), cx);
18748 }
18749
18750 cx.notify();
18751 self.scrollbar_marker_state.dirty = true;
18752 self.active_indent_guides_state.dirty = true;
18753 }
18754
18755 pub fn update_renderer_widths(
18756 &mut self,
18757 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18758 cx: &mut Context<Self>,
18759 ) -> bool {
18760 self.display_map
18761 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18762 }
18763
18764 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18765 self.display_map.read(cx).fold_placeholder.clone()
18766 }
18767
18768 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18769 self.buffer.update(cx, |buffer, cx| {
18770 buffer.set_all_diff_hunks_expanded(cx);
18771 });
18772 }
18773
18774 pub fn expand_all_diff_hunks(
18775 &mut self,
18776 _: &ExpandAllDiffHunks,
18777 _window: &mut Window,
18778 cx: &mut Context<Self>,
18779 ) {
18780 self.buffer.update(cx, |buffer, cx| {
18781 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18782 });
18783 }
18784
18785 pub fn toggle_selected_diff_hunks(
18786 &mut self,
18787 _: &ToggleSelectedDiffHunks,
18788 _window: &mut Window,
18789 cx: &mut Context<Self>,
18790 ) {
18791 let ranges: Vec<_> = self
18792 .selections
18793 .disjoint_anchors()
18794 .iter()
18795 .map(|s| s.range())
18796 .collect();
18797 self.toggle_diff_hunks_in_ranges(ranges, cx);
18798 }
18799
18800 pub fn diff_hunks_in_ranges<'a>(
18801 &'a self,
18802 ranges: &'a [Range<Anchor>],
18803 buffer: &'a MultiBufferSnapshot,
18804 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18805 ranges.iter().flat_map(move |range| {
18806 let end_excerpt_id = range.end.excerpt_id;
18807 let range = range.to_point(buffer);
18808 let mut peek_end = range.end;
18809 if range.end.row < buffer.max_row().0 {
18810 peek_end = Point::new(range.end.row + 1, 0);
18811 }
18812 buffer
18813 .diff_hunks_in_range(range.start..peek_end)
18814 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18815 })
18816 }
18817
18818 pub fn has_stageable_diff_hunks_in_ranges(
18819 &self,
18820 ranges: &[Range<Anchor>],
18821 snapshot: &MultiBufferSnapshot,
18822 ) -> bool {
18823 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18824 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18825 }
18826
18827 pub fn toggle_staged_selected_diff_hunks(
18828 &mut self,
18829 _: &::git::ToggleStaged,
18830 _: &mut Window,
18831 cx: &mut Context<Self>,
18832 ) {
18833 let snapshot = self.buffer.read(cx).snapshot(cx);
18834 let ranges: Vec<_> = self
18835 .selections
18836 .disjoint_anchors()
18837 .iter()
18838 .map(|s| s.range())
18839 .collect();
18840 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18841 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18842 }
18843
18844 pub fn set_render_diff_hunk_controls(
18845 &mut self,
18846 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18847 cx: &mut Context<Self>,
18848 ) {
18849 self.render_diff_hunk_controls = render_diff_hunk_controls;
18850 cx.notify();
18851 }
18852
18853 pub fn stage_and_next(
18854 &mut self,
18855 _: &::git::StageAndNext,
18856 window: &mut Window,
18857 cx: &mut Context<Self>,
18858 ) {
18859 self.do_stage_or_unstage_and_next(true, window, cx);
18860 }
18861
18862 pub fn unstage_and_next(
18863 &mut self,
18864 _: &::git::UnstageAndNext,
18865 window: &mut Window,
18866 cx: &mut Context<Self>,
18867 ) {
18868 self.do_stage_or_unstage_and_next(false, window, cx);
18869 }
18870
18871 pub fn stage_or_unstage_diff_hunks(
18872 &mut self,
18873 stage: bool,
18874 ranges: Vec<Range<Anchor>>,
18875 cx: &mut Context<Self>,
18876 ) {
18877 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18878 cx.spawn(async move |this, cx| {
18879 task.await?;
18880 this.update(cx, |this, cx| {
18881 let snapshot = this.buffer.read(cx).snapshot(cx);
18882 let chunk_by = this
18883 .diff_hunks_in_ranges(&ranges, &snapshot)
18884 .chunk_by(|hunk| hunk.buffer_id);
18885 for (buffer_id, hunks) in &chunk_by {
18886 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18887 }
18888 })
18889 })
18890 .detach_and_log_err(cx);
18891 }
18892
18893 fn save_buffers_for_ranges_if_needed(
18894 &mut self,
18895 ranges: &[Range<Anchor>],
18896 cx: &mut Context<Editor>,
18897 ) -> Task<Result<()>> {
18898 let multibuffer = self.buffer.read(cx);
18899 let snapshot = multibuffer.read(cx);
18900 let buffer_ids: HashSet<_> = ranges
18901 .iter()
18902 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18903 .collect();
18904 drop(snapshot);
18905
18906 let mut buffers = HashSet::default();
18907 for buffer_id in buffer_ids {
18908 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18909 let buffer = buffer_entity.read(cx);
18910 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18911 {
18912 buffers.insert(buffer_entity);
18913 }
18914 }
18915 }
18916
18917 if let Some(project) = &self.project {
18918 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18919 } else {
18920 Task::ready(Ok(()))
18921 }
18922 }
18923
18924 fn do_stage_or_unstage_and_next(
18925 &mut self,
18926 stage: bool,
18927 window: &mut Window,
18928 cx: &mut Context<Self>,
18929 ) {
18930 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18931
18932 if ranges.iter().any(|range| range.start != range.end) {
18933 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18934 return;
18935 }
18936
18937 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18938 let snapshot = self.snapshot(window, cx);
18939 let position = self
18940 .selections
18941 .newest::<Point>(&snapshot.display_snapshot)
18942 .head();
18943 let mut row = snapshot
18944 .buffer_snapshot()
18945 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
18946 .find(|hunk| hunk.row_range.start.0 > position.row)
18947 .map(|hunk| hunk.row_range.start);
18948
18949 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18950 // Outside of the project diff editor, wrap around to the beginning.
18951 if !all_diff_hunks_expanded {
18952 row = row.or_else(|| {
18953 snapshot
18954 .buffer_snapshot()
18955 .diff_hunks_in_range(Point::zero()..position)
18956 .find(|hunk| hunk.row_range.end.0 < position.row)
18957 .map(|hunk| hunk.row_range.start)
18958 });
18959 }
18960
18961 if let Some(row) = row {
18962 let destination = Point::new(row.0, 0);
18963 let autoscroll = Autoscroll::center();
18964
18965 self.unfold_ranges(&[destination..destination], false, false, cx);
18966 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18967 s.select_ranges([destination..destination]);
18968 });
18969 }
18970 }
18971
18972 fn do_stage_or_unstage(
18973 &self,
18974 stage: bool,
18975 buffer_id: BufferId,
18976 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18977 cx: &mut App,
18978 ) -> Option<()> {
18979 let project = self.project()?;
18980 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18981 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18982 let buffer_snapshot = buffer.read(cx).snapshot();
18983 let file_exists = buffer_snapshot
18984 .file()
18985 .is_some_and(|file| file.disk_state().exists());
18986 diff.update(cx, |diff, cx| {
18987 diff.stage_or_unstage_hunks(
18988 stage,
18989 &hunks
18990 .map(|hunk| buffer_diff::DiffHunk {
18991 buffer_range: hunk.buffer_range,
18992 diff_base_byte_range: hunk.diff_base_byte_range,
18993 secondary_status: hunk.secondary_status,
18994 range: Point::zero()..Point::zero(), // unused
18995 })
18996 .collect::<Vec<_>>(),
18997 &buffer_snapshot,
18998 file_exists,
18999 cx,
19000 )
19001 });
19002 None
19003 }
19004
19005 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19006 let ranges: Vec<_> = self
19007 .selections
19008 .disjoint_anchors()
19009 .iter()
19010 .map(|s| s.range())
19011 .collect();
19012 self.buffer
19013 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19014 }
19015
19016 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19017 self.buffer.update(cx, |buffer, cx| {
19018 let ranges = vec![Anchor::min()..Anchor::max()];
19019 if !buffer.all_diff_hunks_expanded()
19020 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19021 {
19022 buffer.collapse_diff_hunks(ranges, cx);
19023 true
19024 } else {
19025 false
19026 }
19027 })
19028 }
19029
19030 fn toggle_diff_hunks_in_ranges(
19031 &mut self,
19032 ranges: Vec<Range<Anchor>>,
19033 cx: &mut Context<Editor>,
19034 ) {
19035 self.buffer.update(cx, |buffer, cx| {
19036 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19037 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19038 })
19039 }
19040
19041 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19042 self.buffer.update(cx, |buffer, cx| {
19043 let snapshot = buffer.snapshot(cx);
19044 let excerpt_id = range.end.excerpt_id;
19045 let point_range = range.to_point(&snapshot);
19046 let expand = !buffer.single_hunk_is_expanded(range, cx);
19047 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19048 })
19049 }
19050
19051 pub(crate) fn apply_all_diff_hunks(
19052 &mut self,
19053 _: &ApplyAllDiffHunks,
19054 window: &mut Window,
19055 cx: &mut Context<Self>,
19056 ) {
19057 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19058
19059 let buffers = self.buffer.read(cx).all_buffers();
19060 for branch_buffer in buffers {
19061 branch_buffer.update(cx, |branch_buffer, cx| {
19062 branch_buffer.merge_into_base(Vec::new(), cx);
19063 });
19064 }
19065
19066 if let Some(project) = self.project.clone() {
19067 self.save(
19068 SaveOptions {
19069 format: true,
19070 autosave: false,
19071 },
19072 project,
19073 window,
19074 cx,
19075 )
19076 .detach_and_log_err(cx);
19077 }
19078 }
19079
19080 pub(crate) fn apply_selected_diff_hunks(
19081 &mut self,
19082 _: &ApplyDiffHunk,
19083 window: &mut Window,
19084 cx: &mut Context<Self>,
19085 ) {
19086 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19087 let snapshot = self.snapshot(window, cx);
19088 let hunks = snapshot.hunks_for_ranges(
19089 self.selections
19090 .all(&snapshot.display_snapshot)
19091 .into_iter()
19092 .map(|selection| selection.range()),
19093 );
19094 let mut ranges_by_buffer = HashMap::default();
19095 self.transact(window, cx, |editor, _window, cx| {
19096 for hunk in hunks {
19097 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19098 ranges_by_buffer
19099 .entry(buffer.clone())
19100 .or_insert_with(Vec::new)
19101 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19102 }
19103 }
19104
19105 for (buffer, ranges) in ranges_by_buffer {
19106 buffer.update(cx, |buffer, cx| {
19107 buffer.merge_into_base(ranges, cx);
19108 });
19109 }
19110 });
19111
19112 if let Some(project) = self.project.clone() {
19113 self.save(
19114 SaveOptions {
19115 format: true,
19116 autosave: false,
19117 },
19118 project,
19119 window,
19120 cx,
19121 )
19122 .detach_and_log_err(cx);
19123 }
19124 }
19125
19126 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19127 if hovered != self.gutter_hovered {
19128 self.gutter_hovered = hovered;
19129 cx.notify();
19130 }
19131 }
19132
19133 pub fn insert_blocks(
19134 &mut self,
19135 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19136 autoscroll: Option<Autoscroll>,
19137 cx: &mut Context<Self>,
19138 ) -> Vec<CustomBlockId> {
19139 let blocks = self
19140 .display_map
19141 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19142 if let Some(autoscroll) = autoscroll {
19143 self.request_autoscroll(autoscroll, cx);
19144 }
19145 cx.notify();
19146 blocks
19147 }
19148
19149 pub fn resize_blocks(
19150 &mut self,
19151 heights: HashMap<CustomBlockId, u32>,
19152 autoscroll: Option<Autoscroll>,
19153 cx: &mut Context<Self>,
19154 ) {
19155 self.display_map
19156 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19157 if let Some(autoscroll) = autoscroll {
19158 self.request_autoscroll(autoscroll, cx);
19159 }
19160 cx.notify();
19161 }
19162
19163 pub fn replace_blocks(
19164 &mut self,
19165 renderers: HashMap<CustomBlockId, RenderBlock>,
19166 autoscroll: Option<Autoscroll>,
19167 cx: &mut Context<Self>,
19168 ) {
19169 self.display_map
19170 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19171 if let Some(autoscroll) = autoscroll {
19172 self.request_autoscroll(autoscroll, cx);
19173 }
19174 cx.notify();
19175 }
19176
19177 pub fn remove_blocks(
19178 &mut self,
19179 block_ids: HashSet<CustomBlockId>,
19180 autoscroll: Option<Autoscroll>,
19181 cx: &mut Context<Self>,
19182 ) {
19183 self.display_map.update(cx, |display_map, cx| {
19184 display_map.remove_blocks(block_ids, cx)
19185 });
19186 if let Some(autoscroll) = autoscroll {
19187 self.request_autoscroll(autoscroll, cx);
19188 }
19189 cx.notify();
19190 }
19191
19192 pub fn row_for_block(
19193 &self,
19194 block_id: CustomBlockId,
19195 cx: &mut Context<Self>,
19196 ) -> Option<DisplayRow> {
19197 self.display_map
19198 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19199 }
19200
19201 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19202 self.focused_block = Some(focused_block);
19203 }
19204
19205 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19206 self.focused_block.take()
19207 }
19208
19209 pub fn insert_creases(
19210 &mut self,
19211 creases: impl IntoIterator<Item = Crease<Anchor>>,
19212 cx: &mut Context<Self>,
19213 ) -> Vec<CreaseId> {
19214 self.display_map
19215 .update(cx, |map, cx| map.insert_creases(creases, cx))
19216 }
19217
19218 pub fn remove_creases(
19219 &mut self,
19220 ids: impl IntoIterator<Item = CreaseId>,
19221 cx: &mut Context<Self>,
19222 ) -> Vec<(CreaseId, Range<Anchor>)> {
19223 self.display_map
19224 .update(cx, |map, cx| map.remove_creases(ids, cx))
19225 }
19226
19227 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19228 self.display_map
19229 .update(cx, |map, cx| map.snapshot(cx))
19230 .longest_row()
19231 }
19232
19233 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19234 self.display_map
19235 .update(cx, |map, cx| map.snapshot(cx))
19236 .max_point()
19237 }
19238
19239 pub fn text(&self, cx: &App) -> String {
19240 self.buffer.read(cx).read(cx).text()
19241 }
19242
19243 pub fn is_empty(&self, cx: &App) -> bool {
19244 self.buffer.read(cx).read(cx).is_empty()
19245 }
19246
19247 pub fn text_option(&self, cx: &App) -> Option<String> {
19248 let text = self.text(cx);
19249 let text = text.trim();
19250
19251 if text.is_empty() {
19252 return None;
19253 }
19254
19255 Some(text.to_string())
19256 }
19257
19258 pub fn set_text(
19259 &mut self,
19260 text: impl Into<Arc<str>>,
19261 window: &mut Window,
19262 cx: &mut Context<Self>,
19263 ) {
19264 self.transact(window, cx, |this, _, cx| {
19265 this.buffer
19266 .read(cx)
19267 .as_singleton()
19268 .expect("you can only call set_text on editors for singleton buffers")
19269 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19270 });
19271 }
19272
19273 pub fn display_text(&self, cx: &mut App) -> String {
19274 self.display_map
19275 .update(cx, |map, cx| map.snapshot(cx))
19276 .text()
19277 }
19278
19279 fn create_minimap(
19280 &self,
19281 minimap_settings: MinimapSettings,
19282 window: &mut Window,
19283 cx: &mut Context<Self>,
19284 ) -> Option<Entity<Self>> {
19285 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19286 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19287 }
19288
19289 fn initialize_new_minimap(
19290 &self,
19291 minimap_settings: MinimapSettings,
19292 window: &mut Window,
19293 cx: &mut Context<Self>,
19294 ) -> Entity<Self> {
19295 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19296
19297 let mut minimap = Editor::new_internal(
19298 EditorMode::Minimap {
19299 parent: cx.weak_entity(),
19300 },
19301 self.buffer.clone(),
19302 None,
19303 Some(self.display_map.clone()),
19304 window,
19305 cx,
19306 );
19307 minimap.scroll_manager.clone_state(&self.scroll_manager);
19308 minimap.set_text_style_refinement(TextStyleRefinement {
19309 font_size: Some(MINIMAP_FONT_SIZE),
19310 font_weight: Some(MINIMAP_FONT_WEIGHT),
19311 ..Default::default()
19312 });
19313 minimap.update_minimap_configuration(minimap_settings, cx);
19314 cx.new(|_| minimap)
19315 }
19316
19317 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19318 let current_line_highlight = minimap_settings
19319 .current_line_highlight
19320 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19321 self.set_current_line_highlight(Some(current_line_highlight));
19322 }
19323
19324 pub fn minimap(&self) -> Option<&Entity<Self>> {
19325 self.minimap
19326 .as_ref()
19327 .filter(|_| self.minimap_visibility.visible())
19328 }
19329
19330 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19331 let mut wrap_guides = smallvec![];
19332
19333 if self.show_wrap_guides == Some(false) {
19334 return wrap_guides;
19335 }
19336
19337 let settings = self.buffer.read(cx).language_settings(cx);
19338 if settings.show_wrap_guides {
19339 match self.soft_wrap_mode(cx) {
19340 SoftWrap::Column(soft_wrap) => {
19341 wrap_guides.push((soft_wrap as usize, true));
19342 }
19343 SoftWrap::Bounded(soft_wrap) => {
19344 wrap_guides.push((soft_wrap as usize, true));
19345 }
19346 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19347 }
19348 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19349 }
19350
19351 wrap_guides
19352 }
19353
19354 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19355 let settings = self.buffer.read(cx).language_settings(cx);
19356 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19357 match mode {
19358 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19359 SoftWrap::None
19360 }
19361 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19362 language_settings::SoftWrap::PreferredLineLength => {
19363 SoftWrap::Column(settings.preferred_line_length)
19364 }
19365 language_settings::SoftWrap::Bounded => {
19366 SoftWrap::Bounded(settings.preferred_line_length)
19367 }
19368 }
19369 }
19370
19371 pub fn set_soft_wrap_mode(
19372 &mut self,
19373 mode: language_settings::SoftWrap,
19374
19375 cx: &mut Context<Self>,
19376 ) {
19377 self.soft_wrap_mode_override = Some(mode);
19378 cx.notify();
19379 }
19380
19381 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19382 self.hard_wrap = hard_wrap;
19383 cx.notify();
19384 }
19385
19386 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19387 self.text_style_refinement = Some(style);
19388 }
19389
19390 /// called by the Element so we know what style we were most recently rendered with.
19391 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19392 // We intentionally do not inform the display map about the minimap style
19393 // so that wrapping is not recalculated and stays consistent for the editor
19394 // and its linked minimap.
19395 if !self.mode.is_minimap() {
19396 let font = style.text.font();
19397 let font_size = style.text.font_size.to_pixels(window.rem_size());
19398 let display_map = self
19399 .placeholder_display_map
19400 .as_ref()
19401 .filter(|_| self.is_empty(cx))
19402 .unwrap_or(&self.display_map);
19403
19404 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19405 }
19406 self.style = Some(style);
19407 }
19408
19409 pub fn style(&self) -> Option<&EditorStyle> {
19410 self.style.as_ref()
19411 }
19412
19413 // Called by the element. This method is not designed to be called outside of the editor
19414 // element's layout code because it does not notify when rewrapping is computed synchronously.
19415 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19416 if self.is_empty(cx) {
19417 self.placeholder_display_map
19418 .as_ref()
19419 .map_or(false, |display_map| {
19420 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19421 })
19422 } else {
19423 self.display_map
19424 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19425 }
19426 }
19427
19428 pub fn set_soft_wrap(&mut self) {
19429 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19430 }
19431
19432 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19433 if self.soft_wrap_mode_override.is_some() {
19434 self.soft_wrap_mode_override.take();
19435 } else {
19436 let soft_wrap = match self.soft_wrap_mode(cx) {
19437 SoftWrap::GitDiff => return,
19438 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19439 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19440 language_settings::SoftWrap::None
19441 }
19442 };
19443 self.soft_wrap_mode_override = Some(soft_wrap);
19444 }
19445 cx.notify();
19446 }
19447
19448 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19449 let Some(workspace) = self.workspace() else {
19450 return;
19451 };
19452 let fs = workspace.read(cx).app_state().fs.clone();
19453 let current_show = TabBarSettings::get_global(cx).show;
19454 update_settings_file(fs, cx, move |setting, _| {
19455 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19456 });
19457 }
19458
19459 pub fn toggle_indent_guides(
19460 &mut self,
19461 _: &ToggleIndentGuides,
19462 _: &mut Window,
19463 cx: &mut Context<Self>,
19464 ) {
19465 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19466 self.buffer
19467 .read(cx)
19468 .language_settings(cx)
19469 .indent_guides
19470 .enabled
19471 });
19472 self.show_indent_guides = Some(!currently_enabled);
19473 cx.notify();
19474 }
19475
19476 fn should_show_indent_guides(&self) -> Option<bool> {
19477 self.show_indent_guides
19478 }
19479
19480 pub fn toggle_line_numbers(
19481 &mut self,
19482 _: &ToggleLineNumbers,
19483 _: &mut Window,
19484 cx: &mut Context<Self>,
19485 ) {
19486 let mut editor_settings = EditorSettings::get_global(cx).clone();
19487 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19488 EditorSettings::override_global(editor_settings, cx);
19489 }
19490
19491 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19492 if let Some(show_line_numbers) = self.show_line_numbers {
19493 return show_line_numbers;
19494 }
19495 EditorSettings::get_global(cx).gutter.line_numbers
19496 }
19497
19498 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19499 self.use_relative_line_numbers
19500 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19501 }
19502
19503 pub fn toggle_relative_line_numbers(
19504 &mut self,
19505 _: &ToggleRelativeLineNumbers,
19506 _: &mut Window,
19507 cx: &mut Context<Self>,
19508 ) {
19509 let is_relative = self.should_use_relative_line_numbers(cx);
19510 self.set_relative_line_number(Some(!is_relative), cx)
19511 }
19512
19513 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19514 self.use_relative_line_numbers = is_relative;
19515 cx.notify();
19516 }
19517
19518 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19519 self.show_gutter = show_gutter;
19520 cx.notify();
19521 }
19522
19523 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19524 self.show_scrollbars = ScrollbarAxes {
19525 horizontal: show,
19526 vertical: show,
19527 };
19528 cx.notify();
19529 }
19530
19531 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19532 self.show_scrollbars.vertical = show;
19533 cx.notify();
19534 }
19535
19536 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19537 self.show_scrollbars.horizontal = show;
19538 cx.notify();
19539 }
19540
19541 pub fn set_minimap_visibility(
19542 &mut self,
19543 minimap_visibility: MinimapVisibility,
19544 window: &mut Window,
19545 cx: &mut Context<Self>,
19546 ) {
19547 if self.minimap_visibility != minimap_visibility {
19548 if minimap_visibility.visible() && self.minimap.is_none() {
19549 let minimap_settings = EditorSettings::get_global(cx).minimap;
19550 self.minimap =
19551 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19552 }
19553 self.minimap_visibility = minimap_visibility;
19554 cx.notify();
19555 }
19556 }
19557
19558 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19559 self.set_show_scrollbars(false, cx);
19560 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19561 }
19562
19563 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19564 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19565 }
19566
19567 /// Normally the text in full mode and auto height editors is padded on the
19568 /// left side by roughly half a character width for improved hit testing.
19569 ///
19570 /// Use this method to disable this for cases where this is not wanted (e.g.
19571 /// if you want to align the editor text with some other text above or below)
19572 /// or if you want to add this padding to single-line editors.
19573 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19574 self.offset_content = offset_content;
19575 cx.notify();
19576 }
19577
19578 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19579 self.show_line_numbers = Some(show_line_numbers);
19580 cx.notify();
19581 }
19582
19583 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19584 self.disable_expand_excerpt_buttons = true;
19585 cx.notify();
19586 }
19587
19588 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19589 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19590 cx.notify();
19591 }
19592
19593 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19594 self.show_code_actions = Some(show_code_actions);
19595 cx.notify();
19596 }
19597
19598 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19599 self.show_runnables = Some(show_runnables);
19600 cx.notify();
19601 }
19602
19603 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19604 self.show_breakpoints = Some(show_breakpoints);
19605 cx.notify();
19606 }
19607
19608 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19609 if self.display_map.read(cx).masked != masked {
19610 self.display_map.update(cx, |map, _| map.masked = masked);
19611 }
19612 cx.notify()
19613 }
19614
19615 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19616 self.show_wrap_guides = Some(show_wrap_guides);
19617 cx.notify();
19618 }
19619
19620 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19621 self.show_indent_guides = Some(show_indent_guides);
19622 cx.notify();
19623 }
19624
19625 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19626 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19627 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19628 && let Some(dir) = file.abs_path(cx).parent()
19629 {
19630 return Some(dir.to_owned());
19631 }
19632 }
19633
19634 None
19635 }
19636
19637 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19638 self.active_excerpt(cx)?
19639 .1
19640 .read(cx)
19641 .file()
19642 .and_then(|f| f.as_local())
19643 }
19644
19645 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19646 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19647 let buffer = buffer.read(cx);
19648 if let Some(project_path) = buffer.project_path(cx) {
19649 let project = self.project()?.read(cx);
19650 project.absolute_path(&project_path, cx)
19651 } else {
19652 buffer
19653 .file()
19654 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19655 }
19656 })
19657 }
19658
19659 pub fn reveal_in_finder(
19660 &mut self,
19661 _: &RevealInFileManager,
19662 _window: &mut Window,
19663 cx: &mut Context<Self>,
19664 ) {
19665 if let Some(target) = self.target_file(cx) {
19666 cx.reveal_path(&target.abs_path(cx));
19667 }
19668 }
19669
19670 pub fn copy_path(
19671 &mut self,
19672 _: &zed_actions::workspace::CopyPath,
19673 _window: &mut Window,
19674 cx: &mut Context<Self>,
19675 ) {
19676 if let Some(path) = self.target_file_abs_path(cx)
19677 && let Some(path) = path.to_str()
19678 {
19679 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19680 } else {
19681 cx.propagate();
19682 }
19683 }
19684
19685 pub fn copy_relative_path(
19686 &mut self,
19687 _: &zed_actions::workspace::CopyRelativePath,
19688 _window: &mut Window,
19689 cx: &mut Context<Self>,
19690 ) {
19691 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19692 let project = self.project()?.read(cx);
19693 let path = buffer.read(cx).file()?.path();
19694 let path = path.display(project.path_style(cx));
19695 Some(path)
19696 }) {
19697 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19698 } else {
19699 cx.propagate();
19700 }
19701 }
19702
19703 /// Returns the project path for the editor's buffer, if any buffer is
19704 /// opened in the editor.
19705 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19706 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19707 buffer.read(cx).project_path(cx)
19708 } else {
19709 None
19710 }
19711 }
19712
19713 // Returns true if the editor handled a go-to-line request
19714 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19715 maybe!({
19716 let breakpoint_store = self.breakpoint_store.as_ref()?;
19717
19718 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19719 else {
19720 self.clear_row_highlights::<ActiveDebugLine>();
19721 return None;
19722 };
19723
19724 let position = active_stack_frame.position;
19725 let buffer_id = position.buffer_id?;
19726 let snapshot = self
19727 .project
19728 .as_ref()?
19729 .read(cx)
19730 .buffer_for_id(buffer_id, cx)?
19731 .read(cx)
19732 .snapshot();
19733
19734 let mut handled = false;
19735 for (id, ExcerptRange { context, .. }) in
19736 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19737 {
19738 if context.start.cmp(&position, &snapshot).is_ge()
19739 || context.end.cmp(&position, &snapshot).is_lt()
19740 {
19741 continue;
19742 }
19743 let snapshot = self.buffer.read(cx).snapshot(cx);
19744 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19745
19746 handled = true;
19747 self.clear_row_highlights::<ActiveDebugLine>();
19748
19749 self.go_to_line::<ActiveDebugLine>(
19750 multibuffer_anchor,
19751 Some(cx.theme().colors().editor_debugger_active_line_background),
19752 window,
19753 cx,
19754 );
19755
19756 cx.notify();
19757 }
19758
19759 handled.then_some(())
19760 })
19761 .is_some()
19762 }
19763
19764 pub fn copy_file_name_without_extension(
19765 &mut self,
19766 _: &CopyFileNameWithoutExtension,
19767 _: &mut Window,
19768 cx: &mut Context<Self>,
19769 ) {
19770 if let Some(file) = self.target_file(cx)
19771 && let Some(file_stem) = file.path().file_stem()
19772 {
19773 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19774 }
19775 }
19776
19777 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19778 if let Some(file) = self.target_file(cx)
19779 && let Some(name) = file.path().file_name()
19780 {
19781 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19782 }
19783 }
19784
19785 pub fn toggle_git_blame(
19786 &mut self,
19787 _: &::git::Blame,
19788 window: &mut Window,
19789 cx: &mut Context<Self>,
19790 ) {
19791 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19792
19793 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19794 self.start_git_blame(true, window, cx);
19795 }
19796
19797 cx.notify();
19798 }
19799
19800 pub fn toggle_git_blame_inline(
19801 &mut self,
19802 _: &ToggleGitBlameInline,
19803 window: &mut Window,
19804 cx: &mut Context<Self>,
19805 ) {
19806 self.toggle_git_blame_inline_internal(true, window, cx);
19807 cx.notify();
19808 }
19809
19810 pub fn open_git_blame_commit(
19811 &mut self,
19812 _: &OpenGitBlameCommit,
19813 window: &mut Window,
19814 cx: &mut Context<Self>,
19815 ) {
19816 self.open_git_blame_commit_internal(window, cx);
19817 }
19818
19819 fn open_git_blame_commit_internal(
19820 &mut self,
19821 window: &mut Window,
19822 cx: &mut Context<Self>,
19823 ) -> Option<()> {
19824 let blame = self.blame.as_ref()?;
19825 let snapshot = self.snapshot(window, cx);
19826 let cursor = self
19827 .selections
19828 .newest::<Point>(&snapshot.display_snapshot)
19829 .head();
19830 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
19831 let (_, blame_entry) = blame
19832 .update(cx, |blame, cx| {
19833 blame
19834 .blame_for_rows(
19835 &[RowInfo {
19836 buffer_id: Some(buffer.remote_id()),
19837 buffer_row: Some(point.row),
19838 ..Default::default()
19839 }],
19840 cx,
19841 )
19842 .next()
19843 })
19844 .flatten()?;
19845 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19846 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19847 let workspace = self.workspace()?.downgrade();
19848 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19849 None
19850 }
19851
19852 pub fn git_blame_inline_enabled(&self) -> bool {
19853 self.git_blame_inline_enabled
19854 }
19855
19856 pub fn toggle_selection_menu(
19857 &mut self,
19858 _: &ToggleSelectionMenu,
19859 _: &mut Window,
19860 cx: &mut Context<Self>,
19861 ) {
19862 self.show_selection_menu = self
19863 .show_selection_menu
19864 .map(|show_selections_menu| !show_selections_menu)
19865 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19866
19867 cx.notify();
19868 }
19869
19870 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19871 self.show_selection_menu
19872 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19873 }
19874
19875 fn start_git_blame(
19876 &mut self,
19877 user_triggered: bool,
19878 window: &mut Window,
19879 cx: &mut Context<Self>,
19880 ) {
19881 if let Some(project) = self.project() {
19882 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19883 && buffer.read(cx).file().is_none()
19884 {
19885 return;
19886 }
19887
19888 let focused = self.focus_handle(cx).contains_focused(window, cx);
19889
19890 let project = project.clone();
19891 let blame = cx
19892 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19893 self.blame_subscription =
19894 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19895 self.blame = Some(blame);
19896 }
19897 }
19898
19899 fn toggle_git_blame_inline_internal(
19900 &mut self,
19901 user_triggered: bool,
19902 window: &mut Window,
19903 cx: &mut Context<Self>,
19904 ) {
19905 if self.git_blame_inline_enabled {
19906 self.git_blame_inline_enabled = false;
19907 self.show_git_blame_inline = false;
19908 self.show_git_blame_inline_delay_task.take();
19909 } else {
19910 self.git_blame_inline_enabled = true;
19911 self.start_git_blame_inline(user_triggered, window, cx);
19912 }
19913
19914 cx.notify();
19915 }
19916
19917 fn start_git_blame_inline(
19918 &mut self,
19919 user_triggered: bool,
19920 window: &mut Window,
19921 cx: &mut Context<Self>,
19922 ) {
19923 self.start_git_blame(user_triggered, window, cx);
19924
19925 if ProjectSettings::get_global(cx)
19926 .git
19927 .inline_blame_delay()
19928 .is_some()
19929 {
19930 self.start_inline_blame_timer(window, cx);
19931 } else {
19932 self.show_git_blame_inline = true
19933 }
19934 }
19935
19936 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19937 self.blame.as_ref()
19938 }
19939
19940 pub fn show_git_blame_gutter(&self) -> bool {
19941 self.show_git_blame_gutter
19942 }
19943
19944 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19945 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19946 }
19947
19948 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19949 self.show_git_blame_inline
19950 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19951 && !self.newest_selection_head_on_empty_line(cx)
19952 && self.has_blame_entries(cx)
19953 }
19954
19955 fn has_blame_entries(&self, cx: &App) -> bool {
19956 self.blame()
19957 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19958 }
19959
19960 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19961 let cursor_anchor = self.selections.newest_anchor().head();
19962
19963 let snapshot = self.buffer.read(cx).snapshot(cx);
19964 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19965
19966 snapshot.line_len(buffer_row) == 0
19967 }
19968
19969 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19970 let buffer_and_selection = maybe!({
19971 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19972 let selection_range = selection.range();
19973
19974 let multi_buffer = self.buffer().read(cx);
19975 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19976 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19977
19978 let (buffer, range, _) = if selection.reversed {
19979 buffer_ranges.first()
19980 } else {
19981 buffer_ranges.last()
19982 }?;
19983
19984 let selection = text::ToPoint::to_point(&range.start, buffer).row
19985 ..text::ToPoint::to_point(&range.end, buffer).row;
19986 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19987 });
19988
19989 let Some((buffer, selection)) = buffer_and_selection else {
19990 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19991 };
19992
19993 let Some(project) = self.project() else {
19994 return Task::ready(Err(anyhow!("editor does not have project")));
19995 };
19996
19997 project.update(cx, |project, cx| {
19998 project.get_permalink_to_line(&buffer, selection, cx)
19999 })
20000 }
20001
20002 pub fn copy_permalink_to_line(
20003 &mut self,
20004 _: &CopyPermalinkToLine,
20005 window: &mut Window,
20006 cx: &mut Context<Self>,
20007 ) {
20008 let permalink_task = self.get_permalink_to_line(cx);
20009 let workspace = self.workspace();
20010
20011 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20012 Ok(permalink) => {
20013 cx.update(|_, cx| {
20014 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20015 })
20016 .ok();
20017 }
20018 Err(err) => {
20019 let message = format!("Failed to copy permalink: {err}");
20020
20021 anyhow::Result::<()>::Err(err).log_err();
20022
20023 if let Some(workspace) = workspace {
20024 workspace
20025 .update_in(cx, |workspace, _, cx| {
20026 struct CopyPermalinkToLine;
20027
20028 workspace.show_toast(
20029 Toast::new(
20030 NotificationId::unique::<CopyPermalinkToLine>(),
20031 message,
20032 ),
20033 cx,
20034 )
20035 })
20036 .ok();
20037 }
20038 }
20039 })
20040 .detach();
20041 }
20042
20043 pub fn copy_file_location(
20044 &mut self,
20045 _: &CopyFileLocation,
20046 _: &mut Window,
20047 cx: &mut Context<Self>,
20048 ) {
20049 let selection = self
20050 .selections
20051 .newest::<Point>(&self.display_snapshot(cx))
20052 .start
20053 .row
20054 + 1;
20055 if let Some(file) = self.target_file(cx) {
20056 let path = file.path().display(file.path_style(cx));
20057 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
20058 }
20059 }
20060
20061 pub fn open_permalink_to_line(
20062 &mut self,
20063 _: &OpenPermalinkToLine,
20064 window: &mut Window,
20065 cx: &mut Context<Self>,
20066 ) {
20067 let permalink_task = self.get_permalink_to_line(cx);
20068 let workspace = self.workspace();
20069
20070 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20071 Ok(permalink) => {
20072 cx.update(|_, cx| {
20073 cx.open_url(permalink.as_ref());
20074 })
20075 .ok();
20076 }
20077 Err(err) => {
20078 let message = format!("Failed to open permalink: {err}");
20079
20080 anyhow::Result::<()>::Err(err).log_err();
20081
20082 if let Some(workspace) = workspace {
20083 workspace
20084 .update(cx, |workspace, cx| {
20085 struct OpenPermalinkToLine;
20086
20087 workspace.show_toast(
20088 Toast::new(
20089 NotificationId::unique::<OpenPermalinkToLine>(),
20090 message,
20091 ),
20092 cx,
20093 )
20094 })
20095 .ok();
20096 }
20097 }
20098 })
20099 .detach();
20100 }
20101
20102 pub fn insert_uuid_v4(
20103 &mut self,
20104 _: &InsertUuidV4,
20105 window: &mut Window,
20106 cx: &mut Context<Self>,
20107 ) {
20108 self.insert_uuid(UuidVersion::V4, window, cx);
20109 }
20110
20111 pub fn insert_uuid_v7(
20112 &mut self,
20113 _: &InsertUuidV7,
20114 window: &mut Window,
20115 cx: &mut Context<Self>,
20116 ) {
20117 self.insert_uuid(UuidVersion::V7, window, cx);
20118 }
20119
20120 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20121 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20122 self.transact(window, cx, |this, window, cx| {
20123 let edits = this
20124 .selections
20125 .all::<Point>(&this.display_snapshot(cx))
20126 .into_iter()
20127 .map(|selection| {
20128 let uuid = match version {
20129 UuidVersion::V4 => uuid::Uuid::new_v4(),
20130 UuidVersion::V7 => uuid::Uuid::now_v7(),
20131 };
20132
20133 (selection.range(), uuid.to_string())
20134 });
20135 this.edit(edits, cx);
20136 this.refresh_edit_prediction(true, false, window, cx);
20137 });
20138 }
20139
20140 pub fn open_selections_in_multibuffer(
20141 &mut self,
20142 _: &OpenSelectionsInMultibuffer,
20143 window: &mut Window,
20144 cx: &mut Context<Self>,
20145 ) {
20146 let multibuffer = self.buffer.read(cx);
20147
20148 let Some(buffer) = multibuffer.as_singleton() else {
20149 return;
20150 };
20151
20152 let Some(workspace) = self.workspace() else {
20153 return;
20154 };
20155
20156 let title = multibuffer.title(cx).to_string();
20157
20158 let locations = self
20159 .selections
20160 .all_anchors(cx)
20161 .iter()
20162 .map(|selection| {
20163 (
20164 buffer.clone(),
20165 (selection.start.text_anchor..selection.end.text_anchor)
20166 .to_point(buffer.read(cx)),
20167 )
20168 })
20169 .into_group_map();
20170
20171 cx.spawn_in(window, async move |_, cx| {
20172 workspace.update_in(cx, |workspace, window, cx| {
20173 Self::open_locations_in_multibuffer(
20174 workspace,
20175 locations,
20176 format!("Selections for '{title}'"),
20177 false,
20178 MultibufferSelectionMode::All,
20179 window,
20180 cx,
20181 );
20182 })
20183 })
20184 .detach();
20185 }
20186
20187 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20188 /// last highlight added will be used.
20189 ///
20190 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20191 pub fn highlight_rows<T: 'static>(
20192 &mut self,
20193 range: Range<Anchor>,
20194 color: Hsla,
20195 options: RowHighlightOptions,
20196 cx: &mut Context<Self>,
20197 ) {
20198 let snapshot = self.buffer().read(cx).snapshot(cx);
20199 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20200 let ix = row_highlights.binary_search_by(|highlight| {
20201 Ordering::Equal
20202 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20203 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20204 });
20205
20206 if let Err(mut ix) = ix {
20207 let index = post_inc(&mut self.highlight_order);
20208
20209 // If this range intersects with the preceding highlight, then merge it with
20210 // the preceding highlight. Otherwise insert a new highlight.
20211 let mut merged = false;
20212 if ix > 0 {
20213 let prev_highlight = &mut row_highlights[ix - 1];
20214 if prev_highlight
20215 .range
20216 .end
20217 .cmp(&range.start, &snapshot)
20218 .is_ge()
20219 {
20220 ix -= 1;
20221 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20222 prev_highlight.range.end = range.end;
20223 }
20224 merged = true;
20225 prev_highlight.index = index;
20226 prev_highlight.color = color;
20227 prev_highlight.options = options;
20228 }
20229 }
20230
20231 if !merged {
20232 row_highlights.insert(
20233 ix,
20234 RowHighlight {
20235 range,
20236 index,
20237 color,
20238 options,
20239 type_id: TypeId::of::<T>(),
20240 },
20241 );
20242 }
20243
20244 // If any of the following highlights intersect with this one, merge them.
20245 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20246 let highlight = &row_highlights[ix];
20247 if next_highlight
20248 .range
20249 .start
20250 .cmp(&highlight.range.end, &snapshot)
20251 .is_le()
20252 {
20253 if next_highlight
20254 .range
20255 .end
20256 .cmp(&highlight.range.end, &snapshot)
20257 .is_gt()
20258 {
20259 row_highlights[ix].range.end = next_highlight.range.end;
20260 }
20261 row_highlights.remove(ix + 1);
20262 } else {
20263 break;
20264 }
20265 }
20266 }
20267 }
20268
20269 /// Remove any highlighted row ranges of the given type that intersect the
20270 /// given ranges.
20271 pub fn remove_highlighted_rows<T: 'static>(
20272 &mut self,
20273 ranges_to_remove: Vec<Range<Anchor>>,
20274 cx: &mut Context<Self>,
20275 ) {
20276 let snapshot = self.buffer().read(cx).snapshot(cx);
20277 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20278 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20279 row_highlights.retain(|highlight| {
20280 while let Some(range_to_remove) = ranges_to_remove.peek() {
20281 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20282 Ordering::Less | Ordering::Equal => {
20283 ranges_to_remove.next();
20284 }
20285 Ordering::Greater => {
20286 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20287 Ordering::Less | Ordering::Equal => {
20288 return false;
20289 }
20290 Ordering::Greater => break,
20291 }
20292 }
20293 }
20294 }
20295
20296 true
20297 })
20298 }
20299
20300 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20301 pub fn clear_row_highlights<T: 'static>(&mut self) {
20302 self.highlighted_rows.remove(&TypeId::of::<T>());
20303 }
20304
20305 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20306 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20307 self.highlighted_rows
20308 .get(&TypeId::of::<T>())
20309 .map_or(&[] as &[_], |vec| vec.as_slice())
20310 .iter()
20311 .map(|highlight| (highlight.range.clone(), highlight.color))
20312 }
20313
20314 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20315 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20316 /// Allows to ignore certain kinds of highlights.
20317 pub fn highlighted_display_rows(
20318 &self,
20319 window: &mut Window,
20320 cx: &mut App,
20321 ) -> BTreeMap<DisplayRow, LineHighlight> {
20322 let snapshot = self.snapshot(window, cx);
20323 let mut used_highlight_orders = HashMap::default();
20324 self.highlighted_rows
20325 .iter()
20326 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20327 .fold(
20328 BTreeMap::<DisplayRow, LineHighlight>::new(),
20329 |mut unique_rows, highlight| {
20330 let start = highlight.range.start.to_display_point(&snapshot);
20331 let end = highlight.range.end.to_display_point(&snapshot);
20332 let start_row = start.row().0;
20333 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20334 && end.column() == 0
20335 {
20336 end.row().0.saturating_sub(1)
20337 } else {
20338 end.row().0
20339 };
20340 for row in start_row..=end_row {
20341 let used_index =
20342 used_highlight_orders.entry(row).or_insert(highlight.index);
20343 if highlight.index >= *used_index {
20344 *used_index = highlight.index;
20345 unique_rows.insert(
20346 DisplayRow(row),
20347 LineHighlight {
20348 include_gutter: highlight.options.include_gutter,
20349 border: None,
20350 background: highlight.color.into(),
20351 type_id: Some(highlight.type_id),
20352 },
20353 );
20354 }
20355 }
20356 unique_rows
20357 },
20358 )
20359 }
20360
20361 pub fn highlighted_display_row_for_autoscroll(
20362 &self,
20363 snapshot: &DisplaySnapshot,
20364 ) -> Option<DisplayRow> {
20365 self.highlighted_rows
20366 .values()
20367 .flat_map(|highlighted_rows| highlighted_rows.iter())
20368 .filter_map(|highlight| {
20369 if highlight.options.autoscroll {
20370 Some(highlight.range.start.to_display_point(snapshot).row())
20371 } else {
20372 None
20373 }
20374 })
20375 .min()
20376 }
20377
20378 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20379 self.highlight_background::<SearchWithinRange>(
20380 ranges,
20381 |colors| colors.colors().editor_document_highlight_read_background,
20382 cx,
20383 )
20384 }
20385
20386 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20387 self.breadcrumb_header = Some(new_header);
20388 }
20389
20390 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20391 self.clear_background_highlights::<SearchWithinRange>(cx);
20392 }
20393
20394 pub fn highlight_background<T: 'static>(
20395 &mut self,
20396 ranges: &[Range<Anchor>],
20397 color_fetcher: fn(&Theme) -> Hsla,
20398 cx: &mut Context<Self>,
20399 ) {
20400 self.background_highlights.insert(
20401 HighlightKey::Type(TypeId::of::<T>()),
20402 (color_fetcher, Arc::from(ranges)),
20403 );
20404 self.scrollbar_marker_state.dirty = true;
20405 cx.notify();
20406 }
20407
20408 pub fn highlight_background_key<T: 'static>(
20409 &mut self,
20410 key: usize,
20411 ranges: &[Range<Anchor>],
20412 color_fetcher: fn(&Theme) -> Hsla,
20413 cx: &mut Context<Self>,
20414 ) {
20415 self.background_highlights.insert(
20416 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20417 (color_fetcher, Arc::from(ranges)),
20418 );
20419 self.scrollbar_marker_state.dirty = true;
20420 cx.notify();
20421 }
20422
20423 pub fn clear_background_highlights<T: 'static>(
20424 &mut self,
20425 cx: &mut Context<Self>,
20426 ) -> Option<BackgroundHighlight> {
20427 let text_highlights = self
20428 .background_highlights
20429 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20430 if !text_highlights.1.is_empty() {
20431 self.scrollbar_marker_state.dirty = true;
20432 cx.notify();
20433 }
20434 Some(text_highlights)
20435 }
20436
20437 pub fn highlight_gutter<T: 'static>(
20438 &mut self,
20439 ranges: impl Into<Vec<Range<Anchor>>>,
20440 color_fetcher: fn(&App) -> Hsla,
20441 cx: &mut Context<Self>,
20442 ) {
20443 self.gutter_highlights
20444 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20445 cx.notify();
20446 }
20447
20448 pub fn clear_gutter_highlights<T: 'static>(
20449 &mut self,
20450 cx: &mut Context<Self>,
20451 ) -> Option<GutterHighlight> {
20452 cx.notify();
20453 self.gutter_highlights.remove(&TypeId::of::<T>())
20454 }
20455
20456 pub fn insert_gutter_highlight<T: 'static>(
20457 &mut self,
20458 range: Range<Anchor>,
20459 color_fetcher: fn(&App) -> Hsla,
20460 cx: &mut Context<Self>,
20461 ) {
20462 let snapshot = self.buffer().read(cx).snapshot(cx);
20463 let mut highlights = self
20464 .gutter_highlights
20465 .remove(&TypeId::of::<T>())
20466 .map(|(_, highlights)| highlights)
20467 .unwrap_or_default();
20468 let ix = highlights.binary_search_by(|highlight| {
20469 Ordering::Equal
20470 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20471 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20472 });
20473 if let Err(ix) = ix {
20474 highlights.insert(ix, range);
20475 }
20476 self.gutter_highlights
20477 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20478 }
20479
20480 pub fn remove_gutter_highlights<T: 'static>(
20481 &mut self,
20482 ranges_to_remove: Vec<Range<Anchor>>,
20483 cx: &mut Context<Self>,
20484 ) {
20485 let snapshot = self.buffer().read(cx).snapshot(cx);
20486 let Some((color_fetcher, mut gutter_highlights)) =
20487 self.gutter_highlights.remove(&TypeId::of::<T>())
20488 else {
20489 return;
20490 };
20491 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20492 gutter_highlights.retain(|highlight| {
20493 while let Some(range_to_remove) = ranges_to_remove.peek() {
20494 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20495 Ordering::Less | Ordering::Equal => {
20496 ranges_to_remove.next();
20497 }
20498 Ordering::Greater => {
20499 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20500 Ordering::Less | Ordering::Equal => {
20501 return false;
20502 }
20503 Ordering::Greater => break,
20504 }
20505 }
20506 }
20507 }
20508
20509 true
20510 });
20511 self.gutter_highlights
20512 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20513 }
20514
20515 #[cfg(feature = "test-support")]
20516 pub fn all_text_highlights(
20517 &self,
20518 window: &mut Window,
20519 cx: &mut Context<Self>,
20520 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20521 let snapshot = self.snapshot(window, cx);
20522 self.display_map.update(cx, |display_map, _| {
20523 display_map
20524 .all_text_highlights()
20525 .map(|highlight| {
20526 let (style, ranges) = highlight.as_ref();
20527 (
20528 *style,
20529 ranges
20530 .iter()
20531 .map(|range| range.clone().to_display_points(&snapshot))
20532 .collect(),
20533 )
20534 })
20535 .collect()
20536 })
20537 }
20538
20539 #[cfg(feature = "test-support")]
20540 pub fn all_text_background_highlights(
20541 &self,
20542 window: &mut Window,
20543 cx: &mut Context<Self>,
20544 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20545 let snapshot = self.snapshot(window, cx);
20546 let buffer = &snapshot.buffer_snapshot();
20547 let start = buffer.anchor_before(0);
20548 let end = buffer.anchor_after(buffer.len());
20549 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20550 }
20551
20552 #[cfg(any(test, feature = "test-support"))]
20553 pub fn sorted_background_highlights_in_range(
20554 &self,
20555 search_range: Range<Anchor>,
20556 display_snapshot: &DisplaySnapshot,
20557 theme: &Theme,
20558 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20559 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20560 res.sort_by(|a, b| {
20561 a.0.start
20562 .cmp(&b.0.start)
20563 .then_with(|| a.0.end.cmp(&b.0.end))
20564 .then_with(|| a.1.cmp(&b.1))
20565 });
20566 res
20567 }
20568
20569 #[cfg(feature = "test-support")]
20570 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20571 let snapshot = self.buffer().read(cx).snapshot(cx);
20572
20573 let highlights = self
20574 .background_highlights
20575 .get(&HighlightKey::Type(TypeId::of::<
20576 items::BufferSearchHighlights,
20577 >()));
20578
20579 if let Some((_color, ranges)) = highlights {
20580 ranges
20581 .iter()
20582 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20583 .collect_vec()
20584 } else {
20585 vec![]
20586 }
20587 }
20588
20589 fn document_highlights_for_position<'a>(
20590 &'a self,
20591 position: Anchor,
20592 buffer: &'a MultiBufferSnapshot,
20593 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20594 let read_highlights = self
20595 .background_highlights
20596 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20597 .map(|h| &h.1);
20598 let write_highlights = self
20599 .background_highlights
20600 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20601 .map(|h| &h.1);
20602 let left_position = position.bias_left(buffer);
20603 let right_position = position.bias_right(buffer);
20604 read_highlights
20605 .into_iter()
20606 .chain(write_highlights)
20607 .flat_map(move |ranges| {
20608 let start_ix = match ranges.binary_search_by(|probe| {
20609 let cmp = probe.end.cmp(&left_position, buffer);
20610 if cmp.is_ge() {
20611 Ordering::Greater
20612 } else {
20613 Ordering::Less
20614 }
20615 }) {
20616 Ok(i) | Err(i) => i,
20617 };
20618
20619 ranges[start_ix..]
20620 .iter()
20621 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20622 })
20623 }
20624
20625 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20626 self.background_highlights
20627 .get(&HighlightKey::Type(TypeId::of::<T>()))
20628 .is_some_and(|(_, highlights)| !highlights.is_empty())
20629 }
20630
20631 /// Returns all background highlights for a given range.
20632 ///
20633 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20634 pub fn background_highlights_in_range(
20635 &self,
20636 search_range: Range<Anchor>,
20637 display_snapshot: &DisplaySnapshot,
20638 theme: &Theme,
20639 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20640 let mut results = Vec::new();
20641 for (color_fetcher, ranges) in self.background_highlights.values() {
20642 let color = color_fetcher(theme);
20643 let start_ix = match ranges.binary_search_by(|probe| {
20644 let cmp = probe
20645 .end
20646 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20647 if cmp.is_gt() {
20648 Ordering::Greater
20649 } else {
20650 Ordering::Less
20651 }
20652 }) {
20653 Ok(i) | Err(i) => i,
20654 };
20655 for range in &ranges[start_ix..] {
20656 if range
20657 .start
20658 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20659 .is_ge()
20660 {
20661 break;
20662 }
20663
20664 let start = range.start.to_display_point(display_snapshot);
20665 let end = range.end.to_display_point(display_snapshot);
20666 results.push((start..end, color))
20667 }
20668 }
20669 results
20670 }
20671
20672 pub fn gutter_highlights_in_range(
20673 &self,
20674 search_range: Range<Anchor>,
20675 display_snapshot: &DisplaySnapshot,
20676 cx: &App,
20677 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20678 let mut results = Vec::new();
20679 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20680 let color = color_fetcher(cx);
20681 let start_ix = match ranges.binary_search_by(|probe| {
20682 let cmp = probe
20683 .end
20684 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20685 if cmp.is_gt() {
20686 Ordering::Greater
20687 } else {
20688 Ordering::Less
20689 }
20690 }) {
20691 Ok(i) | Err(i) => i,
20692 };
20693 for range in &ranges[start_ix..] {
20694 if range
20695 .start
20696 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20697 .is_ge()
20698 {
20699 break;
20700 }
20701
20702 let start = range.start.to_display_point(display_snapshot);
20703 let end = range.end.to_display_point(display_snapshot);
20704 results.push((start..end, color))
20705 }
20706 }
20707 results
20708 }
20709
20710 /// Get the text ranges corresponding to the redaction query
20711 pub fn redacted_ranges(
20712 &self,
20713 search_range: Range<Anchor>,
20714 display_snapshot: &DisplaySnapshot,
20715 cx: &App,
20716 ) -> Vec<Range<DisplayPoint>> {
20717 display_snapshot
20718 .buffer_snapshot()
20719 .redacted_ranges(search_range, |file| {
20720 if let Some(file) = file {
20721 file.is_private()
20722 && EditorSettings::get(
20723 Some(SettingsLocation {
20724 worktree_id: file.worktree_id(cx),
20725 path: file.path().as_ref(),
20726 }),
20727 cx,
20728 )
20729 .redact_private_values
20730 } else {
20731 false
20732 }
20733 })
20734 .map(|range| {
20735 range.start.to_display_point(display_snapshot)
20736 ..range.end.to_display_point(display_snapshot)
20737 })
20738 .collect()
20739 }
20740
20741 pub fn highlight_text_key<T: 'static>(
20742 &mut self,
20743 key: usize,
20744 ranges: Vec<Range<Anchor>>,
20745 style: HighlightStyle,
20746 cx: &mut Context<Self>,
20747 ) {
20748 self.display_map.update(cx, |map, _| {
20749 map.highlight_text(
20750 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20751 ranges,
20752 style,
20753 );
20754 });
20755 cx.notify();
20756 }
20757
20758 pub fn highlight_text<T: 'static>(
20759 &mut self,
20760 ranges: Vec<Range<Anchor>>,
20761 style: HighlightStyle,
20762 cx: &mut Context<Self>,
20763 ) {
20764 self.display_map.update(cx, |map, _| {
20765 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20766 });
20767 cx.notify();
20768 }
20769
20770 pub(crate) fn highlight_inlays<T: 'static>(
20771 &mut self,
20772 highlights: Vec<InlayHighlight>,
20773 style: HighlightStyle,
20774 cx: &mut Context<Self>,
20775 ) {
20776 self.display_map.update(cx, |map, _| {
20777 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20778 });
20779 cx.notify();
20780 }
20781
20782 pub fn text_highlights<'a, T: 'static>(
20783 &'a self,
20784 cx: &'a App,
20785 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20786 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20787 }
20788
20789 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20790 let cleared = self
20791 .display_map
20792 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20793 if cleared {
20794 cx.notify();
20795 }
20796 }
20797
20798 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20799 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20800 && self.focus_handle.is_focused(window)
20801 }
20802
20803 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20804 self.show_cursor_when_unfocused = is_enabled;
20805 cx.notify();
20806 }
20807
20808 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20809 cx.notify();
20810 }
20811
20812 fn on_debug_session_event(
20813 &mut self,
20814 _session: Entity<Session>,
20815 event: &SessionEvent,
20816 cx: &mut Context<Self>,
20817 ) {
20818 if let SessionEvent::InvalidateInlineValue = event {
20819 self.refresh_inline_values(cx);
20820 }
20821 }
20822
20823 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20824 let Some(project) = self.project.clone() else {
20825 return;
20826 };
20827
20828 if !self.inline_value_cache.enabled {
20829 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20830 self.splice_inlays(&inlays, Vec::new(), cx);
20831 return;
20832 }
20833
20834 let current_execution_position = self
20835 .highlighted_rows
20836 .get(&TypeId::of::<ActiveDebugLine>())
20837 .and_then(|lines| lines.last().map(|line| line.range.end));
20838
20839 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20840 let inline_values = editor
20841 .update(cx, |editor, cx| {
20842 let Some(current_execution_position) = current_execution_position else {
20843 return Some(Task::ready(Ok(Vec::new())));
20844 };
20845
20846 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20847 let snapshot = buffer.snapshot(cx);
20848
20849 let excerpt = snapshot.excerpt_containing(
20850 current_execution_position..current_execution_position,
20851 )?;
20852
20853 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20854 })?;
20855
20856 let range =
20857 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20858
20859 project.inline_values(buffer, range, cx)
20860 })
20861 .ok()
20862 .flatten()?
20863 .await
20864 .context("refreshing debugger inlays")
20865 .log_err()?;
20866
20867 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20868
20869 for (buffer_id, inline_value) in inline_values
20870 .into_iter()
20871 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20872 {
20873 buffer_inline_values
20874 .entry(buffer_id)
20875 .or_default()
20876 .push(inline_value);
20877 }
20878
20879 editor
20880 .update(cx, |editor, cx| {
20881 let snapshot = editor.buffer.read(cx).snapshot(cx);
20882 let mut new_inlays = Vec::default();
20883
20884 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20885 let buffer_id = buffer_snapshot.remote_id();
20886 buffer_inline_values
20887 .get(&buffer_id)
20888 .into_iter()
20889 .flatten()
20890 .for_each(|hint| {
20891 let inlay = Inlay::debugger(
20892 post_inc(&mut editor.next_inlay_id),
20893 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20894 hint.text(),
20895 );
20896 if !inlay.text().chars().contains(&'\n') {
20897 new_inlays.push(inlay);
20898 }
20899 });
20900 }
20901
20902 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20903 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20904
20905 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20906 })
20907 .ok()?;
20908 Some(())
20909 });
20910 }
20911
20912 fn on_buffer_event(
20913 &mut self,
20914 multibuffer: &Entity<MultiBuffer>,
20915 event: &multi_buffer::Event,
20916 window: &mut Window,
20917 cx: &mut Context<Self>,
20918 ) {
20919 match event {
20920 multi_buffer::Event::Edited { edited_buffer } => {
20921 self.scrollbar_marker_state.dirty = true;
20922 self.active_indent_guides_state.dirty = true;
20923 self.refresh_active_diagnostics(cx);
20924 self.refresh_code_actions(window, cx);
20925 self.refresh_selected_text_highlights(true, window, cx);
20926 self.refresh_single_line_folds(window, cx);
20927 refresh_matching_bracket_highlights(self, cx);
20928 if self.has_active_edit_prediction() {
20929 self.update_visible_edit_prediction(window, cx);
20930 }
20931
20932 if let Some(edited_buffer) = edited_buffer {
20933 if edited_buffer.read(cx).file().is_none() {
20934 cx.emit(EditorEvent::TitleChanged);
20935 }
20936
20937 let buffer_id = edited_buffer.read(cx).remote_id();
20938 if let Some(project) = self.project.clone() {
20939 self.register_buffer(buffer_id, cx);
20940 self.update_lsp_data(Some(buffer_id), window, cx);
20941 #[allow(clippy::mutable_key_type)]
20942 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20943 multibuffer
20944 .all_buffers()
20945 .into_iter()
20946 .filter_map(|buffer| {
20947 buffer.update(cx, |buffer, cx| {
20948 let language = buffer.language()?;
20949 let should_discard = project.update(cx, |project, cx| {
20950 project.is_local()
20951 && !project.has_language_servers_for(buffer, cx)
20952 });
20953 should_discard.not().then_some(language.clone())
20954 })
20955 })
20956 .collect::<HashSet<_>>()
20957 });
20958 if !languages_affected.is_empty() {
20959 self.refresh_inlay_hints(
20960 InlayHintRefreshReason::BufferEdited(languages_affected),
20961 cx,
20962 );
20963 }
20964 }
20965 }
20966
20967 cx.emit(EditorEvent::BufferEdited);
20968 cx.emit(SearchEvent::MatchesInvalidated);
20969
20970 let Some(project) = &self.project else { return };
20971 let (telemetry, is_via_ssh) = {
20972 let project = project.read(cx);
20973 let telemetry = project.client().telemetry().clone();
20974 let is_via_ssh = project.is_via_remote_server();
20975 (telemetry, is_via_ssh)
20976 };
20977 telemetry.log_edit_event("editor", is_via_ssh);
20978 }
20979 multi_buffer::Event::ExcerptsAdded {
20980 buffer,
20981 predecessor,
20982 excerpts,
20983 } => {
20984 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20985 let buffer_id = buffer.read(cx).remote_id();
20986 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20987 && let Some(project) = &self.project
20988 {
20989 update_uncommitted_diff_for_buffer(
20990 cx.entity(),
20991 project,
20992 [buffer.clone()],
20993 self.buffer.clone(),
20994 cx,
20995 )
20996 .detach();
20997 }
20998 self.update_lsp_data(Some(buffer_id), window, cx);
20999 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21000 cx.emit(EditorEvent::ExcerptsAdded {
21001 buffer: buffer.clone(),
21002 predecessor: *predecessor,
21003 excerpts: excerpts.clone(),
21004 });
21005 }
21006 multi_buffer::Event::ExcerptsRemoved {
21007 ids,
21008 removed_buffer_ids,
21009 } => {
21010 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21011 for buffer_id in removed_buffer_ids {
21012 self.registered_buffers.remove(buffer_id);
21013 }
21014 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21015 cx.emit(EditorEvent::ExcerptsRemoved {
21016 ids: ids.clone(),
21017 removed_buffer_ids: removed_buffer_ids.clone(),
21018 });
21019 }
21020 multi_buffer::Event::ExcerptsEdited {
21021 excerpt_ids,
21022 buffer_ids,
21023 } => {
21024 self.display_map.update(cx, |map, cx| {
21025 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21026 });
21027 cx.emit(EditorEvent::ExcerptsEdited {
21028 ids: excerpt_ids.clone(),
21029 });
21030 }
21031 multi_buffer::Event::ExcerptsExpanded { ids } => {
21032 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21033 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21034 }
21035 multi_buffer::Event::Reparsed(buffer_id) => {
21036 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21037 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21038
21039 cx.emit(EditorEvent::Reparsed(*buffer_id));
21040 }
21041 multi_buffer::Event::DiffHunksToggled => {
21042 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21043 }
21044 multi_buffer::Event::LanguageChanged(buffer_id) => {
21045 self.registered_buffers.remove(&buffer_id);
21046 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21047 cx.emit(EditorEvent::Reparsed(*buffer_id));
21048 cx.notify();
21049 }
21050 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21051 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21052 multi_buffer::Event::FileHandleChanged
21053 | multi_buffer::Event::Reloaded
21054 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21055 multi_buffer::Event::DiagnosticsUpdated => {
21056 self.update_diagnostics_state(window, cx);
21057 }
21058 _ => {}
21059 };
21060 }
21061
21062 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21063 if !self.diagnostics_enabled() {
21064 return;
21065 }
21066 self.refresh_active_diagnostics(cx);
21067 self.refresh_inline_diagnostics(true, window, cx);
21068 self.scrollbar_marker_state.dirty = true;
21069 cx.notify();
21070 }
21071
21072 pub fn start_temporary_diff_override(&mut self) {
21073 self.load_diff_task.take();
21074 self.temporary_diff_override = true;
21075 }
21076
21077 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21078 self.temporary_diff_override = false;
21079 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21080 self.buffer.update(cx, |buffer, cx| {
21081 buffer.set_all_diff_hunks_collapsed(cx);
21082 });
21083
21084 if let Some(project) = self.project.clone() {
21085 self.load_diff_task = Some(
21086 update_uncommitted_diff_for_buffer(
21087 cx.entity(),
21088 &project,
21089 self.buffer.read(cx).all_buffers(),
21090 self.buffer.clone(),
21091 cx,
21092 )
21093 .shared(),
21094 );
21095 }
21096 }
21097
21098 fn on_display_map_changed(
21099 &mut self,
21100 _: Entity<DisplayMap>,
21101 _: &mut Window,
21102 cx: &mut Context<Self>,
21103 ) {
21104 cx.notify();
21105 }
21106
21107 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21108 if self.diagnostics_enabled() {
21109 let new_severity = EditorSettings::get_global(cx)
21110 .diagnostics_max_severity
21111 .unwrap_or(DiagnosticSeverity::Hint);
21112 self.set_max_diagnostics_severity(new_severity, cx);
21113 }
21114 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21115 self.update_edit_prediction_settings(cx);
21116 self.refresh_edit_prediction(true, false, window, cx);
21117 self.refresh_inline_values(cx);
21118 self.refresh_inlay_hints(
21119 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21120 self.selections.newest_anchor().head(),
21121 &self.buffer.read(cx).snapshot(cx),
21122 cx,
21123 )),
21124 cx,
21125 );
21126
21127 let old_cursor_shape = self.cursor_shape;
21128 let old_show_breadcrumbs = self.show_breadcrumbs;
21129
21130 {
21131 let editor_settings = EditorSettings::get_global(cx);
21132 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21133 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21134 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21135 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21136 }
21137
21138 if old_cursor_shape != self.cursor_shape {
21139 cx.emit(EditorEvent::CursorShapeChanged);
21140 }
21141
21142 if old_show_breadcrumbs != self.show_breadcrumbs {
21143 cx.emit(EditorEvent::BreadcrumbsChanged);
21144 }
21145
21146 let project_settings = ProjectSettings::get_global(cx);
21147 self.serialize_dirty_buffers =
21148 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
21149
21150 if self.mode.is_full() {
21151 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21152 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21153 if self.show_inline_diagnostics != show_inline_diagnostics {
21154 self.show_inline_diagnostics = show_inline_diagnostics;
21155 self.refresh_inline_diagnostics(false, window, cx);
21156 }
21157
21158 if self.git_blame_inline_enabled != inline_blame_enabled {
21159 self.toggle_git_blame_inline_internal(false, window, cx);
21160 }
21161
21162 let minimap_settings = EditorSettings::get_global(cx).minimap;
21163 if self.minimap_visibility != MinimapVisibility::Disabled {
21164 if self.minimap_visibility.settings_visibility()
21165 != minimap_settings.minimap_enabled()
21166 {
21167 self.set_minimap_visibility(
21168 MinimapVisibility::for_mode(self.mode(), cx),
21169 window,
21170 cx,
21171 );
21172 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21173 minimap_entity.update(cx, |minimap_editor, cx| {
21174 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21175 })
21176 }
21177 }
21178 }
21179
21180 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21181 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21182 }) {
21183 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
21184 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21185 }
21186 self.refresh_colors_for_visible_range(None, window, cx);
21187 }
21188
21189 cx.notify();
21190 }
21191
21192 pub fn set_searchable(&mut self, searchable: bool) {
21193 self.searchable = searchable;
21194 }
21195
21196 pub fn searchable(&self) -> bool {
21197 self.searchable
21198 }
21199
21200 fn open_proposed_changes_editor(
21201 &mut self,
21202 _: &OpenProposedChangesEditor,
21203 window: &mut Window,
21204 cx: &mut Context<Self>,
21205 ) {
21206 let Some(workspace) = self.workspace() else {
21207 cx.propagate();
21208 return;
21209 };
21210
21211 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
21212 let multi_buffer = self.buffer.read(cx);
21213 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
21214 let mut new_selections_by_buffer = HashMap::default();
21215 for selection in selections {
21216 for (buffer, range, _) in
21217 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
21218 {
21219 let mut range = range.to_point(buffer);
21220 range.start.column = 0;
21221 range.end.column = buffer.line_len(range.end.row);
21222 new_selections_by_buffer
21223 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
21224 .or_insert(Vec::new())
21225 .push(range)
21226 }
21227 }
21228
21229 let proposed_changes_buffers = new_selections_by_buffer
21230 .into_iter()
21231 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
21232 .collect::<Vec<_>>();
21233 let proposed_changes_editor = cx.new(|cx| {
21234 ProposedChangesEditor::new(
21235 "Proposed changes",
21236 proposed_changes_buffers,
21237 self.project.clone(),
21238 window,
21239 cx,
21240 )
21241 });
21242
21243 window.defer(cx, move |window, cx| {
21244 workspace.update(cx, |workspace, cx| {
21245 workspace.active_pane().update(cx, |pane, cx| {
21246 pane.add_item(
21247 Box::new(proposed_changes_editor),
21248 true,
21249 true,
21250 None,
21251 window,
21252 cx,
21253 );
21254 });
21255 });
21256 });
21257 }
21258
21259 pub fn open_excerpts_in_split(
21260 &mut self,
21261 _: &OpenExcerptsSplit,
21262 window: &mut Window,
21263 cx: &mut Context<Self>,
21264 ) {
21265 self.open_excerpts_common(None, true, window, cx)
21266 }
21267
21268 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21269 self.open_excerpts_common(None, false, window, cx)
21270 }
21271
21272 fn open_excerpts_common(
21273 &mut self,
21274 jump_data: Option<JumpData>,
21275 split: bool,
21276 window: &mut Window,
21277 cx: &mut Context<Self>,
21278 ) {
21279 let Some(workspace) = self.workspace() else {
21280 cx.propagate();
21281 return;
21282 };
21283
21284 if self.buffer.read(cx).is_singleton() {
21285 cx.propagate();
21286 return;
21287 }
21288
21289 let mut new_selections_by_buffer = HashMap::default();
21290 match &jump_data {
21291 Some(JumpData::MultiBufferPoint {
21292 excerpt_id,
21293 position,
21294 anchor,
21295 line_offset_from_top,
21296 }) => {
21297 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21298 if let Some(buffer) = multi_buffer_snapshot
21299 .buffer_id_for_excerpt(*excerpt_id)
21300 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21301 {
21302 let buffer_snapshot = buffer.read(cx).snapshot();
21303 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21304 language::ToPoint::to_point(anchor, &buffer_snapshot)
21305 } else {
21306 buffer_snapshot.clip_point(*position, Bias::Left)
21307 };
21308 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21309 new_selections_by_buffer.insert(
21310 buffer,
21311 (
21312 vec![jump_to_offset..jump_to_offset],
21313 Some(*line_offset_from_top),
21314 ),
21315 );
21316 }
21317 }
21318 Some(JumpData::MultiBufferRow {
21319 row,
21320 line_offset_from_top,
21321 }) => {
21322 let point = MultiBufferPoint::new(row.0, 0);
21323 if let Some((buffer, buffer_point, _)) =
21324 self.buffer.read(cx).point_to_buffer_point(point, cx)
21325 {
21326 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21327 new_selections_by_buffer
21328 .entry(buffer)
21329 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21330 .0
21331 .push(buffer_offset..buffer_offset)
21332 }
21333 }
21334 None => {
21335 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
21336 let multi_buffer = self.buffer.read(cx);
21337 for selection in selections {
21338 for (snapshot, range, _, anchor) in multi_buffer
21339 .snapshot(cx)
21340 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21341 {
21342 if let Some(anchor) = anchor {
21343 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21344 else {
21345 continue;
21346 };
21347 let offset = text::ToOffset::to_offset(
21348 &anchor.text_anchor,
21349 &buffer_handle.read(cx).snapshot(),
21350 );
21351 let range = offset..offset;
21352 new_selections_by_buffer
21353 .entry(buffer_handle)
21354 .or_insert((Vec::new(), None))
21355 .0
21356 .push(range)
21357 } else {
21358 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21359 else {
21360 continue;
21361 };
21362 new_selections_by_buffer
21363 .entry(buffer_handle)
21364 .or_insert((Vec::new(), None))
21365 .0
21366 .push(range)
21367 }
21368 }
21369 }
21370 }
21371 }
21372
21373 new_selections_by_buffer
21374 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21375
21376 if new_selections_by_buffer.is_empty() {
21377 return;
21378 }
21379
21380 // We defer the pane interaction because we ourselves are a workspace item
21381 // and activating a new item causes the pane to call a method on us reentrantly,
21382 // which panics if we're on the stack.
21383 window.defer(cx, move |window, cx| {
21384 workspace.update(cx, |workspace, cx| {
21385 let pane = if split {
21386 workspace.adjacent_pane(window, cx)
21387 } else {
21388 workspace.active_pane().clone()
21389 };
21390
21391 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21392 let editor = buffer
21393 .read(cx)
21394 .file()
21395 .is_none()
21396 .then(|| {
21397 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21398 // so `workspace.open_project_item` will never find them, always opening a new editor.
21399 // Instead, we try to activate the existing editor in the pane first.
21400 let (editor, pane_item_index) =
21401 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21402 let editor = item.downcast::<Editor>()?;
21403 let singleton_buffer =
21404 editor.read(cx).buffer().read(cx).as_singleton()?;
21405 if singleton_buffer == buffer {
21406 Some((editor, i))
21407 } else {
21408 None
21409 }
21410 })?;
21411 pane.update(cx, |pane, cx| {
21412 pane.activate_item(pane_item_index, true, true, window, cx)
21413 });
21414 Some(editor)
21415 })
21416 .flatten()
21417 .unwrap_or_else(|| {
21418 workspace.open_project_item::<Self>(
21419 pane.clone(),
21420 buffer,
21421 true,
21422 true,
21423 window,
21424 cx,
21425 )
21426 });
21427
21428 editor.update(cx, |editor, cx| {
21429 let autoscroll = match scroll_offset {
21430 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21431 None => Autoscroll::newest(),
21432 };
21433 let nav_history = editor.nav_history.take();
21434 editor.change_selections(
21435 SelectionEffects::scroll(autoscroll),
21436 window,
21437 cx,
21438 |s| {
21439 s.select_ranges(ranges);
21440 },
21441 );
21442 editor.nav_history = nav_history;
21443 });
21444 }
21445 })
21446 });
21447 }
21448
21449 // For now, don't allow opening excerpts in buffers that aren't backed by
21450 // regular project files.
21451 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21452 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21453 }
21454
21455 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21456 let snapshot = self.buffer.read(cx).read(cx);
21457 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21458 Some(
21459 ranges
21460 .iter()
21461 .map(move |range| {
21462 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21463 })
21464 .collect(),
21465 )
21466 }
21467
21468 fn selection_replacement_ranges(
21469 &self,
21470 range: Range<OffsetUtf16>,
21471 cx: &mut App,
21472 ) -> Vec<Range<OffsetUtf16>> {
21473 let selections = self
21474 .selections
21475 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21476 let newest_selection = selections
21477 .iter()
21478 .max_by_key(|selection| selection.id)
21479 .unwrap();
21480 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21481 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21482 let snapshot = self.buffer.read(cx).read(cx);
21483 selections
21484 .into_iter()
21485 .map(|mut selection| {
21486 selection.start.0 =
21487 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21488 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21489 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21490 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21491 })
21492 .collect()
21493 }
21494
21495 fn report_editor_event(
21496 &self,
21497 reported_event: ReportEditorEvent,
21498 file_extension: Option<String>,
21499 cx: &App,
21500 ) {
21501 if cfg!(any(test, feature = "test-support")) {
21502 return;
21503 }
21504
21505 let Some(project) = &self.project else { return };
21506
21507 // If None, we are in a file without an extension
21508 let file = self
21509 .buffer
21510 .read(cx)
21511 .as_singleton()
21512 .and_then(|b| b.read(cx).file());
21513 let file_extension = file_extension.or(file
21514 .as_ref()
21515 .and_then(|file| Path::new(file.file_name(cx)).extension())
21516 .and_then(|e| e.to_str())
21517 .map(|a| a.to_string()));
21518
21519 let vim_mode = vim_enabled(cx);
21520
21521 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21522 let copilot_enabled = edit_predictions_provider
21523 == language::language_settings::EditPredictionProvider::Copilot;
21524 let copilot_enabled_for_language = self
21525 .buffer
21526 .read(cx)
21527 .language_settings(cx)
21528 .show_edit_predictions;
21529
21530 let project = project.read(cx);
21531 let event_type = reported_event.event_type();
21532
21533 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21534 telemetry::event!(
21535 event_type,
21536 type = if auto_saved {"autosave"} else {"manual"},
21537 file_extension,
21538 vim_mode,
21539 copilot_enabled,
21540 copilot_enabled_for_language,
21541 edit_predictions_provider,
21542 is_via_ssh = project.is_via_remote_server(),
21543 );
21544 } else {
21545 telemetry::event!(
21546 event_type,
21547 file_extension,
21548 vim_mode,
21549 copilot_enabled,
21550 copilot_enabled_for_language,
21551 edit_predictions_provider,
21552 is_via_ssh = project.is_via_remote_server(),
21553 );
21554 };
21555 }
21556
21557 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21558 /// with each line being an array of {text, highlight} objects.
21559 fn copy_highlight_json(
21560 &mut self,
21561 _: &CopyHighlightJson,
21562 window: &mut Window,
21563 cx: &mut Context<Self>,
21564 ) {
21565 #[derive(Serialize)]
21566 struct Chunk<'a> {
21567 text: String,
21568 highlight: Option<&'a str>,
21569 }
21570
21571 let snapshot = self.buffer.read(cx).snapshot(cx);
21572 let range = self
21573 .selected_text_range(false, window, cx)
21574 .and_then(|selection| {
21575 if selection.range.is_empty() {
21576 None
21577 } else {
21578 Some(selection.range)
21579 }
21580 })
21581 .unwrap_or_else(|| 0..snapshot.len());
21582
21583 let chunks = snapshot.chunks(range, true);
21584 let mut lines = Vec::new();
21585 let mut line: VecDeque<Chunk> = VecDeque::new();
21586
21587 let Some(style) = self.style.as_ref() else {
21588 return;
21589 };
21590
21591 for chunk in chunks {
21592 let highlight = chunk
21593 .syntax_highlight_id
21594 .and_then(|id| id.name(&style.syntax));
21595 let mut chunk_lines = chunk.text.split('\n').peekable();
21596 while let Some(text) = chunk_lines.next() {
21597 let mut merged_with_last_token = false;
21598 if let Some(last_token) = line.back_mut()
21599 && last_token.highlight == highlight
21600 {
21601 last_token.text.push_str(text);
21602 merged_with_last_token = true;
21603 }
21604
21605 if !merged_with_last_token {
21606 line.push_back(Chunk {
21607 text: text.into(),
21608 highlight,
21609 });
21610 }
21611
21612 if chunk_lines.peek().is_some() {
21613 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21614 line.pop_front();
21615 }
21616 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21617 line.pop_back();
21618 }
21619
21620 lines.push(mem::take(&mut line));
21621 }
21622 }
21623 }
21624
21625 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21626 return;
21627 };
21628 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21629 }
21630
21631 pub fn open_context_menu(
21632 &mut self,
21633 _: &OpenContextMenu,
21634 window: &mut Window,
21635 cx: &mut Context<Self>,
21636 ) {
21637 self.request_autoscroll(Autoscroll::newest(), cx);
21638 let position = self
21639 .selections
21640 .newest_display(&self.display_snapshot(cx))
21641 .start;
21642 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21643 }
21644
21645 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
21646 &self.inlay_hint_cache
21647 }
21648
21649 pub fn replay_insert_event(
21650 &mut self,
21651 text: &str,
21652 relative_utf16_range: Option<Range<isize>>,
21653 window: &mut Window,
21654 cx: &mut Context<Self>,
21655 ) {
21656 if !self.input_enabled {
21657 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21658 return;
21659 }
21660 if let Some(relative_utf16_range) = relative_utf16_range {
21661 let selections = self
21662 .selections
21663 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21664 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21665 let new_ranges = selections.into_iter().map(|range| {
21666 let start = OffsetUtf16(
21667 range
21668 .head()
21669 .0
21670 .saturating_add_signed(relative_utf16_range.start),
21671 );
21672 let end = OffsetUtf16(
21673 range
21674 .head()
21675 .0
21676 .saturating_add_signed(relative_utf16_range.end),
21677 );
21678 start..end
21679 });
21680 s.select_ranges(new_ranges);
21681 });
21682 }
21683
21684 self.handle_input(text, window, cx);
21685 }
21686
21687 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
21688 let Some(provider) = self.semantics_provider.as_ref() else {
21689 return false;
21690 };
21691
21692 let mut supports = false;
21693 self.buffer().update(cx, |this, cx| {
21694 this.for_each_buffer(|buffer| {
21695 supports |= provider.supports_inlay_hints(buffer, cx);
21696 });
21697 });
21698
21699 supports
21700 }
21701
21702 pub fn is_focused(&self, window: &Window) -> bool {
21703 self.focus_handle.is_focused(window)
21704 }
21705
21706 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21707 cx.emit(EditorEvent::Focused);
21708
21709 if let Some(descendant) = self
21710 .last_focused_descendant
21711 .take()
21712 .and_then(|descendant| descendant.upgrade())
21713 {
21714 window.focus(&descendant);
21715 } else {
21716 if let Some(blame) = self.blame.as_ref() {
21717 blame.update(cx, GitBlame::focus)
21718 }
21719
21720 self.blink_manager.update(cx, BlinkManager::enable);
21721 self.show_cursor_names(window, cx);
21722 self.buffer.update(cx, |buffer, cx| {
21723 buffer.finalize_last_transaction(cx);
21724 if self.leader_id.is_none() {
21725 buffer.set_active_selections(
21726 &self.selections.disjoint_anchors_arc(),
21727 self.selections.line_mode(),
21728 self.cursor_shape,
21729 cx,
21730 );
21731 }
21732 });
21733 }
21734 }
21735
21736 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21737 cx.emit(EditorEvent::FocusedIn)
21738 }
21739
21740 fn handle_focus_out(
21741 &mut self,
21742 event: FocusOutEvent,
21743 _window: &mut Window,
21744 cx: &mut Context<Self>,
21745 ) {
21746 if event.blurred != self.focus_handle {
21747 self.last_focused_descendant = Some(event.blurred);
21748 }
21749 self.selection_drag_state = SelectionDragState::None;
21750 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21751 }
21752
21753 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21754 self.blink_manager.update(cx, BlinkManager::disable);
21755 self.buffer
21756 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21757
21758 if let Some(blame) = self.blame.as_ref() {
21759 blame.update(cx, GitBlame::blur)
21760 }
21761 if !self.hover_state.focused(window, cx) {
21762 hide_hover(self, cx);
21763 }
21764 if !self
21765 .context_menu
21766 .borrow()
21767 .as_ref()
21768 .is_some_and(|context_menu| context_menu.focused(window, cx))
21769 {
21770 self.hide_context_menu(window, cx);
21771 }
21772 self.take_active_edit_prediction(cx);
21773 cx.emit(EditorEvent::Blurred);
21774 cx.notify();
21775 }
21776
21777 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21778 let mut pending: String = window
21779 .pending_input_keystrokes()
21780 .into_iter()
21781 .flatten()
21782 .filter_map(|keystroke| {
21783 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21784 keystroke.key_char.clone()
21785 } else {
21786 None
21787 }
21788 })
21789 .collect();
21790
21791 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21792 pending = "".to_string();
21793 }
21794
21795 let existing_pending = self
21796 .text_highlights::<PendingInput>(cx)
21797 .map(|(_, ranges)| ranges.to_vec());
21798 if existing_pending.is_none() && pending.is_empty() {
21799 return;
21800 }
21801 let transaction =
21802 self.transact(window, cx, |this, window, cx| {
21803 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
21804 let edits = selections
21805 .iter()
21806 .map(|selection| (selection.end..selection.end, pending.clone()));
21807 this.edit(edits, cx);
21808 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21809 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21810 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21811 }));
21812 });
21813 if let Some(existing_ranges) = existing_pending {
21814 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21815 this.edit(edits, cx);
21816 }
21817 });
21818
21819 let snapshot = self.snapshot(window, cx);
21820 let ranges = self
21821 .selections
21822 .all::<usize>(&snapshot.display_snapshot)
21823 .into_iter()
21824 .map(|selection| {
21825 snapshot.buffer_snapshot().anchor_after(selection.end)
21826 ..snapshot
21827 .buffer_snapshot()
21828 .anchor_before(selection.end + pending.len())
21829 })
21830 .collect();
21831
21832 if pending.is_empty() {
21833 self.clear_highlights::<PendingInput>(cx);
21834 } else {
21835 self.highlight_text::<PendingInput>(
21836 ranges,
21837 HighlightStyle {
21838 underline: Some(UnderlineStyle {
21839 thickness: px(1.),
21840 color: None,
21841 wavy: false,
21842 }),
21843 ..Default::default()
21844 },
21845 cx,
21846 );
21847 }
21848
21849 self.ime_transaction = self.ime_transaction.or(transaction);
21850 if let Some(transaction) = self.ime_transaction {
21851 self.buffer.update(cx, |buffer, cx| {
21852 buffer.group_until_transaction(transaction, cx);
21853 });
21854 }
21855
21856 if self.text_highlights::<PendingInput>(cx).is_none() {
21857 self.ime_transaction.take();
21858 }
21859 }
21860
21861 pub fn register_action_renderer(
21862 &mut self,
21863 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21864 ) -> Subscription {
21865 let id = self.next_editor_action_id.post_inc();
21866 self.editor_actions
21867 .borrow_mut()
21868 .insert(id, Box::new(listener));
21869
21870 let editor_actions = self.editor_actions.clone();
21871 Subscription::new(move || {
21872 editor_actions.borrow_mut().remove(&id);
21873 })
21874 }
21875
21876 pub fn register_action<A: Action>(
21877 &mut self,
21878 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21879 ) -> Subscription {
21880 let id = self.next_editor_action_id.post_inc();
21881 let listener = Arc::new(listener);
21882 self.editor_actions.borrow_mut().insert(
21883 id,
21884 Box::new(move |_, window, _| {
21885 let listener = listener.clone();
21886 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21887 let action = action.downcast_ref().unwrap();
21888 if phase == DispatchPhase::Bubble {
21889 listener(action, window, cx)
21890 }
21891 })
21892 }),
21893 );
21894
21895 let editor_actions = self.editor_actions.clone();
21896 Subscription::new(move || {
21897 editor_actions.borrow_mut().remove(&id);
21898 })
21899 }
21900
21901 pub fn file_header_size(&self) -> u32 {
21902 FILE_HEADER_HEIGHT
21903 }
21904
21905 pub fn restore(
21906 &mut self,
21907 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21908 window: &mut Window,
21909 cx: &mut Context<Self>,
21910 ) {
21911 let workspace = self.workspace();
21912 let project = self.project();
21913 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21914 let mut tasks = Vec::new();
21915 for (buffer_id, changes) in revert_changes {
21916 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21917 buffer.update(cx, |buffer, cx| {
21918 buffer.edit(
21919 changes
21920 .into_iter()
21921 .map(|(range, text)| (range, text.to_string())),
21922 None,
21923 cx,
21924 );
21925 });
21926
21927 if let Some(project) =
21928 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21929 {
21930 project.update(cx, |project, cx| {
21931 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21932 })
21933 }
21934 }
21935 }
21936 tasks
21937 });
21938 cx.spawn_in(window, async move |_, cx| {
21939 for (buffer, task) in save_tasks {
21940 let result = task.await;
21941 if result.is_err() {
21942 let Some(path) = buffer
21943 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21944 .ok()
21945 else {
21946 continue;
21947 };
21948 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21949 let Some(task) = cx
21950 .update_window_entity(workspace, |workspace, window, cx| {
21951 workspace
21952 .open_path_preview(path, None, false, false, false, window, cx)
21953 })
21954 .ok()
21955 else {
21956 continue;
21957 };
21958 task.await.log_err();
21959 }
21960 }
21961 }
21962 })
21963 .detach();
21964 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21965 selections.refresh()
21966 });
21967 }
21968
21969 pub fn to_pixel_point(
21970 &self,
21971 source: multi_buffer::Anchor,
21972 editor_snapshot: &EditorSnapshot,
21973 window: &mut Window,
21974 ) -> Option<gpui::Point<Pixels>> {
21975 let source_point = source.to_display_point(editor_snapshot);
21976 self.display_to_pixel_point(source_point, editor_snapshot, window)
21977 }
21978
21979 pub fn display_to_pixel_point(
21980 &self,
21981 source: DisplayPoint,
21982 editor_snapshot: &EditorSnapshot,
21983 window: &mut Window,
21984 ) -> Option<gpui::Point<Pixels>> {
21985 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21986 let text_layout_details = self.text_layout_details(window);
21987 let scroll_top = text_layout_details
21988 .scroll_anchor
21989 .scroll_position(editor_snapshot)
21990 .y;
21991
21992 if source.row().as_f64() < scroll_top.floor() {
21993 return None;
21994 }
21995 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21996 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
21997 Some(gpui::Point::new(source_x, source_y))
21998 }
21999
22000 pub fn has_visible_completions_menu(&self) -> bool {
22001 !self.edit_prediction_preview_is_active()
22002 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
22003 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
22004 })
22005 }
22006
22007 pub fn register_addon<T: Addon>(&mut self, instance: T) {
22008 if self.mode.is_minimap() {
22009 return;
22010 }
22011 self.addons
22012 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
22013 }
22014
22015 pub fn unregister_addon<T: Addon>(&mut self) {
22016 self.addons.remove(&std::any::TypeId::of::<T>());
22017 }
22018
22019 pub fn addon<T: Addon>(&self) -> Option<&T> {
22020 let type_id = std::any::TypeId::of::<T>();
22021 self.addons
22022 .get(&type_id)
22023 .and_then(|item| item.to_any().downcast_ref::<T>())
22024 }
22025
22026 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
22027 let type_id = std::any::TypeId::of::<T>();
22028 self.addons
22029 .get_mut(&type_id)
22030 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
22031 }
22032
22033 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
22034 let text_layout_details = self.text_layout_details(window);
22035 let style = &text_layout_details.editor_style;
22036 let font_id = window.text_system().resolve_font(&style.text.font());
22037 let font_size = style.text.font_size.to_pixels(window.rem_size());
22038 let line_height = style.text.line_height_in_pixels(window.rem_size());
22039 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
22040 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
22041
22042 CharacterDimensions {
22043 em_width,
22044 em_advance,
22045 line_height,
22046 }
22047 }
22048
22049 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
22050 self.load_diff_task.clone()
22051 }
22052
22053 fn read_metadata_from_db(
22054 &mut self,
22055 item_id: u64,
22056 workspace_id: WorkspaceId,
22057 window: &mut Window,
22058 cx: &mut Context<Editor>,
22059 ) {
22060 if self.buffer_kind(cx) == ItemBufferKind::Singleton
22061 && !self.mode.is_minimap()
22062 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
22063 {
22064 let buffer_snapshot = OnceCell::new();
22065
22066 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
22067 && !folds.is_empty()
22068 {
22069 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22070 self.fold_ranges(
22071 folds
22072 .into_iter()
22073 .map(|(start, end)| {
22074 snapshot.clip_offset(start, Bias::Left)
22075 ..snapshot.clip_offset(end, Bias::Right)
22076 })
22077 .collect(),
22078 false,
22079 window,
22080 cx,
22081 );
22082 }
22083
22084 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22085 && !selections.is_empty()
22086 {
22087 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22088 // skip adding the initial selection to selection history
22089 self.selection_history.mode = SelectionHistoryMode::Skipping;
22090 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22091 s.select_ranges(selections.into_iter().map(|(start, end)| {
22092 snapshot.clip_offset(start, Bias::Left)
22093 ..snapshot.clip_offset(end, Bias::Right)
22094 }));
22095 });
22096 self.selection_history.mode = SelectionHistoryMode::Normal;
22097 };
22098 }
22099
22100 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22101 }
22102
22103 fn update_lsp_data(
22104 &mut self,
22105 for_buffer: Option<BufferId>,
22106 window: &mut Window,
22107 cx: &mut Context<'_, Self>,
22108 ) {
22109 self.pull_diagnostics(for_buffer, window, cx);
22110 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22111 }
22112
22113 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22114 if self.ignore_lsp_data() {
22115 return;
22116 }
22117 for (_, (visible_buffer, _, _)) in self.visible_excerpts(None, cx) {
22118 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22119 }
22120 }
22121
22122 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) -> bool {
22123 if !self.registered_buffers.contains_key(&buffer_id)
22124 && let Some(project) = self.project.as_ref()
22125 {
22126 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22127 project.update(cx, |project, cx| {
22128 self.registered_buffers.insert(
22129 buffer_id,
22130 project.register_buffer_with_language_servers(&buffer, cx),
22131 );
22132 });
22133 return true;
22134 } else {
22135 self.registered_buffers.remove(&buffer_id);
22136 }
22137 }
22138
22139 false
22140 }
22141
22142 fn ignore_lsp_data(&self) -> bool {
22143 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22144 // skip any LSP updates for it.
22145 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22146 }
22147}
22148
22149fn edit_for_markdown_paste<'a>(
22150 buffer: &MultiBufferSnapshot,
22151 range: Range<usize>,
22152 to_insert: &'a str,
22153 url: Option<url::Url>,
22154) -> (Range<usize>, Cow<'a, str>) {
22155 if url.is_none() {
22156 return (range, Cow::Borrowed(to_insert));
22157 };
22158
22159 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22160
22161 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22162 Cow::Borrowed(to_insert)
22163 } else {
22164 Cow::Owned(format!("[{old_text}]({to_insert})"))
22165 };
22166 (range, new_text)
22167}
22168
22169fn vim_enabled(cx: &App) -> bool {
22170 vim_mode_setting::VimModeSetting::try_get(cx)
22171 .map(|vim_mode| vim_mode.0)
22172 .unwrap_or(false)
22173}
22174
22175fn process_completion_for_edit(
22176 completion: &Completion,
22177 intent: CompletionIntent,
22178 buffer: &Entity<Buffer>,
22179 cursor_position: &text::Anchor,
22180 cx: &mut Context<Editor>,
22181) -> CompletionEdit {
22182 let buffer = buffer.read(cx);
22183 let buffer_snapshot = buffer.snapshot();
22184 let (snippet, new_text) = if completion.is_snippet() {
22185 let mut snippet_source = completion.new_text.clone();
22186 // Workaround for typescript language server issues so that methods don't expand within
22187 // strings and functions with type expressions. The previous point is used because the query
22188 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22189 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22190 let previous_point = if previous_point.column > 0 {
22191 cursor_position.to_previous_offset(&buffer_snapshot)
22192 } else {
22193 cursor_position.to_offset(&buffer_snapshot)
22194 };
22195 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22196 && scope.prefers_label_for_snippet_in_completion()
22197 && let Some(label) = completion.label()
22198 && matches!(
22199 completion.kind(),
22200 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22201 )
22202 {
22203 snippet_source = label;
22204 }
22205 match Snippet::parse(&snippet_source).log_err() {
22206 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22207 None => (None, completion.new_text.clone()),
22208 }
22209 } else {
22210 (None, completion.new_text.clone())
22211 };
22212
22213 let mut range_to_replace = {
22214 let replace_range = &completion.replace_range;
22215 if let CompletionSource::Lsp {
22216 insert_range: Some(insert_range),
22217 ..
22218 } = &completion.source
22219 {
22220 debug_assert_eq!(
22221 insert_range.start, replace_range.start,
22222 "insert_range and replace_range should start at the same position"
22223 );
22224 debug_assert!(
22225 insert_range
22226 .start
22227 .cmp(cursor_position, &buffer_snapshot)
22228 .is_le(),
22229 "insert_range should start before or at cursor position"
22230 );
22231 debug_assert!(
22232 replace_range
22233 .start
22234 .cmp(cursor_position, &buffer_snapshot)
22235 .is_le(),
22236 "replace_range should start before or at cursor position"
22237 );
22238
22239 let should_replace = match intent {
22240 CompletionIntent::CompleteWithInsert => false,
22241 CompletionIntent::CompleteWithReplace => true,
22242 CompletionIntent::Complete | CompletionIntent::Compose => {
22243 let insert_mode =
22244 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22245 .completions
22246 .lsp_insert_mode;
22247 match insert_mode {
22248 LspInsertMode::Insert => false,
22249 LspInsertMode::Replace => true,
22250 LspInsertMode::ReplaceSubsequence => {
22251 let mut text_to_replace = buffer.chars_for_range(
22252 buffer.anchor_before(replace_range.start)
22253 ..buffer.anchor_after(replace_range.end),
22254 );
22255 let mut current_needle = text_to_replace.next();
22256 for haystack_ch in completion.label.text.chars() {
22257 if let Some(needle_ch) = current_needle
22258 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22259 {
22260 current_needle = text_to_replace.next();
22261 }
22262 }
22263 current_needle.is_none()
22264 }
22265 LspInsertMode::ReplaceSuffix => {
22266 if replace_range
22267 .end
22268 .cmp(cursor_position, &buffer_snapshot)
22269 .is_gt()
22270 {
22271 let range_after_cursor = *cursor_position..replace_range.end;
22272 let text_after_cursor = buffer
22273 .text_for_range(
22274 buffer.anchor_before(range_after_cursor.start)
22275 ..buffer.anchor_after(range_after_cursor.end),
22276 )
22277 .collect::<String>()
22278 .to_ascii_lowercase();
22279 completion
22280 .label
22281 .text
22282 .to_ascii_lowercase()
22283 .ends_with(&text_after_cursor)
22284 } else {
22285 true
22286 }
22287 }
22288 }
22289 }
22290 };
22291
22292 if should_replace {
22293 replace_range.clone()
22294 } else {
22295 insert_range.clone()
22296 }
22297 } else {
22298 replace_range.clone()
22299 }
22300 };
22301
22302 if range_to_replace
22303 .end
22304 .cmp(cursor_position, &buffer_snapshot)
22305 .is_lt()
22306 {
22307 range_to_replace.end = *cursor_position;
22308 }
22309
22310 CompletionEdit {
22311 new_text,
22312 replace_range: range_to_replace.to_offset(buffer),
22313 snippet,
22314 }
22315}
22316
22317struct CompletionEdit {
22318 new_text: String,
22319 replace_range: Range<usize>,
22320 snippet: Option<Snippet>,
22321}
22322
22323fn insert_extra_newline_brackets(
22324 buffer: &MultiBufferSnapshot,
22325 range: Range<usize>,
22326 language: &language::LanguageScope,
22327) -> bool {
22328 let leading_whitespace_len = buffer
22329 .reversed_chars_at(range.start)
22330 .take_while(|c| c.is_whitespace() && *c != '\n')
22331 .map(|c| c.len_utf8())
22332 .sum::<usize>();
22333 let trailing_whitespace_len = buffer
22334 .chars_at(range.end)
22335 .take_while(|c| c.is_whitespace() && *c != '\n')
22336 .map(|c| c.len_utf8())
22337 .sum::<usize>();
22338 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22339
22340 language.brackets().any(|(pair, enabled)| {
22341 let pair_start = pair.start.trim_end();
22342 let pair_end = pair.end.trim_start();
22343
22344 enabled
22345 && pair.newline
22346 && buffer.contains_str_at(range.end, pair_end)
22347 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22348 })
22349}
22350
22351fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22352 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22353 [(buffer, range, _)] => (*buffer, range.clone()),
22354 _ => return false,
22355 };
22356 let pair = {
22357 let mut result: Option<BracketMatch> = None;
22358
22359 for pair in buffer
22360 .all_bracket_ranges(range.clone())
22361 .filter(move |pair| {
22362 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22363 })
22364 {
22365 let len = pair.close_range.end - pair.open_range.start;
22366
22367 if let Some(existing) = &result {
22368 let existing_len = existing.close_range.end - existing.open_range.start;
22369 if len > existing_len {
22370 continue;
22371 }
22372 }
22373
22374 result = Some(pair);
22375 }
22376
22377 result
22378 };
22379 let Some(pair) = pair else {
22380 return false;
22381 };
22382 pair.newline_only
22383 && buffer
22384 .chars_for_range(pair.open_range.end..range.start)
22385 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22386 .all(|c| c.is_whitespace() && c != '\n')
22387}
22388
22389fn update_uncommitted_diff_for_buffer(
22390 editor: Entity<Editor>,
22391 project: &Entity<Project>,
22392 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22393 buffer: Entity<MultiBuffer>,
22394 cx: &mut App,
22395) -> Task<()> {
22396 let mut tasks = Vec::new();
22397 project.update(cx, |project, cx| {
22398 for buffer in buffers {
22399 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22400 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22401 }
22402 }
22403 });
22404 cx.spawn(async move |cx| {
22405 let diffs = future::join_all(tasks).await;
22406 if editor
22407 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22408 .unwrap_or(false)
22409 {
22410 return;
22411 }
22412
22413 buffer
22414 .update(cx, |buffer, cx| {
22415 for diff in diffs.into_iter().flatten() {
22416 buffer.add_diff(diff, cx);
22417 }
22418 })
22419 .ok();
22420 })
22421}
22422
22423fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22424 let tab_size = tab_size.get() as usize;
22425 let mut width = offset;
22426
22427 for ch in text.chars() {
22428 width += if ch == '\t' {
22429 tab_size - (width % tab_size)
22430 } else {
22431 1
22432 };
22433 }
22434
22435 width - offset
22436}
22437
22438#[cfg(test)]
22439mod tests {
22440 use super::*;
22441
22442 #[test]
22443 fn test_string_size_with_expanded_tabs() {
22444 let nz = |val| NonZeroU32::new(val).unwrap();
22445 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22446 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22447 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22448 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22449 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22450 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22451 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22452 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22453 }
22454}
22455
22456/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22457struct WordBreakingTokenizer<'a> {
22458 input: &'a str,
22459}
22460
22461impl<'a> WordBreakingTokenizer<'a> {
22462 fn new(input: &'a str) -> Self {
22463 Self { input }
22464 }
22465}
22466
22467fn is_char_ideographic(ch: char) -> bool {
22468 use unicode_script::Script::*;
22469 use unicode_script::UnicodeScript;
22470 matches!(ch.script(), Han | Tangut | Yi)
22471}
22472
22473fn is_grapheme_ideographic(text: &str) -> bool {
22474 text.chars().any(is_char_ideographic)
22475}
22476
22477fn is_grapheme_whitespace(text: &str) -> bool {
22478 text.chars().any(|x| x.is_whitespace())
22479}
22480
22481fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22482 text.chars()
22483 .next()
22484 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22485}
22486
22487#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22488enum WordBreakToken<'a> {
22489 Word { token: &'a str, grapheme_len: usize },
22490 InlineWhitespace { token: &'a str, grapheme_len: usize },
22491 Newline,
22492}
22493
22494impl<'a> Iterator for WordBreakingTokenizer<'a> {
22495 /// Yields a span, the count of graphemes in the token, and whether it was
22496 /// whitespace. Note that it also breaks at word boundaries.
22497 type Item = WordBreakToken<'a>;
22498
22499 fn next(&mut self) -> Option<Self::Item> {
22500 use unicode_segmentation::UnicodeSegmentation;
22501 if self.input.is_empty() {
22502 return None;
22503 }
22504
22505 let mut iter = self.input.graphemes(true).peekable();
22506 let mut offset = 0;
22507 let mut grapheme_len = 0;
22508 if let Some(first_grapheme) = iter.next() {
22509 let is_newline = first_grapheme == "\n";
22510 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22511 offset += first_grapheme.len();
22512 grapheme_len += 1;
22513 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22514 if let Some(grapheme) = iter.peek().copied()
22515 && should_stay_with_preceding_ideograph(grapheme)
22516 {
22517 offset += grapheme.len();
22518 grapheme_len += 1;
22519 }
22520 } else {
22521 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22522 let mut next_word_bound = words.peek().copied();
22523 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22524 next_word_bound = words.next();
22525 }
22526 while let Some(grapheme) = iter.peek().copied() {
22527 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22528 break;
22529 };
22530 if is_grapheme_whitespace(grapheme) != is_whitespace
22531 || (grapheme == "\n") != is_newline
22532 {
22533 break;
22534 };
22535 offset += grapheme.len();
22536 grapheme_len += 1;
22537 iter.next();
22538 }
22539 }
22540 let token = &self.input[..offset];
22541 self.input = &self.input[offset..];
22542 if token == "\n" {
22543 Some(WordBreakToken::Newline)
22544 } else if is_whitespace {
22545 Some(WordBreakToken::InlineWhitespace {
22546 token,
22547 grapheme_len,
22548 })
22549 } else {
22550 Some(WordBreakToken::Word {
22551 token,
22552 grapheme_len,
22553 })
22554 }
22555 } else {
22556 None
22557 }
22558 }
22559}
22560
22561#[test]
22562fn test_word_breaking_tokenizer() {
22563 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22564 ("", &[]),
22565 (" ", &[whitespace(" ", 2)]),
22566 ("Ʒ", &[word("Ʒ", 1)]),
22567 ("Ǽ", &[word("Ǽ", 1)]),
22568 ("⋑", &[word("⋑", 1)]),
22569 ("⋑⋑", &[word("⋑⋑", 2)]),
22570 (
22571 "原理,进而",
22572 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22573 ),
22574 (
22575 "hello world",
22576 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22577 ),
22578 (
22579 "hello, world",
22580 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22581 ),
22582 (
22583 " hello world",
22584 &[
22585 whitespace(" ", 2),
22586 word("hello", 5),
22587 whitespace(" ", 1),
22588 word("world", 5),
22589 ],
22590 ),
22591 (
22592 "这是什么 \n 钢笔",
22593 &[
22594 word("这", 1),
22595 word("是", 1),
22596 word("什", 1),
22597 word("么", 1),
22598 whitespace(" ", 1),
22599 newline(),
22600 whitespace(" ", 1),
22601 word("钢", 1),
22602 word("笔", 1),
22603 ],
22604 ),
22605 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22606 ];
22607
22608 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22609 WordBreakToken::Word {
22610 token,
22611 grapheme_len,
22612 }
22613 }
22614
22615 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22616 WordBreakToken::InlineWhitespace {
22617 token,
22618 grapheme_len,
22619 }
22620 }
22621
22622 fn newline() -> WordBreakToken<'static> {
22623 WordBreakToken::Newline
22624 }
22625
22626 for (input, result) in tests {
22627 assert_eq!(
22628 WordBreakingTokenizer::new(input)
22629 .collect::<Vec<_>>()
22630 .as_slice(),
22631 *result,
22632 );
22633 }
22634}
22635
22636fn wrap_with_prefix(
22637 first_line_prefix: String,
22638 subsequent_lines_prefix: String,
22639 unwrapped_text: String,
22640 wrap_column: usize,
22641 tab_size: NonZeroU32,
22642 preserve_existing_whitespace: bool,
22643) -> String {
22644 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22645 let subsequent_lines_prefix_len =
22646 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22647 let mut wrapped_text = String::new();
22648 let mut current_line = first_line_prefix;
22649 let mut is_first_line = true;
22650
22651 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22652 let mut current_line_len = first_line_prefix_len;
22653 let mut in_whitespace = false;
22654 for token in tokenizer {
22655 let have_preceding_whitespace = in_whitespace;
22656 match token {
22657 WordBreakToken::Word {
22658 token,
22659 grapheme_len,
22660 } => {
22661 in_whitespace = false;
22662 let current_prefix_len = if is_first_line {
22663 first_line_prefix_len
22664 } else {
22665 subsequent_lines_prefix_len
22666 };
22667 if current_line_len + grapheme_len > wrap_column
22668 && current_line_len != current_prefix_len
22669 {
22670 wrapped_text.push_str(current_line.trim_end());
22671 wrapped_text.push('\n');
22672 is_first_line = false;
22673 current_line = subsequent_lines_prefix.clone();
22674 current_line_len = subsequent_lines_prefix_len;
22675 }
22676 current_line.push_str(token);
22677 current_line_len += grapheme_len;
22678 }
22679 WordBreakToken::InlineWhitespace {
22680 mut token,
22681 mut grapheme_len,
22682 } => {
22683 in_whitespace = true;
22684 if have_preceding_whitespace && !preserve_existing_whitespace {
22685 continue;
22686 }
22687 if !preserve_existing_whitespace {
22688 // Keep a single whitespace grapheme as-is
22689 if let Some(first) =
22690 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22691 {
22692 token = first;
22693 } else {
22694 token = " ";
22695 }
22696 grapheme_len = 1;
22697 }
22698 let current_prefix_len = if is_first_line {
22699 first_line_prefix_len
22700 } else {
22701 subsequent_lines_prefix_len
22702 };
22703 if current_line_len + grapheme_len > wrap_column {
22704 wrapped_text.push_str(current_line.trim_end());
22705 wrapped_text.push('\n');
22706 is_first_line = false;
22707 current_line = subsequent_lines_prefix.clone();
22708 current_line_len = subsequent_lines_prefix_len;
22709 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22710 current_line.push_str(token);
22711 current_line_len += grapheme_len;
22712 }
22713 }
22714 WordBreakToken::Newline => {
22715 in_whitespace = true;
22716 let current_prefix_len = if is_first_line {
22717 first_line_prefix_len
22718 } else {
22719 subsequent_lines_prefix_len
22720 };
22721 if preserve_existing_whitespace {
22722 wrapped_text.push_str(current_line.trim_end());
22723 wrapped_text.push('\n');
22724 is_first_line = false;
22725 current_line = subsequent_lines_prefix.clone();
22726 current_line_len = subsequent_lines_prefix_len;
22727 } else if have_preceding_whitespace {
22728 continue;
22729 } else if current_line_len + 1 > wrap_column
22730 && current_line_len != current_prefix_len
22731 {
22732 wrapped_text.push_str(current_line.trim_end());
22733 wrapped_text.push('\n');
22734 is_first_line = false;
22735 current_line = subsequent_lines_prefix.clone();
22736 current_line_len = subsequent_lines_prefix_len;
22737 } else if current_line_len != current_prefix_len {
22738 current_line.push(' ');
22739 current_line_len += 1;
22740 }
22741 }
22742 }
22743 }
22744
22745 if !current_line.is_empty() {
22746 wrapped_text.push_str(¤t_line);
22747 }
22748 wrapped_text
22749}
22750
22751#[test]
22752fn test_wrap_with_prefix() {
22753 assert_eq!(
22754 wrap_with_prefix(
22755 "# ".to_string(),
22756 "# ".to_string(),
22757 "abcdefg".to_string(),
22758 4,
22759 NonZeroU32::new(4).unwrap(),
22760 false,
22761 ),
22762 "# abcdefg"
22763 );
22764 assert_eq!(
22765 wrap_with_prefix(
22766 "".to_string(),
22767 "".to_string(),
22768 "\thello world".to_string(),
22769 8,
22770 NonZeroU32::new(4).unwrap(),
22771 false,
22772 ),
22773 "hello\nworld"
22774 );
22775 assert_eq!(
22776 wrap_with_prefix(
22777 "// ".to_string(),
22778 "// ".to_string(),
22779 "xx \nyy zz aa bb cc".to_string(),
22780 12,
22781 NonZeroU32::new(4).unwrap(),
22782 false,
22783 ),
22784 "// xx yy zz\n// aa bb cc"
22785 );
22786 assert_eq!(
22787 wrap_with_prefix(
22788 String::new(),
22789 String::new(),
22790 "这是什么 \n 钢笔".to_string(),
22791 3,
22792 NonZeroU32::new(4).unwrap(),
22793 false,
22794 ),
22795 "这是什\n么 钢\n笔"
22796 );
22797 assert_eq!(
22798 wrap_with_prefix(
22799 String::new(),
22800 String::new(),
22801 format!("foo{}bar", '\u{2009}'), // thin space
22802 80,
22803 NonZeroU32::new(4).unwrap(),
22804 false,
22805 ),
22806 format!("foo{}bar", '\u{2009}')
22807 );
22808}
22809
22810pub trait CollaborationHub {
22811 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22812 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22813 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22814}
22815
22816impl CollaborationHub for Entity<Project> {
22817 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22818 self.read(cx).collaborators()
22819 }
22820
22821 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22822 self.read(cx).user_store().read(cx).participant_indices()
22823 }
22824
22825 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22826 let this = self.read(cx);
22827 let user_ids = this.collaborators().values().map(|c| c.user_id);
22828 this.user_store().read(cx).participant_names(user_ids, cx)
22829 }
22830}
22831
22832pub trait SemanticsProvider {
22833 fn hover(
22834 &self,
22835 buffer: &Entity<Buffer>,
22836 position: text::Anchor,
22837 cx: &mut App,
22838 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22839
22840 fn inline_values(
22841 &self,
22842 buffer_handle: Entity<Buffer>,
22843 range: Range<text::Anchor>,
22844 cx: &mut App,
22845 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22846
22847 fn inlay_hints(
22848 &self,
22849 buffer_handle: Entity<Buffer>,
22850 range: Range<text::Anchor>,
22851 cx: &mut App,
22852 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22853
22854 fn resolve_inlay_hint(
22855 &self,
22856 hint: InlayHint,
22857 buffer_handle: Entity<Buffer>,
22858 server_id: LanguageServerId,
22859 cx: &mut App,
22860 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22861
22862 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22863
22864 fn document_highlights(
22865 &self,
22866 buffer: &Entity<Buffer>,
22867 position: text::Anchor,
22868 cx: &mut App,
22869 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22870
22871 fn definitions(
22872 &self,
22873 buffer: &Entity<Buffer>,
22874 position: text::Anchor,
22875 kind: GotoDefinitionKind,
22876 cx: &mut App,
22877 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22878
22879 fn range_for_rename(
22880 &self,
22881 buffer: &Entity<Buffer>,
22882 position: text::Anchor,
22883 cx: &mut App,
22884 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22885
22886 fn perform_rename(
22887 &self,
22888 buffer: &Entity<Buffer>,
22889 position: text::Anchor,
22890 new_name: String,
22891 cx: &mut App,
22892 ) -> Option<Task<Result<ProjectTransaction>>>;
22893}
22894
22895pub trait CompletionProvider {
22896 fn completions(
22897 &self,
22898 excerpt_id: ExcerptId,
22899 buffer: &Entity<Buffer>,
22900 buffer_position: text::Anchor,
22901 trigger: CompletionContext,
22902 window: &mut Window,
22903 cx: &mut Context<Editor>,
22904 ) -> Task<Result<Vec<CompletionResponse>>>;
22905
22906 fn resolve_completions(
22907 &self,
22908 _buffer: Entity<Buffer>,
22909 _completion_indices: Vec<usize>,
22910 _completions: Rc<RefCell<Box<[Completion]>>>,
22911 _cx: &mut Context<Editor>,
22912 ) -> Task<Result<bool>> {
22913 Task::ready(Ok(false))
22914 }
22915
22916 fn apply_additional_edits_for_completion(
22917 &self,
22918 _buffer: Entity<Buffer>,
22919 _completions: Rc<RefCell<Box<[Completion]>>>,
22920 _completion_index: usize,
22921 _push_to_history: bool,
22922 _cx: &mut Context<Editor>,
22923 ) -> Task<Result<Option<language::Transaction>>> {
22924 Task::ready(Ok(None))
22925 }
22926
22927 fn is_completion_trigger(
22928 &self,
22929 buffer: &Entity<Buffer>,
22930 position: language::Anchor,
22931 text: &str,
22932 trigger_in_words: bool,
22933 menu_is_open: bool,
22934 cx: &mut Context<Editor>,
22935 ) -> bool;
22936
22937 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22938
22939 fn sort_completions(&self) -> bool {
22940 true
22941 }
22942
22943 fn filter_completions(&self) -> bool {
22944 true
22945 }
22946}
22947
22948pub trait CodeActionProvider {
22949 fn id(&self) -> Arc<str>;
22950
22951 fn code_actions(
22952 &self,
22953 buffer: &Entity<Buffer>,
22954 range: Range<text::Anchor>,
22955 window: &mut Window,
22956 cx: &mut App,
22957 ) -> Task<Result<Vec<CodeAction>>>;
22958
22959 fn apply_code_action(
22960 &self,
22961 buffer_handle: Entity<Buffer>,
22962 action: CodeAction,
22963 excerpt_id: ExcerptId,
22964 push_to_history: bool,
22965 window: &mut Window,
22966 cx: &mut App,
22967 ) -> Task<Result<ProjectTransaction>>;
22968}
22969
22970impl CodeActionProvider for Entity<Project> {
22971 fn id(&self) -> Arc<str> {
22972 "project".into()
22973 }
22974
22975 fn code_actions(
22976 &self,
22977 buffer: &Entity<Buffer>,
22978 range: Range<text::Anchor>,
22979 _window: &mut Window,
22980 cx: &mut App,
22981 ) -> Task<Result<Vec<CodeAction>>> {
22982 self.update(cx, |project, cx| {
22983 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22984 let code_actions = project.code_actions(buffer, range, None, cx);
22985 cx.background_spawn(async move {
22986 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22987 Ok(code_lens_actions
22988 .context("code lens fetch")?
22989 .into_iter()
22990 .flatten()
22991 .chain(
22992 code_actions
22993 .context("code action fetch")?
22994 .into_iter()
22995 .flatten(),
22996 )
22997 .collect())
22998 })
22999 })
23000 }
23001
23002 fn apply_code_action(
23003 &self,
23004 buffer_handle: Entity<Buffer>,
23005 action: CodeAction,
23006 _excerpt_id: ExcerptId,
23007 push_to_history: bool,
23008 _window: &mut Window,
23009 cx: &mut App,
23010 ) -> Task<Result<ProjectTransaction>> {
23011 self.update(cx, |project, cx| {
23012 project.apply_code_action(buffer_handle, action, push_to_history, cx)
23013 })
23014 }
23015}
23016
23017fn snippet_completions(
23018 project: &Project,
23019 buffer: &Entity<Buffer>,
23020 buffer_position: text::Anchor,
23021 cx: &mut App,
23022) -> Task<Result<CompletionResponse>> {
23023 let languages = buffer.read(cx).languages_at(buffer_position);
23024 let snippet_store = project.snippets().read(cx);
23025
23026 let scopes: Vec<_> = languages
23027 .iter()
23028 .filter_map(|language| {
23029 let language_name = language.lsp_id();
23030 let snippets = snippet_store.snippets_for(Some(language_name), cx);
23031
23032 if snippets.is_empty() {
23033 None
23034 } else {
23035 Some((language.default_scope(), snippets))
23036 }
23037 })
23038 .collect();
23039
23040 if scopes.is_empty() {
23041 return Task::ready(Ok(CompletionResponse {
23042 completions: vec![],
23043 display_options: CompletionDisplayOptions::default(),
23044 is_incomplete: false,
23045 }));
23046 }
23047
23048 let snapshot = buffer.read(cx).text_snapshot();
23049 let executor = cx.background_executor().clone();
23050
23051 cx.background_spawn(async move {
23052 let mut is_incomplete = false;
23053 let mut completions: Vec<Completion> = Vec::new();
23054 for (scope, snippets) in scopes.into_iter() {
23055 let classifier =
23056 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
23057
23058 const MAX_WORD_PREFIX_LEN: usize = 128;
23059 let last_word: String = snapshot
23060 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
23061 .take(MAX_WORD_PREFIX_LEN)
23062 .take_while(|c| classifier.is_word(*c))
23063 .collect::<String>()
23064 .chars()
23065 .rev()
23066 .collect();
23067
23068 if last_word.is_empty() {
23069 return Ok(CompletionResponse {
23070 completions: vec![],
23071 display_options: CompletionDisplayOptions::default(),
23072 is_incomplete: true,
23073 });
23074 }
23075
23076 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
23077 let to_lsp = |point: &text::Anchor| {
23078 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
23079 point_to_lsp(end)
23080 };
23081 let lsp_end = to_lsp(&buffer_position);
23082
23083 let candidates = snippets
23084 .iter()
23085 .enumerate()
23086 .flat_map(|(ix, snippet)| {
23087 snippet
23088 .prefix
23089 .iter()
23090 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
23091 })
23092 .collect::<Vec<StringMatchCandidate>>();
23093
23094 const MAX_RESULTS: usize = 100;
23095 let mut matches = fuzzy::match_strings(
23096 &candidates,
23097 &last_word,
23098 last_word.chars().any(|c| c.is_uppercase()),
23099 true,
23100 MAX_RESULTS,
23101 &Default::default(),
23102 executor.clone(),
23103 )
23104 .await;
23105
23106 if matches.len() >= MAX_RESULTS {
23107 is_incomplete = true;
23108 }
23109
23110 // Remove all candidates where the query's start does not match the start of any word in the candidate
23111 if let Some(query_start) = last_word.chars().next() {
23112 matches.retain(|string_match| {
23113 split_words(&string_match.string).any(|word| {
23114 // Check that the first codepoint of the word as lowercase matches the first
23115 // codepoint of the query as lowercase
23116 word.chars()
23117 .flat_map(|codepoint| codepoint.to_lowercase())
23118 .zip(query_start.to_lowercase())
23119 .all(|(word_cp, query_cp)| word_cp == query_cp)
23120 })
23121 });
23122 }
23123
23124 let matched_strings = matches
23125 .into_iter()
23126 .map(|m| m.string)
23127 .collect::<HashSet<_>>();
23128
23129 completions.extend(snippets.iter().filter_map(|snippet| {
23130 let matching_prefix = snippet
23131 .prefix
23132 .iter()
23133 .find(|prefix| matched_strings.contains(*prefix))?;
23134 let start = as_offset - last_word.len();
23135 let start = snapshot.anchor_before(start);
23136 let range = start..buffer_position;
23137 let lsp_start = to_lsp(&start);
23138 let lsp_range = lsp::Range {
23139 start: lsp_start,
23140 end: lsp_end,
23141 };
23142 Some(Completion {
23143 replace_range: range,
23144 new_text: snippet.body.clone(),
23145 source: CompletionSource::Lsp {
23146 insert_range: None,
23147 server_id: LanguageServerId(usize::MAX),
23148 resolved: true,
23149 lsp_completion: Box::new(lsp::CompletionItem {
23150 label: snippet.prefix.first().unwrap().clone(),
23151 kind: Some(CompletionItemKind::SNIPPET),
23152 label_details: snippet.description.as_ref().map(|description| {
23153 lsp::CompletionItemLabelDetails {
23154 detail: Some(description.clone()),
23155 description: None,
23156 }
23157 }),
23158 insert_text_format: Some(InsertTextFormat::SNIPPET),
23159 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23160 lsp::InsertReplaceEdit {
23161 new_text: snippet.body.clone(),
23162 insert: lsp_range,
23163 replace: lsp_range,
23164 },
23165 )),
23166 filter_text: Some(snippet.body.clone()),
23167 sort_text: Some(char::MAX.to_string()),
23168 ..lsp::CompletionItem::default()
23169 }),
23170 lsp_defaults: None,
23171 },
23172 label: CodeLabel::plain(matching_prefix.clone(), None),
23173 icon_path: None,
23174 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23175 single_line: snippet.name.clone().into(),
23176 plain_text: snippet
23177 .description
23178 .clone()
23179 .map(|description| description.into()),
23180 }),
23181 insert_text_mode: None,
23182 confirm: None,
23183 })
23184 }))
23185 }
23186
23187 Ok(CompletionResponse {
23188 completions,
23189 display_options: CompletionDisplayOptions::default(),
23190 is_incomplete,
23191 })
23192 })
23193}
23194
23195impl CompletionProvider for Entity<Project> {
23196 fn completions(
23197 &self,
23198 _excerpt_id: ExcerptId,
23199 buffer: &Entity<Buffer>,
23200 buffer_position: text::Anchor,
23201 options: CompletionContext,
23202 _window: &mut Window,
23203 cx: &mut Context<Editor>,
23204 ) -> Task<Result<Vec<CompletionResponse>>> {
23205 self.update(cx, |project, cx| {
23206 let snippets = snippet_completions(project, buffer, buffer_position, cx);
23207 let project_completions = project.completions(buffer, buffer_position, options, cx);
23208 cx.background_spawn(async move {
23209 let mut responses = project_completions.await?;
23210 let snippets = snippets.await?;
23211 if !snippets.completions.is_empty() {
23212 responses.push(snippets);
23213 }
23214 Ok(responses)
23215 })
23216 })
23217 }
23218
23219 fn resolve_completions(
23220 &self,
23221 buffer: Entity<Buffer>,
23222 completion_indices: Vec<usize>,
23223 completions: Rc<RefCell<Box<[Completion]>>>,
23224 cx: &mut Context<Editor>,
23225 ) -> Task<Result<bool>> {
23226 self.update(cx, |project, cx| {
23227 project.lsp_store().update(cx, |lsp_store, cx| {
23228 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23229 })
23230 })
23231 }
23232
23233 fn apply_additional_edits_for_completion(
23234 &self,
23235 buffer: Entity<Buffer>,
23236 completions: Rc<RefCell<Box<[Completion]>>>,
23237 completion_index: usize,
23238 push_to_history: bool,
23239 cx: &mut Context<Editor>,
23240 ) -> Task<Result<Option<language::Transaction>>> {
23241 self.update(cx, |project, cx| {
23242 project.lsp_store().update(cx, |lsp_store, cx| {
23243 lsp_store.apply_additional_edits_for_completion(
23244 buffer,
23245 completions,
23246 completion_index,
23247 push_to_history,
23248 cx,
23249 )
23250 })
23251 })
23252 }
23253
23254 fn is_completion_trigger(
23255 &self,
23256 buffer: &Entity<Buffer>,
23257 position: language::Anchor,
23258 text: &str,
23259 trigger_in_words: bool,
23260 menu_is_open: bool,
23261 cx: &mut Context<Editor>,
23262 ) -> bool {
23263 let mut chars = text.chars();
23264 let char = if let Some(char) = chars.next() {
23265 char
23266 } else {
23267 return false;
23268 };
23269 if chars.next().is_some() {
23270 return false;
23271 }
23272
23273 let buffer = buffer.read(cx);
23274 let snapshot = buffer.snapshot();
23275 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23276 return false;
23277 }
23278 let classifier = snapshot
23279 .char_classifier_at(position)
23280 .scope_context(Some(CharScopeContext::Completion));
23281 if trigger_in_words && classifier.is_word(char) {
23282 return true;
23283 }
23284
23285 buffer.completion_triggers().contains(text)
23286 }
23287}
23288
23289impl SemanticsProvider for Entity<Project> {
23290 fn hover(
23291 &self,
23292 buffer: &Entity<Buffer>,
23293 position: text::Anchor,
23294 cx: &mut App,
23295 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23296 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23297 }
23298
23299 fn document_highlights(
23300 &self,
23301 buffer: &Entity<Buffer>,
23302 position: text::Anchor,
23303 cx: &mut App,
23304 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23305 Some(self.update(cx, |project, cx| {
23306 project.document_highlights(buffer, position, cx)
23307 }))
23308 }
23309
23310 fn definitions(
23311 &self,
23312 buffer: &Entity<Buffer>,
23313 position: text::Anchor,
23314 kind: GotoDefinitionKind,
23315 cx: &mut App,
23316 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23317 Some(self.update(cx, |project, cx| match kind {
23318 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23319 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23320 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23321 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23322 }))
23323 }
23324
23325 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23326 self.update(cx, |project, cx| {
23327 if project
23328 .active_debug_session(cx)
23329 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23330 {
23331 return true;
23332 }
23333
23334 buffer.update(cx, |buffer, cx| {
23335 project.any_language_server_supports_inlay_hints(buffer, cx)
23336 })
23337 })
23338 }
23339
23340 fn inline_values(
23341 &self,
23342 buffer_handle: Entity<Buffer>,
23343 range: Range<text::Anchor>,
23344 cx: &mut App,
23345 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23346 self.update(cx, |project, cx| {
23347 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23348
23349 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23350 })
23351 }
23352
23353 fn inlay_hints(
23354 &self,
23355 buffer_handle: Entity<Buffer>,
23356 range: Range<text::Anchor>,
23357 cx: &mut App,
23358 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23359 Some(self.update(cx, |project, cx| {
23360 project.inlay_hints(buffer_handle, range, cx)
23361 }))
23362 }
23363
23364 fn resolve_inlay_hint(
23365 &self,
23366 hint: InlayHint,
23367 buffer_handle: Entity<Buffer>,
23368 server_id: LanguageServerId,
23369 cx: &mut App,
23370 ) -> Option<Task<anyhow::Result<InlayHint>>> {
23371 Some(self.update(cx, |project, cx| {
23372 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
23373 }))
23374 }
23375
23376 fn range_for_rename(
23377 &self,
23378 buffer: &Entity<Buffer>,
23379 position: text::Anchor,
23380 cx: &mut App,
23381 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23382 Some(self.update(cx, |project, cx| {
23383 let buffer = buffer.clone();
23384 let task = project.prepare_rename(buffer.clone(), position, cx);
23385 cx.spawn(async move |_, cx| {
23386 Ok(match task.await? {
23387 PrepareRenameResponse::Success(range) => Some(range),
23388 PrepareRenameResponse::InvalidPosition => None,
23389 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23390 // Fallback on using TreeSitter info to determine identifier range
23391 buffer.read_with(cx, |buffer, _| {
23392 let snapshot = buffer.snapshot();
23393 let (range, kind) = snapshot.surrounding_word(position, None);
23394 if kind != Some(CharKind::Word) {
23395 return None;
23396 }
23397 Some(
23398 snapshot.anchor_before(range.start)
23399 ..snapshot.anchor_after(range.end),
23400 )
23401 })?
23402 }
23403 })
23404 })
23405 }))
23406 }
23407
23408 fn perform_rename(
23409 &self,
23410 buffer: &Entity<Buffer>,
23411 position: text::Anchor,
23412 new_name: String,
23413 cx: &mut App,
23414 ) -> Option<Task<Result<ProjectTransaction>>> {
23415 Some(self.update(cx, |project, cx| {
23416 project.perform_rename(buffer.clone(), position, new_name, cx)
23417 }))
23418 }
23419}
23420
23421fn inlay_hint_settings(
23422 location: Anchor,
23423 snapshot: &MultiBufferSnapshot,
23424 cx: &mut Context<Editor>,
23425) -> InlayHintSettings {
23426 let file = snapshot.file_at(location);
23427 let language = snapshot.language_at(location).map(|l| l.name());
23428 language_settings(language, file, cx).inlay_hints
23429}
23430
23431fn consume_contiguous_rows(
23432 contiguous_row_selections: &mut Vec<Selection<Point>>,
23433 selection: &Selection<Point>,
23434 display_map: &DisplaySnapshot,
23435 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23436) -> (MultiBufferRow, MultiBufferRow) {
23437 contiguous_row_selections.push(selection.clone());
23438 let start_row = starting_row(selection, display_map);
23439 let mut end_row = ending_row(selection, display_map);
23440
23441 while let Some(next_selection) = selections.peek() {
23442 if next_selection.start.row <= end_row.0 {
23443 end_row = ending_row(next_selection, display_map);
23444 contiguous_row_selections.push(selections.next().unwrap().clone());
23445 } else {
23446 break;
23447 }
23448 }
23449 (start_row, end_row)
23450}
23451
23452fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23453 if selection.start.column > 0 {
23454 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23455 } else {
23456 MultiBufferRow(selection.start.row)
23457 }
23458}
23459
23460fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23461 if next_selection.end.column > 0 || next_selection.is_empty() {
23462 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23463 } else {
23464 MultiBufferRow(next_selection.end.row)
23465 }
23466}
23467
23468impl EditorSnapshot {
23469 pub fn remote_selections_in_range<'a>(
23470 &'a self,
23471 range: &'a Range<Anchor>,
23472 collaboration_hub: &dyn CollaborationHub,
23473 cx: &'a App,
23474 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23475 let participant_names = collaboration_hub.user_names(cx);
23476 let participant_indices = collaboration_hub.user_participant_indices(cx);
23477 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23478 let collaborators_by_replica_id = collaborators_by_peer_id
23479 .values()
23480 .map(|collaborator| (collaborator.replica_id, collaborator))
23481 .collect::<HashMap<_, _>>();
23482 self.buffer_snapshot()
23483 .selections_in_range(range, false)
23484 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23485 if replica_id == AGENT_REPLICA_ID {
23486 Some(RemoteSelection {
23487 replica_id,
23488 selection,
23489 cursor_shape,
23490 line_mode,
23491 collaborator_id: CollaboratorId::Agent,
23492 user_name: Some("Agent".into()),
23493 color: cx.theme().players().agent(),
23494 })
23495 } else {
23496 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23497 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23498 let user_name = participant_names.get(&collaborator.user_id).cloned();
23499 Some(RemoteSelection {
23500 replica_id,
23501 selection,
23502 cursor_shape,
23503 line_mode,
23504 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23505 user_name,
23506 color: if let Some(index) = participant_index {
23507 cx.theme().players().color_for_participant(index.0)
23508 } else {
23509 cx.theme().players().absent()
23510 },
23511 })
23512 }
23513 })
23514 }
23515
23516 pub fn hunks_for_ranges(
23517 &self,
23518 ranges: impl IntoIterator<Item = Range<Point>>,
23519 ) -> Vec<MultiBufferDiffHunk> {
23520 let mut hunks = Vec::new();
23521 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23522 HashMap::default();
23523 for query_range in ranges {
23524 let query_rows =
23525 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23526 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23527 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23528 ) {
23529 // Include deleted hunks that are adjacent to the query range, because
23530 // otherwise they would be missed.
23531 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23532 if hunk.status().is_deleted() {
23533 intersects_range |= hunk.row_range.start == query_rows.end;
23534 intersects_range |= hunk.row_range.end == query_rows.start;
23535 }
23536 if intersects_range {
23537 if !processed_buffer_rows
23538 .entry(hunk.buffer_id)
23539 .or_default()
23540 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23541 {
23542 continue;
23543 }
23544 hunks.push(hunk);
23545 }
23546 }
23547 }
23548
23549 hunks
23550 }
23551
23552 fn display_diff_hunks_for_rows<'a>(
23553 &'a self,
23554 display_rows: Range<DisplayRow>,
23555 folded_buffers: &'a HashSet<BufferId>,
23556 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23557 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23558 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23559
23560 self.buffer_snapshot()
23561 .diff_hunks_in_range(buffer_start..buffer_end)
23562 .filter_map(|hunk| {
23563 if folded_buffers.contains(&hunk.buffer_id) {
23564 return None;
23565 }
23566
23567 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23568 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23569
23570 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23571 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23572
23573 let display_hunk = if hunk_display_start.column() != 0 {
23574 DisplayDiffHunk::Folded {
23575 display_row: hunk_display_start.row(),
23576 }
23577 } else {
23578 let mut end_row = hunk_display_end.row();
23579 if hunk_display_end.column() > 0 {
23580 end_row.0 += 1;
23581 }
23582 let is_created_file = hunk.is_created_file();
23583 DisplayDiffHunk::Unfolded {
23584 status: hunk.status(),
23585 diff_base_byte_range: hunk.diff_base_byte_range,
23586 display_row_range: hunk_display_start.row()..end_row,
23587 multi_buffer_range: Anchor::range_in_buffer(
23588 hunk.excerpt_id,
23589 hunk.buffer_id,
23590 hunk.buffer_range,
23591 ),
23592 is_created_file,
23593 }
23594 };
23595
23596 Some(display_hunk)
23597 })
23598 }
23599
23600 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23601 self.display_snapshot
23602 .buffer_snapshot()
23603 .language_at(position)
23604 }
23605
23606 pub fn is_focused(&self) -> bool {
23607 self.is_focused
23608 }
23609
23610 pub fn placeholder_text(&self) -> Option<String> {
23611 self.placeholder_display_snapshot
23612 .as_ref()
23613 .map(|display_map| display_map.text())
23614 }
23615
23616 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23617 self.scroll_anchor.scroll_position(&self.display_snapshot)
23618 }
23619
23620 fn gutter_dimensions(
23621 &self,
23622 font_id: FontId,
23623 font_size: Pixels,
23624 max_line_number_width: Pixels,
23625 cx: &App,
23626 ) -> Option<GutterDimensions> {
23627 if !self.show_gutter {
23628 return None;
23629 }
23630
23631 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23632 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23633
23634 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23635 matches!(
23636 ProjectSettings::get_global(cx).git.git_gutter,
23637 GitGutterSetting::TrackedFiles
23638 )
23639 });
23640 let gutter_settings = EditorSettings::get_global(cx).gutter;
23641 let show_line_numbers = self
23642 .show_line_numbers
23643 .unwrap_or(gutter_settings.line_numbers);
23644 let line_gutter_width = if show_line_numbers {
23645 // Avoid flicker-like gutter resizes when the line number gains another digit by
23646 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23647 let min_width_for_number_on_gutter =
23648 ch_advance * gutter_settings.min_line_number_digits as f32;
23649 max_line_number_width.max(min_width_for_number_on_gutter)
23650 } else {
23651 0.0.into()
23652 };
23653
23654 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23655 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23656
23657 let git_blame_entries_width =
23658 self.git_blame_gutter_max_author_length
23659 .map(|max_author_length| {
23660 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23661 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23662
23663 /// The number of characters to dedicate to gaps and margins.
23664 const SPACING_WIDTH: usize = 4;
23665
23666 let max_char_count = max_author_length.min(renderer.max_author_length())
23667 + ::git::SHORT_SHA_LENGTH
23668 + MAX_RELATIVE_TIMESTAMP.len()
23669 + SPACING_WIDTH;
23670
23671 ch_advance * max_char_count
23672 });
23673
23674 let is_singleton = self.buffer_snapshot().is_singleton();
23675
23676 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23677 left_padding += if !is_singleton {
23678 ch_width * 4.0
23679 } else if show_runnables || show_breakpoints {
23680 ch_width * 3.0
23681 } else if show_git_gutter && show_line_numbers {
23682 ch_width * 2.0
23683 } else if show_git_gutter || show_line_numbers {
23684 ch_width
23685 } else {
23686 px(0.)
23687 };
23688
23689 let shows_folds = is_singleton && gutter_settings.folds;
23690
23691 let right_padding = if shows_folds && show_line_numbers {
23692 ch_width * 4.0
23693 } else if shows_folds || (!is_singleton && show_line_numbers) {
23694 ch_width * 3.0
23695 } else if show_line_numbers {
23696 ch_width
23697 } else {
23698 px(0.)
23699 };
23700
23701 Some(GutterDimensions {
23702 left_padding,
23703 right_padding,
23704 width: line_gutter_width + left_padding + right_padding,
23705 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23706 git_blame_entries_width,
23707 })
23708 }
23709
23710 pub fn render_crease_toggle(
23711 &self,
23712 buffer_row: MultiBufferRow,
23713 row_contains_cursor: bool,
23714 editor: Entity<Editor>,
23715 window: &mut Window,
23716 cx: &mut App,
23717 ) -> Option<AnyElement> {
23718 let folded = self.is_line_folded(buffer_row);
23719 let mut is_foldable = false;
23720
23721 if let Some(crease) = self
23722 .crease_snapshot
23723 .query_row(buffer_row, self.buffer_snapshot())
23724 {
23725 is_foldable = true;
23726 match crease {
23727 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23728 if let Some(render_toggle) = render_toggle {
23729 let toggle_callback =
23730 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23731 if folded {
23732 editor.update(cx, |editor, cx| {
23733 editor.fold_at(buffer_row, window, cx)
23734 });
23735 } else {
23736 editor.update(cx, |editor, cx| {
23737 editor.unfold_at(buffer_row, window, cx)
23738 });
23739 }
23740 });
23741 return Some((render_toggle)(
23742 buffer_row,
23743 folded,
23744 toggle_callback,
23745 window,
23746 cx,
23747 ));
23748 }
23749 }
23750 }
23751 }
23752
23753 is_foldable |= self.starts_indent(buffer_row);
23754
23755 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23756 Some(
23757 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23758 .toggle_state(folded)
23759 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23760 if folded {
23761 this.unfold_at(buffer_row, window, cx);
23762 } else {
23763 this.fold_at(buffer_row, window, cx);
23764 }
23765 }))
23766 .into_any_element(),
23767 )
23768 } else {
23769 None
23770 }
23771 }
23772
23773 pub fn render_crease_trailer(
23774 &self,
23775 buffer_row: MultiBufferRow,
23776 window: &mut Window,
23777 cx: &mut App,
23778 ) -> Option<AnyElement> {
23779 let folded = self.is_line_folded(buffer_row);
23780 if let Crease::Inline { render_trailer, .. } = self
23781 .crease_snapshot
23782 .query_row(buffer_row, self.buffer_snapshot())?
23783 {
23784 let render_trailer = render_trailer.as_ref()?;
23785 Some(render_trailer(buffer_row, folded, window, cx))
23786 } else {
23787 None
23788 }
23789 }
23790}
23791
23792impl Deref for EditorSnapshot {
23793 type Target = DisplaySnapshot;
23794
23795 fn deref(&self) -> &Self::Target {
23796 &self.display_snapshot
23797 }
23798}
23799
23800#[derive(Clone, Debug, PartialEq, Eq)]
23801pub enum EditorEvent {
23802 InputIgnored {
23803 text: Arc<str>,
23804 },
23805 InputHandled {
23806 utf16_range_to_replace: Option<Range<isize>>,
23807 text: Arc<str>,
23808 },
23809 ExcerptsAdded {
23810 buffer: Entity<Buffer>,
23811 predecessor: ExcerptId,
23812 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23813 },
23814 ExcerptsRemoved {
23815 ids: Vec<ExcerptId>,
23816 removed_buffer_ids: Vec<BufferId>,
23817 },
23818 BufferFoldToggled {
23819 ids: Vec<ExcerptId>,
23820 folded: bool,
23821 },
23822 ExcerptsEdited {
23823 ids: Vec<ExcerptId>,
23824 },
23825 ExcerptsExpanded {
23826 ids: Vec<ExcerptId>,
23827 },
23828 BufferEdited,
23829 Edited {
23830 transaction_id: clock::Lamport,
23831 },
23832 Reparsed(BufferId),
23833 Focused,
23834 FocusedIn,
23835 Blurred,
23836 DirtyChanged,
23837 Saved,
23838 TitleChanged,
23839 SelectionsChanged {
23840 local: bool,
23841 },
23842 ScrollPositionChanged {
23843 local: bool,
23844 autoscroll: bool,
23845 },
23846 TransactionUndone {
23847 transaction_id: clock::Lamport,
23848 },
23849 TransactionBegun {
23850 transaction_id: clock::Lamport,
23851 },
23852 CursorShapeChanged,
23853 BreadcrumbsChanged,
23854 PushedToNavHistory {
23855 anchor: Anchor,
23856 is_deactivate: bool,
23857 },
23858}
23859
23860impl EventEmitter<EditorEvent> for Editor {}
23861
23862impl Focusable for Editor {
23863 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23864 self.focus_handle.clone()
23865 }
23866}
23867
23868impl Render for Editor {
23869 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23870 let settings = ThemeSettings::get_global(cx);
23871
23872 let mut text_style = match self.mode {
23873 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23874 color: cx.theme().colors().editor_foreground,
23875 font_family: settings.ui_font.family.clone(),
23876 font_features: settings.ui_font.features.clone(),
23877 font_fallbacks: settings.ui_font.fallbacks.clone(),
23878 font_size: rems(0.875).into(),
23879 font_weight: settings.ui_font.weight,
23880 line_height: relative(settings.buffer_line_height.value()),
23881 ..Default::default()
23882 },
23883 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23884 color: cx.theme().colors().editor_foreground,
23885 font_family: settings.buffer_font.family.clone(),
23886 font_features: settings.buffer_font.features.clone(),
23887 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23888 font_size: settings.buffer_font_size(cx).into(),
23889 font_weight: settings.buffer_font.weight,
23890 line_height: relative(settings.buffer_line_height.value()),
23891 ..Default::default()
23892 },
23893 };
23894 if let Some(text_style_refinement) = &self.text_style_refinement {
23895 text_style.refine(text_style_refinement)
23896 }
23897
23898 let background = match self.mode {
23899 EditorMode::SingleLine => cx.theme().system().transparent,
23900 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23901 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23902 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23903 };
23904
23905 EditorElement::new(
23906 &cx.entity(),
23907 EditorStyle {
23908 background,
23909 border: cx.theme().colors().border,
23910 local_player: cx.theme().players().local(),
23911 text: text_style,
23912 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23913 syntax: cx.theme().syntax().clone(),
23914 status: cx.theme().status().clone(),
23915 inlay_hints_style: make_inlay_hints_style(cx),
23916 edit_prediction_styles: make_suggestion_styles(cx),
23917 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23918 show_underlines: self.diagnostics_enabled(),
23919 },
23920 )
23921 }
23922}
23923
23924impl EntityInputHandler for Editor {
23925 fn text_for_range(
23926 &mut self,
23927 range_utf16: Range<usize>,
23928 adjusted_range: &mut Option<Range<usize>>,
23929 _: &mut Window,
23930 cx: &mut Context<Self>,
23931 ) -> Option<String> {
23932 let snapshot = self.buffer.read(cx).read(cx);
23933 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23934 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23935 if (start.0..end.0) != range_utf16 {
23936 adjusted_range.replace(start.0..end.0);
23937 }
23938 Some(snapshot.text_for_range(start..end).collect())
23939 }
23940
23941 fn selected_text_range(
23942 &mut self,
23943 ignore_disabled_input: bool,
23944 _: &mut Window,
23945 cx: &mut Context<Self>,
23946 ) -> Option<UTF16Selection> {
23947 // Prevent the IME menu from appearing when holding down an alphabetic key
23948 // while input is disabled.
23949 if !ignore_disabled_input && !self.input_enabled {
23950 return None;
23951 }
23952
23953 let selection = self
23954 .selections
23955 .newest::<OffsetUtf16>(&self.display_snapshot(cx));
23956 let range = selection.range();
23957
23958 Some(UTF16Selection {
23959 range: range.start.0..range.end.0,
23960 reversed: selection.reversed,
23961 })
23962 }
23963
23964 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23965 let snapshot = self.buffer.read(cx).read(cx);
23966 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23967 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23968 }
23969
23970 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23971 self.clear_highlights::<InputComposition>(cx);
23972 self.ime_transaction.take();
23973 }
23974
23975 fn replace_text_in_range(
23976 &mut self,
23977 range_utf16: Option<Range<usize>>,
23978 text: &str,
23979 window: &mut Window,
23980 cx: &mut Context<Self>,
23981 ) {
23982 if !self.input_enabled {
23983 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23984 return;
23985 }
23986
23987 self.transact(window, cx, |this, window, cx| {
23988 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23989 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23990 Some(this.selection_replacement_ranges(range_utf16, cx))
23991 } else {
23992 this.marked_text_ranges(cx)
23993 };
23994
23995 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23996 let newest_selection_id = this.selections.newest_anchor().id;
23997 this.selections
23998 .all::<OffsetUtf16>(&this.display_snapshot(cx))
23999 .iter()
24000 .zip(ranges_to_replace.iter())
24001 .find_map(|(selection, range)| {
24002 if selection.id == newest_selection_id {
24003 Some(
24004 (range.start.0 as isize - selection.head().0 as isize)
24005 ..(range.end.0 as isize - selection.head().0 as isize),
24006 )
24007 } else {
24008 None
24009 }
24010 })
24011 });
24012
24013 cx.emit(EditorEvent::InputHandled {
24014 utf16_range_to_replace: range_to_replace,
24015 text: text.into(),
24016 });
24017
24018 if let Some(new_selected_ranges) = new_selected_ranges {
24019 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24020 selections.select_ranges(new_selected_ranges)
24021 });
24022 this.backspace(&Default::default(), window, cx);
24023 }
24024
24025 this.handle_input(text, window, cx);
24026 });
24027
24028 if let Some(transaction) = self.ime_transaction {
24029 self.buffer.update(cx, |buffer, cx| {
24030 buffer.group_until_transaction(transaction, cx);
24031 });
24032 }
24033
24034 self.unmark_text(window, cx);
24035 }
24036
24037 fn replace_and_mark_text_in_range(
24038 &mut self,
24039 range_utf16: Option<Range<usize>>,
24040 text: &str,
24041 new_selected_range_utf16: Option<Range<usize>>,
24042 window: &mut Window,
24043 cx: &mut Context<Self>,
24044 ) {
24045 if !self.input_enabled {
24046 return;
24047 }
24048
24049 let transaction = self.transact(window, cx, |this, window, cx| {
24050 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
24051 let snapshot = this.buffer.read(cx).read(cx);
24052 if let Some(relative_range_utf16) = range_utf16.as_ref() {
24053 for marked_range in &mut marked_ranges {
24054 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
24055 marked_range.start.0 += relative_range_utf16.start;
24056 marked_range.start =
24057 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
24058 marked_range.end =
24059 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
24060 }
24061 }
24062 Some(marked_ranges)
24063 } else if let Some(range_utf16) = range_utf16 {
24064 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24065 Some(this.selection_replacement_ranges(range_utf16, cx))
24066 } else {
24067 None
24068 };
24069
24070 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
24071 let newest_selection_id = this.selections.newest_anchor().id;
24072 this.selections
24073 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24074 .iter()
24075 .zip(ranges_to_replace.iter())
24076 .find_map(|(selection, range)| {
24077 if selection.id == newest_selection_id {
24078 Some(
24079 (range.start.0 as isize - selection.head().0 as isize)
24080 ..(range.end.0 as isize - selection.head().0 as isize),
24081 )
24082 } else {
24083 None
24084 }
24085 })
24086 });
24087
24088 cx.emit(EditorEvent::InputHandled {
24089 utf16_range_to_replace: range_to_replace,
24090 text: text.into(),
24091 });
24092
24093 if let Some(ranges) = ranges_to_replace {
24094 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24095 s.select_ranges(ranges)
24096 });
24097 }
24098
24099 let marked_ranges = {
24100 let snapshot = this.buffer.read(cx).read(cx);
24101 this.selections
24102 .disjoint_anchors_arc()
24103 .iter()
24104 .map(|selection| {
24105 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24106 })
24107 .collect::<Vec<_>>()
24108 };
24109
24110 if text.is_empty() {
24111 this.unmark_text(window, cx);
24112 } else {
24113 this.highlight_text::<InputComposition>(
24114 marked_ranges.clone(),
24115 HighlightStyle {
24116 underline: Some(UnderlineStyle {
24117 thickness: px(1.),
24118 color: None,
24119 wavy: false,
24120 }),
24121 ..Default::default()
24122 },
24123 cx,
24124 );
24125 }
24126
24127 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24128 let use_autoclose = this.use_autoclose;
24129 let use_auto_surround = this.use_auto_surround;
24130 this.set_use_autoclose(false);
24131 this.set_use_auto_surround(false);
24132 this.handle_input(text, window, cx);
24133 this.set_use_autoclose(use_autoclose);
24134 this.set_use_auto_surround(use_auto_surround);
24135
24136 if let Some(new_selected_range) = new_selected_range_utf16 {
24137 let snapshot = this.buffer.read(cx).read(cx);
24138 let new_selected_ranges = marked_ranges
24139 .into_iter()
24140 .map(|marked_range| {
24141 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24142 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
24143 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
24144 snapshot.clip_offset_utf16(new_start, Bias::Left)
24145 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24146 })
24147 .collect::<Vec<_>>();
24148
24149 drop(snapshot);
24150 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24151 selections.select_ranges(new_selected_ranges)
24152 });
24153 }
24154 });
24155
24156 self.ime_transaction = self.ime_transaction.or(transaction);
24157 if let Some(transaction) = self.ime_transaction {
24158 self.buffer.update(cx, |buffer, cx| {
24159 buffer.group_until_transaction(transaction, cx);
24160 });
24161 }
24162
24163 if self.text_highlights::<InputComposition>(cx).is_none() {
24164 self.ime_transaction.take();
24165 }
24166 }
24167
24168 fn bounds_for_range(
24169 &mut self,
24170 range_utf16: Range<usize>,
24171 element_bounds: gpui::Bounds<Pixels>,
24172 window: &mut Window,
24173 cx: &mut Context<Self>,
24174 ) -> Option<gpui::Bounds<Pixels>> {
24175 let text_layout_details = self.text_layout_details(window);
24176 let CharacterDimensions {
24177 em_width,
24178 em_advance,
24179 line_height,
24180 } = self.character_dimensions(window);
24181
24182 let snapshot = self.snapshot(window, cx);
24183 let scroll_position = snapshot.scroll_position();
24184 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24185
24186 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
24187 let x = Pixels::from(
24188 ScrollOffset::from(
24189 snapshot.x_for_display_point(start, &text_layout_details)
24190 + self.gutter_dimensions.full_width(),
24191 ) - scroll_left,
24192 );
24193 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24194
24195 Some(Bounds {
24196 origin: element_bounds.origin + point(x, y),
24197 size: size(em_width, line_height),
24198 })
24199 }
24200
24201 fn character_index_for_point(
24202 &mut self,
24203 point: gpui::Point<Pixels>,
24204 _window: &mut Window,
24205 _cx: &mut Context<Self>,
24206 ) -> Option<usize> {
24207 let position_map = self.last_position_map.as_ref()?;
24208 if !position_map.text_hitbox.contains(&point) {
24209 return None;
24210 }
24211 let display_point = position_map.point_for_position(point).previous_valid;
24212 let anchor = position_map
24213 .snapshot
24214 .display_point_to_anchor(display_point, Bias::Left);
24215 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24216 Some(utf16_offset.0)
24217 }
24218}
24219
24220trait SelectionExt {
24221 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24222 fn spanned_rows(
24223 &self,
24224 include_end_if_at_line_start: bool,
24225 map: &DisplaySnapshot,
24226 ) -> Range<MultiBufferRow>;
24227}
24228
24229impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24230 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24231 let start = self
24232 .start
24233 .to_point(map.buffer_snapshot())
24234 .to_display_point(map);
24235 let end = self
24236 .end
24237 .to_point(map.buffer_snapshot())
24238 .to_display_point(map);
24239 if self.reversed {
24240 end..start
24241 } else {
24242 start..end
24243 }
24244 }
24245
24246 fn spanned_rows(
24247 &self,
24248 include_end_if_at_line_start: bool,
24249 map: &DisplaySnapshot,
24250 ) -> Range<MultiBufferRow> {
24251 let start = self.start.to_point(map.buffer_snapshot());
24252 let mut end = self.end.to_point(map.buffer_snapshot());
24253 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24254 end.row -= 1;
24255 }
24256
24257 let buffer_start = map.prev_line_boundary(start).0;
24258 let buffer_end = map.next_line_boundary(end).0;
24259 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24260 }
24261}
24262
24263impl<T: InvalidationRegion> InvalidationStack<T> {
24264 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24265 where
24266 S: Clone + ToOffset,
24267 {
24268 while let Some(region) = self.last() {
24269 let all_selections_inside_invalidation_ranges =
24270 if selections.len() == region.ranges().len() {
24271 selections
24272 .iter()
24273 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24274 .all(|(selection, invalidation_range)| {
24275 let head = selection.head().to_offset(buffer);
24276 invalidation_range.start <= head && invalidation_range.end >= head
24277 })
24278 } else {
24279 false
24280 };
24281
24282 if all_selections_inside_invalidation_ranges {
24283 break;
24284 } else {
24285 self.pop();
24286 }
24287 }
24288 }
24289}
24290
24291impl<T> Default for InvalidationStack<T> {
24292 fn default() -> Self {
24293 Self(Default::default())
24294 }
24295}
24296
24297impl<T> Deref for InvalidationStack<T> {
24298 type Target = Vec<T>;
24299
24300 fn deref(&self) -> &Self::Target {
24301 &self.0
24302 }
24303}
24304
24305impl<T> DerefMut for InvalidationStack<T> {
24306 fn deref_mut(&mut self) -> &mut Self::Target {
24307 &mut self.0
24308 }
24309}
24310
24311impl InvalidationRegion for SnippetState {
24312 fn ranges(&self) -> &[Range<Anchor>] {
24313 &self.ranges[self.active_index]
24314 }
24315}
24316
24317fn edit_prediction_edit_text(
24318 current_snapshot: &BufferSnapshot,
24319 edits: &[(Range<Anchor>, String)],
24320 edit_preview: &EditPreview,
24321 include_deletions: bool,
24322 cx: &App,
24323) -> HighlightedText {
24324 let edits = edits
24325 .iter()
24326 .map(|(anchor, text)| {
24327 (
24328 anchor.start.text_anchor..anchor.end.text_anchor,
24329 text.clone(),
24330 )
24331 })
24332 .collect::<Vec<_>>();
24333
24334 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24335}
24336
24337fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
24338 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24339 // Just show the raw edit text with basic styling
24340 let mut text = String::new();
24341 let mut highlights = Vec::new();
24342
24343 let insertion_highlight_style = HighlightStyle {
24344 color: Some(cx.theme().colors().text),
24345 ..Default::default()
24346 };
24347
24348 for (_, edit_text) in edits {
24349 let start_offset = text.len();
24350 text.push_str(edit_text);
24351 let end_offset = text.len();
24352
24353 if start_offset < end_offset {
24354 highlights.push((start_offset..end_offset, insertion_highlight_style));
24355 }
24356 }
24357
24358 HighlightedText {
24359 text: text.into(),
24360 highlights,
24361 }
24362}
24363
24364pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24365 match severity {
24366 lsp::DiagnosticSeverity::ERROR => colors.error,
24367 lsp::DiagnosticSeverity::WARNING => colors.warning,
24368 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24369 lsp::DiagnosticSeverity::HINT => colors.info,
24370 _ => colors.ignored,
24371 }
24372}
24373
24374pub fn styled_runs_for_code_label<'a>(
24375 label: &'a CodeLabel,
24376 syntax_theme: &'a theme::SyntaxTheme,
24377) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24378 let fade_out = HighlightStyle {
24379 fade_out: Some(0.35),
24380 ..Default::default()
24381 };
24382
24383 let mut prev_end = label.filter_range.end;
24384 label
24385 .runs
24386 .iter()
24387 .enumerate()
24388 .flat_map(move |(ix, (range, highlight_id))| {
24389 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24390 style
24391 } else {
24392 return Default::default();
24393 };
24394 let muted_style = style.highlight(fade_out);
24395
24396 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24397 if range.start >= label.filter_range.end {
24398 if range.start > prev_end {
24399 runs.push((prev_end..range.start, fade_out));
24400 }
24401 runs.push((range.clone(), muted_style));
24402 } else if range.end <= label.filter_range.end {
24403 runs.push((range.clone(), style));
24404 } else {
24405 runs.push((range.start..label.filter_range.end, style));
24406 runs.push((label.filter_range.end..range.end, muted_style));
24407 }
24408 prev_end = cmp::max(prev_end, range.end);
24409
24410 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24411 runs.push((prev_end..label.text.len(), fade_out));
24412 }
24413
24414 runs
24415 })
24416}
24417
24418pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24419 let mut prev_index = 0;
24420 let mut prev_codepoint: Option<char> = None;
24421 text.char_indices()
24422 .chain([(text.len(), '\0')])
24423 .filter_map(move |(index, codepoint)| {
24424 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24425 let is_boundary = index == text.len()
24426 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24427 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24428 if is_boundary {
24429 let chunk = &text[prev_index..index];
24430 prev_index = index;
24431 Some(chunk)
24432 } else {
24433 None
24434 }
24435 })
24436}
24437
24438pub trait RangeToAnchorExt: Sized {
24439 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24440
24441 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24442 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24443 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24444 }
24445}
24446
24447impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24448 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24449 let start_offset = self.start.to_offset(snapshot);
24450 let end_offset = self.end.to_offset(snapshot);
24451 if start_offset == end_offset {
24452 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24453 } else {
24454 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24455 }
24456 }
24457}
24458
24459pub trait RowExt {
24460 fn as_f64(&self) -> f64;
24461
24462 fn next_row(&self) -> Self;
24463
24464 fn previous_row(&self) -> Self;
24465
24466 fn minus(&self, other: Self) -> u32;
24467}
24468
24469impl RowExt for DisplayRow {
24470 fn as_f64(&self) -> f64 {
24471 self.0 as _
24472 }
24473
24474 fn next_row(&self) -> Self {
24475 Self(self.0 + 1)
24476 }
24477
24478 fn previous_row(&self) -> Self {
24479 Self(self.0.saturating_sub(1))
24480 }
24481
24482 fn minus(&self, other: Self) -> u32 {
24483 self.0 - other.0
24484 }
24485}
24486
24487impl RowExt for MultiBufferRow {
24488 fn as_f64(&self) -> f64 {
24489 self.0 as _
24490 }
24491
24492 fn next_row(&self) -> Self {
24493 Self(self.0 + 1)
24494 }
24495
24496 fn previous_row(&self) -> Self {
24497 Self(self.0.saturating_sub(1))
24498 }
24499
24500 fn minus(&self, other: Self) -> u32 {
24501 self.0 - other.0
24502 }
24503}
24504
24505trait RowRangeExt {
24506 type Row;
24507
24508 fn len(&self) -> usize;
24509
24510 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24511}
24512
24513impl RowRangeExt for Range<MultiBufferRow> {
24514 type Row = MultiBufferRow;
24515
24516 fn len(&self) -> usize {
24517 (self.end.0 - self.start.0) as usize
24518 }
24519
24520 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24521 (self.start.0..self.end.0).map(MultiBufferRow)
24522 }
24523}
24524
24525impl RowRangeExt for Range<DisplayRow> {
24526 type Row = DisplayRow;
24527
24528 fn len(&self) -> usize {
24529 (self.end.0 - self.start.0) as usize
24530 }
24531
24532 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24533 (self.start.0..self.end.0).map(DisplayRow)
24534 }
24535}
24536
24537/// If select range has more than one line, we
24538/// just point the cursor to range.start.
24539fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24540 if range.start.row == range.end.row {
24541 range
24542 } else {
24543 range.start..range.start
24544 }
24545}
24546pub struct KillRing(ClipboardItem);
24547impl Global for KillRing {}
24548
24549const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24550
24551enum BreakpointPromptEditAction {
24552 Log,
24553 Condition,
24554 HitCondition,
24555}
24556
24557struct BreakpointPromptEditor {
24558 pub(crate) prompt: Entity<Editor>,
24559 editor: WeakEntity<Editor>,
24560 breakpoint_anchor: Anchor,
24561 breakpoint: Breakpoint,
24562 edit_action: BreakpointPromptEditAction,
24563 block_ids: HashSet<CustomBlockId>,
24564 editor_margins: Arc<Mutex<EditorMargins>>,
24565 _subscriptions: Vec<Subscription>,
24566}
24567
24568impl BreakpointPromptEditor {
24569 const MAX_LINES: u8 = 4;
24570
24571 fn new(
24572 editor: WeakEntity<Editor>,
24573 breakpoint_anchor: Anchor,
24574 breakpoint: Breakpoint,
24575 edit_action: BreakpointPromptEditAction,
24576 window: &mut Window,
24577 cx: &mut Context<Self>,
24578 ) -> Self {
24579 let base_text = match edit_action {
24580 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24581 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24582 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24583 }
24584 .map(|msg| msg.to_string())
24585 .unwrap_or_default();
24586
24587 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24588 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24589
24590 let prompt = cx.new(|cx| {
24591 let mut prompt = Editor::new(
24592 EditorMode::AutoHeight {
24593 min_lines: 1,
24594 max_lines: Some(Self::MAX_LINES as usize),
24595 },
24596 buffer,
24597 None,
24598 window,
24599 cx,
24600 );
24601 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24602 prompt.set_show_cursor_when_unfocused(false, cx);
24603 prompt.set_placeholder_text(
24604 match edit_action {
24605 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24606 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24607 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24608 },
24609 window,
24610 cx,
24611 );
24612
24613 prompt
24614 });
24615
24616 Self {
24617 prompt,
24618 editor,
24619 breakpoint_anchor,
24620 breakpoint,
24621 edit_action,
24622 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24623 block_ids: Default::default(),
24624 _subscriptions: vec![],
24625 }
24626 }
24627
24628 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24629 self.block_ids.extend(block_ids)
24630 }
24631
24632 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24633 if let Some(editor) = self.editor.upgrade() {
24634 let message = self
24635 .prompt
24636 .read(cx)
24637 .buffer
24638 .read(cx)
24639 .as_singleton()
24640 .expect("A multi buffer in breakpoint prompt isn't possible")
24641 .read(cx)
24642 .as_rope()
24643 .to_string();
24644
24645 editor.update(cx, |editor, cx| {
24646 editor.edit_breakpoint_at_anchor(
24647 self.breakpoint_anchor,
24648 self.breakpoint.clone(),
24649 match self.edit_action {
24650 BreakpointPromptEditAction::Log => {
24651 BreakpointEditAction::EditLogMessage(message.into())
24652 }
24653 BreakpointPromptEditAction::Condition => {
24654 BreakpointEditAction::EditCondition(message.into())
24655 }
24656 BreakpointPromptEditAction::HitCondition => {
24657 BreakpointEditAction::EditHitCondition(message.into())
24658 }
24659 },
24660 cx,
24661 );
24662
24663 editor.remove_blocks(self.block_ids.clone(), None, cx);
24664 cx.focus_self(window);
24665 });
24666 }
24667 }
24668
24669 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24670 self.editor
24671 .update(cx, |editor, cx| {
24672 editor.remove_blocks(self.block_ids.clone(), None, cx);
24673 window.focus(&editor.focus_handle);
24674 })
24675 .log_err();
24676 }
24677
24678 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24679 let settings = ThemeSettings::get_global(cx);
24680 let text_style = TextStyle {
24681 color: if self.prompt.read(cx).read_only(cx) {
24682 cx.theme().colors().text_disabled
24683 } else {
24684 cx.theme().colors().text
24685 },
24686 font_family: settings.buffer_font.family.clone(),
24687 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24688 font_size: settings.buffer_font_size(cx).into(),
24689 font_weight: settings.buffer_font.weight,
24690 line_height: relative(settings.buffer_line_height.value()),
24691 ..Default::default()
24692 };
24693 EditorElement::new(
24694 &self.prompt,
24695 EditorStyle {
24696 background: cx.theme().colors().editor_background,
24697 local_player: cx.theme().players().local(),
24698 text: text_style,
24699 ..Default::default()
24700 },
24701 )
24702 }
24703}
24704
24705impl Render for BreakpointPromptEditor {
24706 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24707 let editor_margins = *self.editor_margins.lock();
24708 let gutter_dimensions = editor_margins.gutter;
24709 h_flex()
24710 .key_context("Editor")
24711 .bg(cx.theme().colors().editor_background)
24712 .border_y_1()
24713 .border_color(cx.theme().status().info_border)
24714 .size_full()
24715 .py(window.line_height() / 2.5)
24716 .on_action(cx.listener(Self::confirm))
24717 .on_action(cx.listener(Self::cancel))
24718 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24719 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24720 }
24721}
24722
24723impl Focusable for BreakpointPromptEditor {
24724 fn focus_handle(&self, cx: &App) -> FocusHandle {
24725 self.prompt.focus_handle(cx)
24726 }
24727}
24728
24729fn all_edits_insertions_or_deletions(
24730 edits: &Vec<(Range<Anchor>, String)>,
24731 snapshot: &MultiBufferSnapshot,
24732) -> bool {
24733 let mut all_insertions = true;
24734 let mut all_deletions = true;
24735
24736 for (range, new_text) in edits.iter() {
24737 let range_is_empty = range.to_offset(snapshot).is_empty();
24738 let text_is_empty = new_text.is_empty();
24739
24740 if range_is_empty != text_is_empty {
24741 if range_is_empty {
24742 all_deletions = false;
24743 } else {
24744 all_insertions = false;
24745 }
24746 } else {
24747 return false;
24748 }
24749
24750 if !all_insertions && !all_deletions {
24751 return false;
24752 }
24753 }
24754 all_insertions || all_deletions
24755}
24756
24757struct MissingEditPredictionKeybindingTooltip;
24758
24759impl Render for MissingEditPredictionKeybindingTooltip {
24760 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24761 ui::tooltip_container(cx, |container, cx| {
24762 container
24763 .flex_shrink_0()
24764 .max_w_80()
24765 .min_h(rems_from_px(124.))
24766 .justify_between()
24767 .child(
24768 v_flex()
24769 .flex_1()
24770 .text_ui_sm(cx)
24771 .child(Label::new("Conflict with Accept Keybinding"))
24772 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24773 )
24774 .child(
24775 h_flex()
24776 .pb_1()
24777 .gap_1()
24778 .items_end()
24779 .w_full()
24780 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24781 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
24782 }))
24783 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24784 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24785 })),
24786 )
24787 })
24788 }
24789}
24790
24791#[derive(Debug, Clone, Copy, PartialEq)]
24792pub struct LineHighlight {
24793 pub background: Background,
24794 pub border: Option<gpui::Hsla>,
24795 pub include_gutter: bool,
24796 pub type_id: Option<TypeId>,
24797}
24798
24799struct LineManipulationResult {
24800 pub new_text: String,
24801 pub line_count_before: usize,
24802 pub line_count_after: usize,
24803}
24804
24805fn render_diff_hunk_controls(
24806 row: u32,
24807 status: &DiffHunkStatus,
24808 hunk_range: Range<Anchor>,
24809 is_created_file: bool,
24810 line_height: Pixels,
24811 editor: &Entity<Editor>,
24812 _window: &mut Window,
24813 cx: &mut App,
24814) -> AnyElement {
24815 h_flex()
24816 .h(line_height)
24817 .mr_1()
24818 .gap_1()
24819 .px_0p5()
24820 .pb_1()
24821 .border_x_1()
24822 .border_b_1()
24823 .border_color(cx.theme().colors().border_variant)
24824 .rounded_b_lg()
24825 .bg(cx.theme().colors().editor_background)
24826 .gap_1()
24827 .block_mouse_except_scroll()
24828 .shadow_md()
24829 .child(if status.has_secondary_hunk() {
24830 Button::new(("stage", row as u64), "Stage")
24831 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24832 .tooltip({
24833 let focus_handle = editor.focus_handle(cx);
24834 move |window, cx| {
24835 Tooltip::for_action_in(
24836 "Stage Hunk",
24837 &::git::ToggleStaged,
24838 &focus_handle,
24839 window,
24840 cx,
24841 )
24842 }
24843 })
24844 .on_click({
24845 let editor = editor.clone();
24846 move |_event, _window, cx| {
24847 editor.update(cx, |editor, cx| {
24848 editor.stage_or_unstage_diff_hunks(
24849 true,
24850 vec![hunk_range.start..hunk_range.start],
24851 cx,
24852 );
24853 });
24854 }
24855 })
24856 } else {
24857 Button::new(("unstage", row as u64), "Unstage")
24858 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24859 .tooltip({
24860 let focus_handle = editor.focus_handle(cx);
24861 move |window, cx| {
24862 Tooltip::for_action_in(
24863 "Unstage Hunk",
24864 &::git::ToggleStaged,
24865 &focus_handle,
24866 window,
24867 cx,
24868 )
24869 }
24870 })
24871 .on_click({
24872 let editor = editor.clone();
24873 move |_event, _window, cx| {
24874 editor.update(cx, |editor, cx| {
24875 editor.stage_or_unstage_diff_hunks(
24876 false,
24877 vec![hunk_range.start..hunk_range.start],
24878 cx,
24879 );
24880 });
24881 }
24882 })
24883 })
24884 .child(
24885 Button::new(("restore", row as u64), "Restore")
24886 .tooltip({
24887 let focus_handle = editor.focus_handle(cx);
24888 move |window, cx| {
24889 Tooltip::for_action_in(
24890 "Restore Hunk",
24891 &::git::Restore,
24892 &focus_handle,
24893 window,
24894 cx,
24895 )
24896 }
24897 })
24898 .on_click({
24899 let editor = editor.clone();
24900 move |_event, window, cx| {
24901 editor.update(cx, |editor, cx| {
24902 let snapshot = editor.snapshot(window, cx);
24903 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
24904 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24905 });
24906 }
24907 })
24908 .disabled(is_created_file),
24909 )
24910 .when(
24911 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24912 |el| {
24913 el.child(
24914 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24915 .shape(IconButtonShape::Square)
24916 .icon_size(IconSize::Small)
24917 // .disabled(!has_multiple_hunks)
24918 .tooltip({
24919 let focus_handle = editor.focus_handle(cx);
24920 move |window, cx| {
24921 Tooltip::for_action_in(
24922 "Next Hunk",
24923 &GoToHunk,
24924 &focus_handle,
24925 window,
24926 cx,
24927 )
24928 }
24929 })
24930 .on_click({
24931 let editor = editor.clone();
24932 move |_event, window, cx| {
24933 editor.update(cx, |editor, cx| {
24934 let snapshot = editor.snapshot(window, cx);
24935 let position =
24936 hunk_range.end.to_point(&snapshot.buffer_snapshot());
24937 editor.go_to_hunk_before_or_after_position(
24938 &snapshot,
24939 position,
24940 Direction::Next,
24941 window,
24942 cx,
24943 );
24944 editor.expand_selected_diff_hunks(cx);
24945 });
24946 }
24947 }),
24948 )
24949 .child(
24950 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24951 .shape(IconButtonShape::Square)
24952 .icon_size(IconSize::Small)
24953 // .disabled(!has_multiple_hunks)
24954 .tooltip({
24955 let focus_handle = editor.focus_handle(cx);
24956 move |window, cx| {
24957 Tooltip::for_action_in(
24958 "Previous Hunk",
24959 &GoToPreviousHunk,
24960 &focus_handle,
24961 window,
24962 cx,
24963 )
24964 }
24965 })
24966 .on_click({
24967 let editor = editor.clone();
24968 move |_event, window, cx| {
24969 editor.update(cx, |editor, cx| {
24970 let snapshot = editor.snapshot(window, cx);
24971 let point =
24972 hunk_range.start.to_point(&snapshot.buffer_snapshot());
24973 editor.go_to_hunk_before_or_after_position(
24974 &snapshot,
24975 point,
24976 Direction::Prev,
24977 window,
24978 cx,
24979 );
24980 editor.expand_selected_diff_hunks(cx);
24981 });
24982 }
24983 }),
24984 )
24985 },
24986 )
24987 .into_any_element()
24988}
24989
24990pub fn multibuffer_context_lines(cx: &App) -> u32 {
24991 EditorSettings::try_get(cx)
24992 .map(|settings| settings.excerpt_context_lines)
24993 .unwrap_or(2)
24994 .min(32)
24995}