1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod element;
22mod git;
23mod highlight_matching_bracket;
24mod hover_links;
25pub mod hover_popover;
26mod indent_guides;
27mod inlay_hint_cache;
28pub mod items;
29mod jsx_tag_auto_close;
30mod linked_editing_ranges;
31mod lsp_colors;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod edit_prediction_tests;
46#[cfg(test)]
47mod editor_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
54pub use edit_prediction::Direction;
55pub use editor_settings::{
56 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
57 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
58};
59pub use element::{
60 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
61};
62pub use git::blame::BlameRenderer;
63pub use hover_popover::hover_markdown_style;
64pub use items::MAX_TAB_TITLE_LEN;
65pub use lsp::CompletionContext;
66pub use lsp_ext::lsp_tasks;
67pub use multi_buffer::{
68 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
69 RowInfo, ToOffset, ToPoint,
70};
71pub use proposed_changes_editor::{
72 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
73};
74pub use text::Bias;
75
76use ::git::{
77 Restore,
78 blame::{BlameEntry, ParsedCommitMessage},
79};
80use aho_corasick::AhoCorasick;
81use anyhow::{Context as _, Result, anyhow};
82use blink_manager::BlinkManager;
83use buffer_diff::DiffHunkStatus;
84use client::{Collaborator, ParticipantIndex, parse_zed_link};
85use clock::{AGENT_REPLICA_ID, ReplicaId};
86use code_context_menus::{
87 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
88 CompletionsMenu, ContextMenuOrigin,
89};
90use collections::{BTreeMap, HashMap, HashSet, VecDeque};
91use convert_case::{Case, Casing};
92use dap::TelemetrySpawnLocation;
93use display_map::*;
94use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
95use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
96use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
97use futures::{
98 FutureExt, StreamExt as _,
99 future::{self, Shared, join},
100 stream::FuturesUnordered,
101};
102use fuzzy::{StringMatch, StringMatchCandidate};
103use git::blame::{GitBlame, GlobalBlameRenderer};
104use gpui::{
105 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
106 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
107 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
108 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
109 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
110 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
111 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
112 div, point, prelude::*, pulsating_between, px, relative, size,
113};
114use highlight_matching_bracket::refresh_matching_bracket_highlights;
115use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
116use hover_popover::{HoverState, hide_hover};
117use indent_guides::ActiveIndentGuidesState;
118use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
119use itertools::{Either, Itertools};
120use language::{
121 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
122 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
123 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
124 IndentSize, Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal,
125 TextObject, TransactionId, TreeSitterOptions, WordsQuery,
126 language_settings::{
127 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
128 all_language_settings, language_settings,
129 },
130 point_from_lsp, point_to_lsp, text_diff_with_options,
131};
132use linked_editing_ranges::refresh_linked_ranges;
133use lsp::{
134 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
135 LanguageServerId,
136};
137use lsp_colors::LspColorData;
138use markdown::Markdown;
139use mouse_context_menu::MouseContextMenu;
140use movement::TextLayoutDetails;
141use multi_buffer::{
142 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
143 ToOffsetUtf16,
144};
145use parking_lot::Mutex;
146use persistence::DB;
147use project::{
148 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
149 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint,
150 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectPath,
151 ProjectTransaction, TaskSourceKind,
152 debugger::{
153 breakpoint_store::{
154 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
155 BreakpointStore, BreakpointStoreEvent,
156 },
157 session::{Session, SessionEvent},
158 },
159 git_store::{GitStoreEvent, RepositoryEvent},
160 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
161 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
162};
163use rand::seq::SliceRandom;
164use rpc::{ErrorCode, ErrorExt, proto::PeerId};
165use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
166use selections_collection::{
167 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
168};
169use serde::{Deserialize, Serialize};
170use settings::{GitGutterSetting, Settings, SettingsLocation, SettingsStore, update_settings_file};
171use smallvec::{SmallVec, smallvec};
172use snippet::Snippet;
173use std::{
174 any::{Any, TypeId},
175 borrow::Cow,
176 cell::{OnceCell, RefCell},
177 cmp::{self, Ordering, Reverse},
178 iter::{self, Peekable},
179 mem,
180 num::NonZeroU32,
181 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
182 path::{Path, PathBuf},
183 rc::Rc,
184 sync::Arc,
185 time::{Duration, Instant},
186};
187use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
188use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
189use theme::{
190 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
191 observe_buffer_font_size_adjustment,
192};
193use ui::{
194 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
195 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
196};
197use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
198use workspace::{
199 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
200 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
201 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
202 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
203 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
204 searchable::SearchEvent,
205};
206
207use crate::{
208 code_context_menus::CompletionsMenuSource,
209 editor_settings::MultiCursorModifier,
210 hover_links::{find_url, find_url_from_range},
211 scroll::{ScrollOffset, ScrollPixelOffset},
212 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
213};
214
215pub const FILE_HEADER_HEIGHT: u32 = 2;
216pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
217const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
218const MAX_LINE_LEN: usize = 1024;
219const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
220const MAX_SELECTION_HISTORY_LEN: usize = 1024;
221pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
222#[doc(hidden)]
223pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
224pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
225
226pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
227pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
228pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
229pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
230
231pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
232pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
233pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
234
235pub type RenderDiffHunkControlsFn = Arc<
236 dyn Fn(
237 u32,
238 &DiffHunkStatus,
239 Range<Anchor>,
240 bool,
241 Pixels,
242 &Entity<Editor>,
243 &mut Window,
244 &mut App,
245 ) -> AnyElement,
246>;
247
248enum ReportEditorEvent {
249 Saved { auto_saved: bool },
250 EditorOpened,
251 Closed,
252}
253
254impl ReportEditorEvent {
255 pub fn event_type(&self) -> &'static str {
256 match self {
257 Self::Saved { .. } => "Editor Saved",
258 Self::EditorOpened => "Editor Opened",
259 Self::Closed => "Editor Closed",
260 }
261 }
262}
263
264struct InlineValueCache {
265 enabled: bool,
266 inlays: Vec<InlayId>,
267 refresh_task: Task<Option<()>>,
268}
269
270impl InlineValueCache {
271 fn new(enabled: bool) -> Self {
272 Self {
273 enabled,
274 inlays: Vec::new(),
275 refresh_task: Task::ready(None),
276 }
277 }
278}
279
280#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
281pub enum InlayId {
282 EditPrediction(u32),
283 DebuggerValue(u32),
284 // LSP
285 Hint(u32),
286 Color(u32),
287}
288
289impl InlayId {
290 fn id(&self) -> u32 {
291 match self {
292 Self::EditPrediction(id) => *id,
293 Self::DebuggerValue(id) => *id,
294 Self::Hint(id) => *id,
295 Self::Color(id) => *id,
296 }
297 }
298}
299
300pub enum ActiveDebugLine {}
301pub enum DebugStackFrameLine {}
302enum DocumentHighlightRead {}
303enum DocumentHighlightWrite {}
304enum InputComposition {}
305pub enum PendingInput {}
306enum SelectedTextHighlight {}
307
308pub enum ConflictsOuter {}
309pub enum ConflictsOurs {}
310pub enum ConflictsTheirs {}
311pub enum ConflictsOursMarker {}
312pub enum ConflictsTheirsMarker {}
313
314#[derive(Debug, Copy, Clone, PartialEq, Eq)]
315pub enum Navigated {
316 Yes,
317 No,
318}
319
320impl Navigated {
321 pub fn from_bool(yes: bool) -> Navigated {
322 if yes { Navigated::Yes } else { Navigated::No }
323 }
324}
325
326#[derive(Debug, Clone, PartialEq, Eq)]
327enum DisplayDiffHunk {
328 Folded {
329 display_row: DisplayRow,
330 },
331 Unfolded {
332 is_created_file: bool,
333 diff_base_byte_range: Range<usize>,
334 display_row_range: Range<DisplayRow>,
335 multi_buffer_range: Range<Anchor>,
336 status: DiffHunkStatus,
337 },
338}
339
340pub enum HideMouseCursorOrigin {
341 TypingAction,
342 MovementAction,
343}
344
345pub fn init_settings(cx: &mut App) {
346 EditorSettings::register(cx);
347}
348
349pub fn init(cx: &mut App) {
350 init_settings(cx);
351
352 cx.set_global(GlobalBlameRenderer(Arc::new(())));
353
354 workspace::register_project_item::<Editor>(cx);
355 workspace::FollowableViewRegistry::register::<Editor>(cx);
356 workspace::register_serializable_item::<Editor>(cx);
357
358 cx.observe_new(
359 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
360 workspace.register_action(Editor::new_file);
361 workspace.register_action(Editor::new_file_vertical);
362 workspace.register_action(Editor::new_file_horizontal);
363 workspace.register_action(Editor::cancel_language_server_work);
364 workspace.register_action(Editor::toggle_focus);
365 },
366 )
367 .detach();
368
369 cx.on_action(move |_: &workspace::NewFile, cx| {
370 let app_state = workspace::AppState::global(cx);
371 if let Some(app_state) = app_state.upgrade() {
372 workspace::open_new(
373 Default::default(),
374 app_state,
375 cx,
376 |workspace, window, cx| {
377 Editor::new_file(workspace, &Default::default(), window, cx)
378 },
379 )
380 .detach();
381 }
382 });
383 cx.on_action(move |_: &workspace::NewWindow, cx| {
384 let app_state = workspace::AppState::global(cx);
385 if let Some(app_state) = app_state.upgrade() {
386 workspace::open_new(
387 Default::default(),
388 app_state,
389 cx,
390 |workspace, window, cx| {
391 cx.activate(true);
392 Editor::new_file(workspace, &Default::default(), window, cx)
393 },
394 )
395 .detach();
396 }
397 });
398}
399
400pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
401 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
402}
403
404pub trait DiagnosticRenderer {
405 fn render_group(
406 &self,
407 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
408 buffer_id: BufferId,
409 snapshot: EditorSnapshot,
410 editor: WeakEntity<Editor>,
411 cx: &mut App,
412 ) -> Vec<BlockProperties<Anchor>>;
413
414 fn render_hover(
415 &self,
416 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
417 range: Range<Point>,
418 buffer_id: BufferId,
419 cx: &mut App,
420 ) -> Option<Entity<markdown::Markdown>>;
421
422 fn open_link(
423 &self,
424 editor: &mut Editor,
425 link: SharedString,
426 window: &mut Window,
427 cx: &mut Context<Editor>,
428 );
429}
430
431pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
432
433impl GlobalDiagnosticRenderer {
434 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
435 cx.try_global::<Self>().map(|g| g.0.clone())
436 }
437}
438
439impl gpui::Global for GlobalDiagnosticRenderer {}
440pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
441 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
442}
443
444pub struct SearchWithinRange;
445
446trait InvalidationRegion {
447 fn ranges(&self) -> &[Range<Anchor>];
448}
449
450#[derive(Clone, Debug, PartialEq)]
451pub enum SelectPhase {
452 Begin {
453 position: DisplayPoint,
454 add: bool,
455 click_count: usize,
456 },
457 BeginColumnar {
458 position: DisplayPoint,
459 reset: bool,
460 mode: ColumnarMode,
461 goal_column: u32,
462 },
463 Extend {
464 position: DisplayPoint,
465 click_count: usize,
466 },
467 Update {
468 position: DisplayPoint,
469 goal_column: u32,
470 scroll_delta: gpui::Point<f32>,
471 },
472 End,
473}
474
475#[derive(Clone, Debug, PartialEq)]
476pub enum ColumnarMode {
477 FromMouse,
478 FromSelection,
479}
480
481#[derive(Clone, Debug)]
482pub enum SelectMode {
483 Character,
484 Word(Range<Anchor>),
485 Line(Range<Anchor>),
486 All,
487}
488
489#[derive(Clone, PartialEq, Eq, Debug)]
490pub enum EditorMode {
491 SingleLine,
492 AutoHeight {
493 min_lines: usize,
494 max_lines: Option<usize>,
495 },
496 Full {
497 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
498 scale_ui_elements_with_buffer_font_size: bool,
499 /// When set to `true`, the editor will render a background for the active line.
500 show_active_line_background: bool,
501 /// When set to `true`, the editor's height will be determined by its content.
502 sized_by_content: bool,
503 },
504 Minimap {
505 parent: WeakEntity<Editor>,
506 },
507}
508
509impl EditorMode {
510 pub fn full() -> Self {
511 Self::Full {
512 scale_ui_elements_with_buffer_font_size: true,
513 show_active_line_background: true,
514 sized_by_content: false,
515 }
516 }
517
518 #[inline]
519 pub fn is_full(&self) -> bool {
520 matches!(self, Self::Full { .. })
521 }
522
523 #[inline]
524 pub fn is_single_line(&self) -> bool {
525 matches!(self, Self::SingleLine { .. })
526 }
527
528 #[inline]
529 fn is_minimap(&self) -> bool {
530 matches!(self, Self::Minimap { .. })
531 }
532}
533
534#[derive(Copy, Clone, Debug)]
535pub enum SoftWrap {
536 /// Prefer not to wrap at all.
537 ///
538 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
539 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
540 GitDiff,
541 /// Prefer a single line generally, unless an overly long line is encountered.
542 None,
543 /// Soft wrap lines that exceed the editor width.
544 EditorWidth,
545 /// Soft wrap lines at the preferred line length.
546 Column(u32),
547 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
548 Bounded(u32),
549}
550
551#[derive(Clone)]
552pub struct EditorStyle {
553 pub background: Hsla,
554 pub border: Hsla,
555 pub local_player: PlayerColor,
556 pub text: TextStyle,
557 pub scrollbar_width: Pixels,
558 pub syntax: Arc<SyntaxTheme>,
559 pub status: StatusColors,
560 pub inlay_hints_style: HighlightStyle,
561 pub edit_prediction_styles: EditPredictionStyles,
562 pub unnecessary_code_fade: f32,
563 pub show_underlines: bool,
564}
565
566impl Default for EditorStyle {
567 fn default() -> Self {
568 Self {
569 background: Hsla::default(),
570 border: Hsla::default(),
571 local_player: PlayerColor::default(),
572 text: TextStyle::default(),
573 scrollbar_width: Pixels::default(),
574 syntax: Default::default(),
575 // HACK: Status colors don't have a real default.
576 // We should look into removing the status colors from the editor
577 // style and retrieve them directly from the theme.
578 status: StatusColors::dark(),
579 inlay_hints_style: HighlightStyle::default(),
580 edit_prediction_styles: EditPredictionStyles {
581 insertion: HighlightStyle::default(),
582 whitespace: HighlightStyle::default(),
583 },
584 unnecessary_code_fade: Default::default(),
585 show_underlines: true,
586 }
587 }
588}
589
590pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
591 let show_background = language_settings::language_settings(None, None, cx)
592 .inlay_hints
593 .show_background;
594
595 let mut style = cx.theme().syntax().get("hint");
596
597 if style.color.is_none() {
598 style.color = Some(cx.theme().status().hint);
599 }
600
601 if !show_background {
602 style.background_color = None;
603 return style;
604 }
605
606 if style.background_color.is_none() {
607 style.background_color = Some(cx.theme().status().hint_background);
608 }
609
610 style
611}
612
613pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
614 EditPredictionStyles {
615 insertion: HighlightStyle {
616 color: Some(cx.theme().status().predictive),
617 ..HighlightStyle::default()
618 },
619 whitespace: HighlightStyle {
620 background_color: Some(cx.theme().status().created_background),
621 ..HighlightStyle::default()
622 },
623 }
624}
625
626type CompletionId = usize;
627
628pub(crate) enum EditDisplayMode {
629 TabAccept,
630 DiffPopover,
631 Inline,
632}
633
634enum EditPrediction {
635 Edit {
636 edits: Vec<(Range<Anchor>, String)>,
637 edit_preview: Option<EditPreview>,
638 display_mode: EditDisplayMode,
639 snapshot: BufferSnapshot,
640 },
641 /// Move to a specific location in the active editor
642 MoveWithin {
643 target: Anchor,
644 snapshot: BufferSnapshot,
645 },
646 /// Move to a specific location in a different editor (not the active one)
647 MoveOutside {
648 target: language::Anchor,
649 snapshot: BufferSnapshot,
650 },
651}
652
653struct EditPredictionState {
654 inlay_ids: Vec<InlayId>,
655 completion: EditPrediction,
656 completion_id: Option<SharedString>,
657 invalidation_range: Option<Range<Anchor>>,
658}
659
660enum EditPredictionSettings {
661 Disabled,
662 Enabled {
663 show_in_menu: bool,
664 preview_requires_modifier: bool,
665 },
666}
667
668enum EditPredictionHighlight {}
669
670#[derive(Debug, Clone)]
671struct InlineDiagnostic {
672 message: SharedString,
673 group_id: usize,
674 is_primary: bool,
675 start: Point,
676 severity: lsp::DiagnosticSeverity,
677}
678
679pub enum MenuEditPredictionsPolicy {
680 Never,
681 ByProvider,
682}
683
684pub enum EditPredictionPreview {
685 /// Modifier is not pressed
686 Inactive { released_too_fast: bool },
687 /// Modifier pressed
688 Active {
689 since: Instant,
690 previous_scroll_position: Option<ScrollAnchor>,
691 },
692}
693
694impl EditPredictionPreview {
695 pub fn released_too_fast(&self) -> bool {
696 match self {
697 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
698 EditPredictionPreview::Active { .. } => false,
699 }
700 }
701
702 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
703 if let EditPredictionPreview::Active {
704 previous_scroll_position,
705 ..
706 } = self
707 {
708 *previous_scroll_position = scroll_position;
709 }
710 }
711}
712
713pub struct ContextMenuOptions {
714 pub min_entries_visible: usize,
715 pub max_entries_visible: usize,
716 pub placement: Option<ContextMenuPlacement>,
717}
718
719#[derive(Debug, Clone, PartialEq, Eq)]
720pub enum ContextMenuPlacement {
721 Above,
722 Below,
723}
724
725#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
726struct EditorActionId(usize);
727
728impl EditorActionId {
729 pub fn post_inc(&mut self) -> Self {
730 let answer = self.0;
731
732 *self = Self(answer + 1);
733
734 Self(answer)
735 }
736}
737
738// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
739// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
740
741type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
742type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
743
744#[derive(Default)]
745struct ScrollbarMarkerState {
746 scrollbar_size: Size<Pixels>,
747 dirty: bool,
748 markers: Arc<[PaintQuad]>,
749 pending_refresh: Option<Task<Result<()>>>,
750}
751
752impl ScrollbarMarkerState {
753 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
754 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
755 }
756}
757
758#[derive(Clone, Copy, PartialEq, Eq)]
759pub enum MinimapVisibility {
760 Disabled,
761 Enabled {
762 /// The configuration currently present in the users settings.
763 setting_configuration: bool,
764 /// Whether to override the currently set visibility from the users setting.
765 toggle_override: bool,
766 },
767}
768
769impl MinimapVisibility {
770 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
771 if mode.is_full() {
772 Self::Enabled {
773 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
774 toggle_override: false,
775 }
776 } else {
777 Self::Disabled
778 }
779 }
780
781 fn hidden(&self) -> Self {
782 match *self {
783 Self::Enabled {
784 setting_configuration,
785 ..
786 } => Self::Enabled {
787 setting_configuration,
788 toggle_override: setting_configuration,
789 },
790 Self::Disabled => Self::Disabled,
791 }
792 }
793
794 fn disabled(&self) -> bool {
795 matches!(*self, Self::Disabled)
796 }
797
798 fn settings_visibility(&self) -> bool {
799 match *self {
800 Self::Enabled {
801 setting_configuration,
802 ..
803 } => setting_configuration,
804 _ => false,
805 }
806 }
807
808 fn visible(&self) -> bool {
809 match *self {
810 Self::Enabled {
811 setting_configuration,
812 toggle_override,
813 } => setting_configuration ^ toggle_override,
814 _ => false,
815 }
816 }
817
818 fn toggle_visibility(&self) -> Self {
819 match *self {
820 Self::Enabled {
821 toggle_override,
822 setting_configuration,
823 } => Self::Enabled {
824 setting_configuration,
825 toggle_override: !toggle_override,
826 },
827 Self::Disabled => Self::Disabled,
828 }
829 }
830}
831
832#[derive(Clone, Debug)]
833struct RunnableTasks {
834 templates: Vec<(TaskSourceKind, TaskTemplate)>,
835 offset: multi_buffer::Anchor,
836 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
837 column: u32,
838 // Values of all named captures, including those starting with '_'
839 extra_variables: HashMap<String, String>,
840 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
841 context_range: Range<BufferOffset>,
842}
843
844impl RunnableTasks {
845 fn resolve<'a>(
846 &'a self,
847 cx: &'a task::TaskContext,
848 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
849 self.templates.iter().filter_map(|(kind, template)| {
850 template
851 .resolve_task(&kind.to_id_base(), cx)
852 .map(|task| (kind.clone(), task))
853 })
854 }
855}
856
857#[derive(Clone)]
858pub struct ResolvedTasks {
859 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
860 position: Anchor,
861}
862
863#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
864struct BufferOffset(usize);
865
866/// Addons allow storing per-editor state in other crates (e.g. Vim)
867pub trait Addon: 'static {
868 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
869
870 fn render_buffer_header_controls(
871 &self,
872 _: &ExcerptInfo,
873 _: &Window,
874 _: &App,
875 ) -> Option<AnyElement> {
876 None
877 }
878
879 fn to_any(&self) -> &dyn std::any::Any;
880
881 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
882 None
883 }
884}
885
886struct ChangeLocation {
887 current: Option<Vec<Anchor>>,
888 original: Vec<Anchor>,
889}
890impl ChangeLocation {
891 fn locations(&self) -> &[Anchor] {
892 self.current.as_ref().unwrap_or(&self.original)
893 }
894}
895
896/// A set of caret positions, registered when the editor was edited.
897pub struct ChangeList {
898 changes: Vec<ChangeLocation>,
899 /// Currently "selected" change.
900 position: Option<usize>,
901}
902
903impl ChangeList {
904 pub fn new() -> Self {
905 Self {
906 changes: Vec::new(),
907 position: None,
908 }
909 }
910
911 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
912 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
913 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
914 if self.changes.is_empty() {
915 return None;
916 }
917
918 let prev = self.position.unwrap_or(self.changes.len());
919 let next = if direction == Direction::Prev {
920 prev.saturating_sub(count)
921 } else {
922 (prev + count).min(self.changes.len() - 1)
923 };
924 self.position = Some(next);
925 self.changes.get(next).map(|change| change.locations())
926 }
927
928 /// Adds a new change to the list, resetting the change list position.
929 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
930 self.position.take();
931 if let Some(last) = self.changes.last_mut()
932 && group
933 {
934 last.current = Some(new_positions)
935 } else {
936 self.changes.push(ChangeLocation {
937 original: new_positions,
938 current: None,
939 });
940 }
941 }
942
943 pub fn last(&self) -> Option<&[Anchor]> {
944 self.changes.last().map(|change| change.locations())
945 }
946
947 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
948 self.changes.last().map(|change| change.original.as_slice())
949 }
950
951 pub fn invert_last_group(&mut self) {
952 if let Some(last) = self.changes.last_mut()
953 && let Some(current) = last.current.as_mut()
954 {
955 mem::swap(&mut last.original, current);
956 }
957 }
958}
959
960#[derive(Clone)]
961struct InlineBlamePopoverState {
962 scroll_handle: ScrollHandle,
963 commit_message: Option<ParsedCommitMessage>,
964 markdown: Entity<Markdown>,
965}
966
967struct InlineBlamePopover {
968 position: gpui::Point<Pixels>,
969 hide_task: Option<Task<()>>,
970 popover_bounds: Option<Bounds<Pixels>>,
971 popover_state: InlineBlamePopoverState,
972 keyboard_grace: bool,
973}
974
975enum SelectionDragState {
976 /// State when no drag related activity is detected.
977 None,
978 /// State when the mouse is down on a selection that is about to be dragged.
979 ReadyToDrag {
980 selection: Selection<Anchor>,
981 click_position: gpui::Point<Pixels>,
982 mouse_down_time: Instant,
983 },
984 /// State when the mouse is dragging the selection in the editor.
985 Dragging {
986 selection: Selection<Anchor>,
987 drop_cursor: Selection<Anchor>,
988 hide_drop_cursor: bool,
989 },
990}
991
992enum ColumnarSelectionState {
993 FromMouse {
994 selection_tail: Anchor,
995 display_point: Option<DisplayPoint>,
996 },
997 FromSelection {
998 selection_tail: Anchor,
999 },
1000}
1001
1002/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1003/// a breakpoint on them.
1004#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1005struct PhantomBreakpointIndicator {
1006 display_row: DisplayRow,
1007 /// There's a small debounce between hovering over the line and showing the indicator.
1008 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1009 is_active: bool,
1010 collides_with_existing_breakpoint: bool,
1011}
1012
1013/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1014///
1015/// See the [module level documentation](self) for more information.
1016pub struct Editor {
1017 focus_handle: FocusHandle,
1018 last_focused_descendant: Option<WeakFocusHandle>,
1019 /// The text buffer being edited
1020 buffer: Entity<MultiBuffer>,
1021 /// Map of how text in the buffer should be displayed.
1022 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1023 pub display_map: Entity<DisplayMap>,
1024 placeholder_display_map: Option<Entity<DisplayMap>>,
1025 pub selections: SelectionsCollection,
1026 pub scroll_manager: ScrollManager,
1027 /// When inline assist editors are linked, they all render cursors because
1028 /// typing enters text into each of them, even the ones that aren't focused.
1029 pub(crate) show_cursor_when_unfocused: bool,
1030 columnar_selection_state: Option<ColumnarSelectionState>,
1031 add_selections_state: Option<AddSelectionsState>,
1032 select_next_state: Option<SelectNextState>,
1033 select_prev_state: Option<SelectNextState>,
1034 selection_history: SelectionHistory,
1035 defer_selection_effects: bool,
1036 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1037 autoclose_regions: Vec<AutocloseRegion>,
1038 snippet_stack: InvalidationStack<SnippetState>,
1039 select_syntax_node_history: SelectSyntaxNodeHistory,
1040 ime_transaction: Option<TransactionId>,
1041 pub diagnostics_max_severity: DiagnosticSeverity,
1042 active_diagnostics: ActiveDiagnostic,
1043 show_inline_diagnostics: bool,
1044 inline_diagnostics_update: Task<()>,
1045 inline_diagnostics_enabled: bool,
1046 diagnostics_enabled: bool,
1047 word_completions_enabled: bool,
1048 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1049 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1050 hard_wrap: Option<usize>,
1051 project: Option<Entity<Project>>,
1052 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1053 completion_provider: Option<Rc<dyn CompletionProvider>>,
1054 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1055 blink_manager: Entity<BlinkManager>,
1056 show_cursor_names: bool,
1057 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1058 pub show_local_selections: bool,
1059 mode: EditorMode,
1060 show_breadcrumbs: bool,
1061 show_gutter: bool,
1062 show_scrollbars: ScrollbarAxes,
1063 minimap_visibility: MinimapVisibility,
1064 offset_content: bool,
1065 disable_expand_excerpt_buttons: bool,
1066 show_line_numbers: Option<bool>,
1067 use_relative_line_numbers: Option<bool>,
1068 show_git_diff_gutter: Option<bool>,
1069 show_code_actions: Option<bool>,
1070 show_runnables: Option<bool>,
1071 show_breakpoints: Option<bool>,
1072 show_wrap_guides: Option<bool>,
1073 show_indent_guides: Option<bool>,
1074 highlight_order: usize,
1075 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1076 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1077 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1078 scrollbar_marker_state: ScrollbarMarkerState,
1079 active_indent_guides_state: ActiveIndentGuidesState,
1080 nav_history: Option<ItemNavHistory>,
1081 context_menu: RefCell<Option<CodeContextMenu>>,
1082 context_menu_options: Option<ContextMenuOptions>,
1083 mouse_context_menu: Option<MouseContextMenu>,
1084 completion_tasks: Vec<(CompletionId, Task<()>)>,
1085 inline_blame_popover: Option<InlineBlamePopover>,
1086 inline_blame_popover_show_task: Option<Task<()>>,
1087 signature_help_state: SignatureHelpState,
1088 auto_signature_help: Option<bool>,
1089 find_all_references_task_sources: Vec<Anchor>,
1090 next_completion_id: CompletionId,
1091 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1092 code_actions_task: Option<Task<Result<()>>>,
1093 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1094 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1095 document_highlights_task: Option<Task<()>>,
1096 linked_editing_range_task: Option<Task<Option<()>>>,
1097 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1098 pending_rename: Option<RenameState>,
1099 searchable: bool,
1100 cursor_shape: CursorShape,
1101 current_line_highlight: Option<CurrentLineHighlight>,
1102 collapse_matches: bool,
1103 autoindent_mode: Option<AutoindentMode>,
1104 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1105 input_enabled: bool,
1106 use_modal_editing: bool,
1107 read_only: bool,
1108 leader_id: Option<CollaboratorId>,
1109 remote_id: Option<ViewId>,
1110 pub hover_state: HoverState,
1111 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1112 gutter_hovered: bool,
1113 hovered_link_state: Option<HoveredLinkState>,
1114 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1115 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1116 active_edit_prediction: Option<EditPredictionState>,
1117 /// Used to prevent flickering as the user types while the menu is open
1118 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1119 edit_prediction_settings: EditPredictionSettings,
1120 edit_predictions_hidden_for_vim_mode: bool,
1121 show_edit_predictions_override: Option<bool>,
1122 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1123 edit_prediction_preview: EditPredictionPreview,
1124 edit_prediction_indent_conflict: bool,
1125 edit_prediction_requires_modifier_in_indent_conflict: bool,
1126 inlay_hint_cache: InlayHintCache,
1127 next_inlay_id: u32,
1128 next_color_inlay_id: u32,
1129 _subscriptions: Vec<Subscription>,
1130 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1131 gutter_dimensions: GutterDimensions,
1132 style: Option<EditorStyle>,
1133 text_style_refinement: Option<TextStyleRefinement>,
1134 next_editor_action_id: EditorActionId,
1135 editor_actions: Rc<
1136 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1137 >,
1138 use_autoclose: bool,
1139 use_auto_surround: bool,
1140 auto_replace_emoji_shortcode: bool,
1141 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1142 show_git_blame_gutter: bool,
1143 show_git_blame_inline: bool,
1144 show_git_blame_inline_delay_task: Option<Task<()>>,
1145 git_blame_inline_enabled: bool,
1146 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1147 serialize_dirty_buffers: bool,
1148 show_selection_menu: Option<bool>,
1149 blame: Option<Entity<GitBlame>>,
1150 blame_subscription: Option<Subscription>,
1151 custom_context_menu: Option<
1152 Box<
1153 dyn 'static
1154 + Fn(
1155 &mut Self,
1156 DisplayPoint,
1157 &mut Window,
1158 &mut Context<Self>,
1159 ) -> Option<Entity<ui::ContextMenu>>,
1160 >,
1161 >,
1162 last_bounds: Option<Bounds<Pixels>>,
1163 last_position_map: Option<Rc<PositionMap>>,
1164 expect_bounds_change: Option<Bounds<Pixels>>,
1165 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1166 tasks_update_task: Option<Task<()>>,
1167 breakpoint_store: Option<Entity<BreakpointStore>>,
1168 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1169 hovered_diff_hunk_row: Option<DisplayRow>,
1170 pull_diagnostics_task: Task<()>,
1171 in_project_search: bool,
1172 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1173 breadcrumb_header: Option<String>,
1174 focused_block: Option<FocusedBlock>,
1175 next_scroll_position: NextScrollCursorCenterTopBottom,
1176 addons: HashMap<TypeId, Box<dyn Addon>>,
1177 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1178 load_diff_task: Option<Shared<Task<()>>>,
1179 /// Whether we are temporarily displaying a diff other than git's
1180 temporary_diff_override: bool,
1181 selection_mark_mode: bool,
1182 toggle_fold_multiple_buffers: Task<()>,
1183 _scroll_cursor_center_top_bottom_task: Task<()>,
1184 serialize_selections: Task<()>,
1185 serialize_folds: Task<()>,
1186 mouse_cursor_hidden: bool,
1187 minimap: Option<Entity<Self>>,
1188 hide_mouse_mode: HideMouseMode,
1189 pub change_list: ChangeList,
1190 inline_value_cache: InlineValueCache,
1191 selection_drag_state: SelectionDragState,
1192 colors: Option<LspColorData>,
1193 refresh_colors_task: Task<()>,
1194 folding_newlines: Task<()>,
1195 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1196}
1197
1198#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1199enum NextScrollCursorCenterTopBottom {
1200 #[default]
1201 Center,
1202 Top,
1203 Bottom,
1204}
1205
1206impl NextScrollCursorCenterTopBottom {
1207 fn next(&self) -> Self {
1208 match self {
1209 Self::Center => Self::Top,
1210 Self::Top => Self::Bottom,
1211 Self::Bottom => Self::Center,
1212 }
1213 }
1214}
1215
1216#[derive(Clone)]
1217pub struct EditorSnapshot {
1218 pub mode: EditorMode,
1219 show_gutter: bool,
1220 show_line_numbers: Option<bool>,
1221 show_git_diff_gutter: Option<bool>,
1222 show_code_actions: Option<bool>,
1223 show_runnables: Option<bool>,
1224 show_breakpoints: Option<bool>,
1225 git_blame_gutter_max_author_length: Option<usize>,
1226 pub display_snapshot: DisplaySnapshot,
1227 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1228 is_focused: bool,
1229 scroll_anchor: ScrollAnchor,
1230 ongoing_scroll: OngoingScroll,
1231 current_line_highlight: CurrentLineHighlight,
1232 gutter_hovered: bool,
1233}
1234
1235#[derive(Default, Debug, Clone, Copy)]
1236pub struct GutterDimensions {
1237 pub left_padding: Pixels,
1238 pub right_padding: Pixels,
1239 pub width: Pixels,
1240 pub margin: Pixels,
1241 pub git_blame_entries_width: Option<Pixels>,
1242}
1243
1244impl GutterDimensions {
1245 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1246 Self {
1247 margin: Self::default_gutter_margin(font_id, font_size, cx),
1248 ..Default::default()
1249 }
1250 }
1251
1252 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1253 -cx.text_system().descent(font_id, font_size)
1254 }
1255 /// The full width of the space taken up by the gutter.
1256 pub fn full_width(&self) -> Pixels {
1257 self.margin + self.width
1258 }
1259
1260 /// The width of the space reserved for the fold indicators,
1261 /// use alongside 'justify_end' and `gutter_width` to
1262 /// right align content with the line numbers
1263 pub fn fold_area_width(&self) -> Pixels {
1264 self.margin + self.right_padding
1265 }
1266}
1267
1268struct CharacterDimensions {
1269 em_width: Pixels,
1270 em_advance: Pixels,
1271 line_height: Pixels,
1272}
1273
1274#[derive(Debug)]
1275pub struct RemoteSelection {
1276 pub replica_id: ReplicaId,
1277 pub selection: Selection<Anchor>,
1278 pub cursor_shape: CursorShape,
1279 pub collaborator_id: CollaboratorId,
1280 pub line_mode: bool,
1281 pub user_name: Option<SharedString>,
1282 pub color: PlayerColor,
1283}
1284
1285#[derive(Clone, Debug)]
1286struct SelectionHistoryEntry {
1287 selections: Arc<[Selection<Anchor>]>,
1288 select_next_state: Option<SelectNextState>,
1289 select_prev_state: Option<SelectNextState>,
1290 add_selections_state: Option<AddSelectionsState>,
1291}
1292
1293#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1294enum SelectionHistoryMode {
1295 Normal,
1296 Undoing,
1297 Redoing,
1298 Skipping,
1299}
1300
1301#[derive(Clone, PartialEq, Eq, Hash)]
1302struct HoveredCursor {
1303 replica_id: u16,
1304 selection_id: usize,
1305}
1306
1307impl Default for SelectionHistoryMode {
1308 fn default() -> Self {
1309 Self::Normal
1310 }
1311}
1312
1313#[derive(Debug)]
1314/// SelectionEffects controls the side-effects of updating the selection.
1315///
1316/// The default behaviour does "what you mostly want":
1317/// - it pushes to the nav history if the cursor moved by >10 lines
1318/// - it re-triggers completion requests
1319/// - it scrolls to fit
1320///
1321/// You might want to modify these behaviours. For example when doing a "jump"
1322/// like go to definition, we always want to add to nav history; but when scrolling
1323/// in vim mode we never do.
1324///
1325/// Similarly, you might want to disable scrolling if you don't want the viewport to
1326/// move.
1327#[derive(Clone)]
1328pub struct SelectionEffects {
1329 nav_history: Option<bool>,
1330 completions: bool,
1331 scroll: Option<Autoscroll>,
1332}
1333
1334impl Default for SelectionEffects {
1335 fn default() -> Self {
1336 Self {
1337 nav_history: None,
1338 completions: true,
1339 scroll: Some(Autoscroll::fit()),
1340 }
1341 }
1342}
1343impl SelectionEffects {
1344 pub fn scroll(scroll: Autoscroll) -> Self {
1345 Self {
1346 scroll: Some(scroll),
1347 ..Default::default()
1348 }
1349 }
1350
1351 pub fn no_scroll() -> Self {
1352 Self {
1353 scroll: None,
1354 ..Default::default()
1355 }
1356 }
1357
1358 pub fn completions(self, completions: bool) -> Self {
1359 Self {
1360 completions,
1361 ..self
1362 }
1363 }
1364
1365 pub fn nav_history(self, nav_history: bool) -> Self {
1366 Self {
1367 nav_history: Some(nav_history),
1368 ..self
1369 }
1370 }
1371}
1372
1373struct DeferredSelectionEffectsState {
1374 changed: bool,
1375 effects: SelectionEffects,
1376 old_cursor_position: Anchor,
1377 history_entry: SelectionHistoryEntry,
1378}
1379
1380#[derive(Default)]
1381struct SelectionHistory {
1382 #[allow(clippy::type_complexity)]
1383 selections_by_transaction:
1384 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1385 mode: SelectionHistoryMode,
1386 undo_stack: VecDeque<SelectionHistoryEntry>,
1387 redo_stack: VecDeque<SelectionHistoryEntry>,
1388}
1389
1390impl SelectionHistory {
1391 #[track_caller]
1392 fn insert_transaction(
1393 &mut self,
1394 transaction_id: TransactionId,
1395 selections: Arc<[Selection<Anchor>]>,
1396 ) {
1397 if selections.is_empty() {
1398 log::error!(
1399 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1400 std::panic::Location::caller()
1401 );
1402 return;
1403 }
1404 self.selections_by_transaction
1405 .insert(transaction_id, (selections, None));
1406 }
1407
1408 #[allow(clippy::type_complexity)]
1409 fn transaction(
1410 &self,
1411 transaction_id: TransactionId,
1412 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1413 self.selections_by_transaction.get(&transaction_id)
1414 }
1415
1416 #[allow(clippy::type_complexity)]
1417 fn transaction_mut(
1418 &mut self,
1419 transaction_id: TransactionId,
1420 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1421 self.selections_by_transaction.get_mut(&transaction_id)
1422 }
1423
1424 fn push(&mut self, entry: SelectionHistoryEntry) {
1425 if !entry.selections.is_empty() {
1426 match self.mode {
1427 SelectionHistoryMode::Normal => {
1428 self.push_undo(entry);
1429 self.redo_stack.clear();
1430 }
1431 SelectionHistoryMode::Undoing => self.push_redo(entry),
1432 SelectionHistoryMode::Redoing => self.push_undo(entry),
1433 SelectionHistoryMode::Skipping => {}
1434 }
1435 }
1436 }
1437
1438 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1439 if self
1440 .undo_stack
1441 .back()
1442 .is_none_or(|e| e.selections != entry.selections)
1443 {
1444 self.undo_stack.push_back(entry);
1445 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1446 self.undo_stack.pop_front();
1447 }
1448 }
1449 }
1450
1451 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1452 if self
1453 .redo_stack
1454 .back()
1455 .is_none_or(|e| e.selections != entry.selections)
1456 {
1457 self.redo_stack.push_back(entry);
1458 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1459 self.redo_stack.pop_front();
1460 }
1461 }
1462 }
1463}
1464
1465#[derive(Clone, Copy)]
1466pub struct RowHighlightOptions {
1467 pub autoscroll: bool,
1468 pub include_gutter: bool,
1469}
1470
1471impl Default for RowHighlightOptions {
1472 fn default() -> Self {
1473 Self {
1474 autoscroll: Default::default(),
1475 include_gutter: true,
1476 }
1477 }
1478}
1479
1480struct RowHighlight {
1481 index: usize,
1482 range: Range<Anchor>,
1483 color: Hsla,
1484 options: RowHighlightOptions,
1485 type_id: TypeId,
1486}
1487
1488#[derive(Clone, Debug)]
1489struct AddSelectionsState {
1490 groups: Vec<AddSelectionsGroup>,
1491}
1492
1493#[derive(Clone, Debug)]
1494struct AddSelectionsGroup {
1495 above: bool,
1496 stack: Vec<usize>,
1497}
1498
1499#[derive(Clone)]
1500struct SelectNextState {
1501 query: AhoCorasick,
1502 wordwise: bool,
1503 done: bool,
1504}
1505
1506impl std::fmt::Debug for SelectNextState {
1507 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1508 f.debug_struct(std::any::type_name::<Self>())
1509 .field("wordwise", &self.wordwise)
1510 .field("done", &self.done)
1511 .finish()
1512 }
1513}
1514
1515#[derive(Debug)]
1516struct AutocloseRegion {
1517 selection_id: usize,
1518 range: Range<Anchor>,
1519 pair: BracketPair,
1520}
1521
1522#[derive(Debug)]
1523struct SnippetState {
1524 ranges: Vec<Vec<Range<Anchor>>>,
1525 active_index: usize,
1526 choices: Vec<Option<Vec<String>>>,
1527}
1528
1529#[doc(hidden)]
1530pub struct RenameState {
1531 pub range: Range<Anchor>,
1532 pub old_name: Arc<str>,
1533 pub editor: Entity<Editor>,
1534 block_id: CustomBlockId,
1535}
1536
1537struct InvalidationStack<T>(Vec<T>);
1538
1539struct RegisteredEditPredictionProvider {
1540 provider: Arc<dyn EditPredictionProviderHandle>,
1541 _subscription: Subscription,
1542}
1543
1544#[derive(Debug, PartialEq, Eq)]
1545pub struct ActiveDiagnosticGroup {
1546 pub active_range: Range<Anchor>,
1547 pub active_message: String,
1548 pub group_id: usize,
1549 pub blocks: HashSet<CustomBlockId>,
1550}
1551
1552#[derive(Debug, PartialEq, Eq)]
1553
1554pub(crate) enum ActiveDiagnostic {
1555 None,
1556 All,
1557 Group(ActiveDiagnosticGroup),
1558}
1559
1560#[derive(Serialize, Deserialize, Clone, Debug)]
1561pub struct ClipboardSelection {
1562 /// The number of bytes in this selection.
1563 pub len: usize,
1564 /// Whether this was a full-line selection.
1565 pub is_entire_line: bool,
1566 /// The indentation of the first line when this content was originally copied.
1567 pub first_line_indent: u32,
1568}
1569
1570// selections, scroll behavior, was newest selection reversed
1571type SelectSyntaxNodeHistoryState = (
1572 Box<[Selection<usize>]>,
1573 SelectSyntaxNodeScrollBehavior,
1574 bool,
1575);
1576
1577#[derive(Default)]
1578struct SelectSyntaxNodeHistory {
1579 stack: Vec<SelectSyntaxNodeHistoryState>,
1580 // disable temporarily to allow changing selections without losing the stack
1581 pub disable_clearing: bool,
1582}
1583
1584impl SelectSyntaxNodeHistory {
1585 pub fn try_clear(&mut self) {
1586 if !self.disable_clearing {
1587 self.stack.clear();
1588 }
1589 }
1590
1591 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1592 self.stack.push(selection);
1593 }
1594
1595 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1596 self.stack.pop()
1597 }
1598}
1599
1600enum SelectSyntaxNodeScrollBehavior {
1601 CursorTop,
1602 FitSelection,
1603 CursorBottom,
1604}
1605
1606#[derive(Debug)]
1607pub(crate) struct NavigationData {
1608 cursor_anchor: Anchor,
1609 cursor_position: Point,
1610 scroll_anchor: ScrollAnchor,
1611 scroll_top_row: u32,
1612}
1613
1614#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1615pub enum GotoDefinitionKind {
1616 Symbol,
1617 Declaration,
1618 Type,
1619 Implementation,
1620}
1621
1622#[derive(Debug, Clone)]
1623enum InlayHintRefreshReason {
1624 ModifiersChanged(bool),
1625 Toggle(bool),
1626 SettingsChange(InlayHintSettings),
1627 NewLinesShown,
1628 BufferEdited(HashSet<Arc<Language>>),
1629 RefreshRequested,
1630 ExcerptsRemoved(Vec<ExcerptId>),
1631}
1632
1633impl InlayHintRefreshReason {
1634 fn description(&self) -> &'static str {
1635 match self {
1636 Self::ModifiersChanged(_) => "modifiers changed",
1637 Self::Toggle(_) => "toggle",
1638 Self::SettingsChange(_) => "settings change",
1639 Self::NewLinesShown => "new lines shown",
1640 Self::BufferEdited(_) => "buffer edited",
1641 Self::RefreshRequested => "refresh requested",
1642 Self::ExcerptsRemoved(_) => "excerpts removed",
1643 }
1644 }
1645}
1646
1647pub enum FormatTarget {
1648 Buffers(HashSet<Entity<Buffer>>),
1649 Ranges(Vec<Range<MultiBufferPoint>>),
1650}
1651
1652pub(crate) struct FocusedBlock {
1653 id: BlockId,
1654 focus_handle: WeakFocusHandle,
1655}
1656
1657#[derive(Clone)]
1658enum JumpData {
1659 MultiBufferRow {
1660 row: MultiBufferRow,
1661 line_offset_from_top: u32,
1662 },
1663 MultiBufferPoint {
1664 excerpt_id: ExcerptId,
1665 position: Point,
1666 anchor: text::Anchor,
1667 line_offset_from_top: u32,
1668 },
1669}
1670
1671pub enum MultibufferSelectionMode {
1672 First,
1673 All,
1674}
1675
1676#[derive(Clone, Copy, Debug, Default)]
1677pub struct RewrapOptions {
1678 pub override_language_settings: bool,
1679 pub preserve_existing_whitespace: bool,
1680}
1681
1682impl Editor {
1683 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1684 let buffer = cx.new(|cx| Buffer::local("", cx));
1685 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1686 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1687 }
1688
1689 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1690 let buffer = cx.new(|cx| Buffer::local("", cx));
1691 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1692 Self::new(EditorMode::full(), buffer, None, window, cx)
1693 }
1694
1695 pub fn auto_height(
1696 min_lines: usize,
1697 max_lines: usize,
1698 window: &mut Window,
1699 cx: &mut Context<Self>,
1700 ) -> Self {
1701 let buffer = cx.new(|cx| Buffer::local("", cx));
1702 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1703 Self::new(
1704 EditorMode::AutoHeight {
1705 min_lines,
1706 max_lines: Some(max_lines),
1707 },
1708 buffer,
1709 None,
1710 window,
1711 cx,
1712 )
1713 }
1714
1715 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1716 /// The editor grows as tall as needed to fit its content.
1717 pub fn auto_height_unbounded(
1718 min_lines: usize,
1719 window: &mut Window,
1720 cx: &mut Context<Self>,
1721 ) -> Self {
1722 let buffer = cx.new(|cx| Buffer::local("", cx));
1723 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1724 Self::new(
1725 EditorMode::AutoHeight {
1726 min_lines,
1727 max_lines: None,
1728 },
1729 buffer,
1730 None,
1731 window,
1732 cx,
1733 )
1734 }
1735
1736 pub fn for_buffer(
1737 buffer: Entity<Buffer>,
1738 project: Option<Entity<Project>>,
1739 window: &mut Window,
1740 cx: &mut Context<Self>,
1741 ) -> Self {
1742 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1743 Self::new(EditorMode::full(), buffer, project, window, cx)
1744 }
1745
1746 pub fn for_multibuffer(
1747 buffer: Entity<MultiBuffer>,
1748 project: Option<Entity<Project>>,
1749 window: &mut Window,
1750 cx: &mut Context<Self>,
1751 ) -> Self {
1752 Self::new(EditorMode::full(), buffer, project, window, cx)
1753 }
1754
1755 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1756 let mut clone = Self::new(
1757 self.mode.clone(),
1758 self.buffer.clone(),
1759 self.project.clone(),
1760 window,
1761 cx,
1762 );
1763 self.display_map.update(cx, |display_map, cx| {
1764 let snapshot = display_map.snapshot(cx);
1765 clone.display_map.update(cx, |display_map, cx| {
1766 display_map.set_state(&snapshot, cx);
1767 });
1768 });
1769 clone.folds_did_change(cx);
1770 clone.selections.clone_state(&self.selections);
1771 clone.scroll_manager.clone_state(&self.scroll_manager);
1772 clone.searchable = self.searchable;
1773 clone.read_only = self.read_only;
1774 clone
1775 }
1776
1777 pub fn new(
1778 mode: EditorMode,
1779 buffer: Entity<MultiBuffer>,
1780 project: Option<Entity<Project>>,
1781 window: &mut Window,
1782 cx: &mut Context<Self>,
1783 ) -> Self {
1784 Editor::new_internal(mode, buffer, project, None, window, cx)
1785 }
1786
1787 fn new_internal(
1788 mode: EditorMode,
1789 buffer: Entity<MultiBuffer>,
1790 project: Option<Entity<Project>>,
1791 display_map: Option<Entity<DisplayMap>>,
1792 window: &mut Window,
1793 cx: &mut Context<Self>,
1794 ) -> Self {
1795 debug_assert!(
1796 display_map.is_none() || mode.is_minimap(),
1797 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1798 );
1799
1800 let full_mode = mode.is_full();
1801 let is_minimap = mode.is_minimap();
1802 let diagnostics_max_severity = if full_mode {
1803 EditorSettings::get_global(cx)
1804 .diagnostics_max_severity
1805 .unwrap_or(DiagnosticSeverity::Hint)
1806 } else {
1807 DiagnosticSeverity::Off
1808 };
1809 let style = window.text_style();
1810 let font_size = style.font_size.to_pixels(window.rem_size());
1811 let editor = cx.entity().downgrade();
1812 let fold_placeholder = FoldPlaceholder {
1813 constrain_width: false,
1814 render: Arc::new(move |fold_id, fold_range, cx| {
1815 let editor = editor.clone();
1816 div()
1817 .id(fold_id)
1818 .bg(cx.theme().colors().ghost_element_background)
1819 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1820 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1821 .rounded_xs()
1822 .size_full()
1823 .cursor_pointer()
1824 .child("⋯")
1825 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1826 .on_click(move |_, _window, cx| {
1827 editor
1828 .update(cx, |editor, cx| {
1829 editor.unfold_ranges(
1830 &[fold_range.start..fold_range.end],
1831 true,
1832 false,
1833 cx,
1834 );
1835 cx.stop_propagation();
1836 })
1837 .ok();
1838 })
1839 .into_any()
1840 }),
1841 merge_adjacent: true,
1842 ..FoldPlaceholder::default()
1843 };
1844 let display_map = display_map.unwrap_or_else(|| {
1845 cx.new(|cx| {
1846 DisplayMap::new(
1847 buffer.clone(),
1848 style.font(),
1849 font_size,
1850 None,
1851 FILE_HEADER_HEIGHT,
1852 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1853 fold_placeholder,
1854 diagnostics_max_severity,
1855 cx,
1856 )
1857 })
1858 });
1859
1860 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1861
1862 let blink_manager = cx.new(|cx| {
1863 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1864 if is_minimap {
1865 blink_manager.disable(cx);
1866 }
1867 blink_manager
1868 });
1869
1870 let soft_wrap_mode_override =
1871 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1872
1873 let mut project_subscriptions = Vec::new();
1874 if full_mode && let Some(project) = project.as_ref() {
1875 project_subscriptions.push(cx.subscribe_in(
1876 project,
1877 window,
1878 |editor, _, event, window, cx| match event {
1879 project::Event::RefreshCodeLens => {
1880 // we always query lens with actions, without storing them, always refreshing them
1881 }
1882 project::Event::RefreshInlayHints => {
1883 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1884 }
1885 project::Event::LanguageServerAdded(..)
1886 | project::Event::LanguageServerRemoved(..) => {
1887 if editor.tasks_update_task.is_none() {
1888 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1889 }
1890 }
1891 project::Event::SnippetEdit(id, snippet_edits) => {
1892 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1893 let focus_handle = editor.focus_handle(cx);
1894 if focus_handle.is_focused(window) {
1895 let snapshot = buffer.read(cx).snapshot();
1896 for (range, snippet) in snippet_edits {
1897 let editor_range =
1898 language::range_from_lsp(*range).to_offset(&snapshot);
1899 editor
1900 .insert_snippet(
1901 &[editor_range],
1902 snippet.clone(),
1903 window,
1904 cx,
1905 )
1906 .ok();
1907 }
1908 }
1909 }
1910 }
1911 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1912 if editor.buffer().read(cx).buffer(*buffer_id).is_some() {
1913 editor.update_lsp_data(false, Some(*buffer_id), window, cx);
1914 }
1915 }
1916
1917 project::Event::EntryRenamed(transaction) => {
1918 let Some(workspace) = editor.workspace() else {
1919 return;
1920 };
1921 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1922 else {
1923 return;
1924 };
1925 if active_editor.entity_id() == cx.entity_id() {
1926 let edited_buffers_already_open = {
1927 let other_editors: Vec<Entity<Editor>> = workspace
1928 .read(cx)
1929 .panes()
1930 .iter()
1931 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1932 .filter(|editor| editor.entity_id() != cx.entity_id())
1933 .collect();
1934
1935 transaction.0.keys().all(|buffer| {
1936 other_editors.iter().any(|editor| {
1937 let multi_buffer = editor.read(cx).buffer();
1938 multi_buffer.read(cx).is_singleton()
1939 && multi_buffer.read(cx).as_singleton().map_or(
1940 false,
1941 |singleton| {
1942 singleton.entity_id() == buffer.entity_id()
1943 },
1944 )
1945 })
1946 })
1947 };
1948
1949 if !edited_buffers_already_open {
1950 let workspace = workspace.downgrade();
1951 let transaction = transaction.clone();
1952 cx.defer_in(window, move |_, window, cx| {
1953 cx.spawn_in(window, async move |editor, cx| {
1954 Self::open_project_transaction(
1955 &editor,
1956 workspace,
1957 transaction,
1958 "Rename".to_string(),
1959 cx,
1960 )
1961 .await
1962 .ok()
1963 })
1964 .detach();
1965 });
1966 }
1967 }
1968 }
1969
1970 _ => {}
1971 },
1972 ));
1973 if let Some(task_inventory) = project
1974 .read(cx)
1975 .task_store()
1976 .read(cx)
1977 .task_inventory()
1978 .cloned()
1979 {
1980 project_subscriptions.push(cx.observe_in(
1981 &task_inventory,
1982 window,
1983 |editor, _, window, cx| {
1984 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1985 },
1986 ));
1987 };
1988
1989 project_subscriptions.push(cx.subscribe_in(
1990 &project.read(cx).breakpoint_store(),
1991 window,
1992 |editor, _, event, window, cx| match event {
1993 BreakpointStoreEvent::ClearDebugLines => {
1994 editor.clear_row_highlights::<ActiveDebugLine>();
1995 editor.refresh_inline_values(cx);
1996 }
1997 BreakpointStoreEvent::SetDebugLine => {
1998 if editor.go_to_active_debug_line(window, cx) {
1999 cx.stop_propagation();
2000 }
2001
2002 editor.refresh_inline_values(cx);
2003 }
2004 _ => {}
2005 },
2006 ));
2007 let git_store = project.read(cx).git_store().clone();
2008 let project = project.clone();
2009 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2010 if let GitStoreEvent::RepositoryUpdated(
2011 _,
2012 RepositoryEvent::Updated {
2013 new_instance: true, ..
2014 },
2015 _,
2016 ) = event
2017 {
2018 this.load_diff_task = Some(
2019 update_uncommitted_diff_for_buffer(
2020 cx.entity(),
2021 &project,
2022 this.buffer.read(cx).all_buffers(),
2023 this.buffer.clone(),
2024 cx,
2025 )
2026 .shared(),
2027 );
2028 }
2029 }));
2030 }
2031
2032 let buffer_snapshot = buffer.read(cx).snapshot(cx);
2033
2034 let inlay_hint_settings =
2035 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2036 let focus_handle = cx.focus_handle();
2037 if !is_minimap {
2038 cx.on_focus(&focus_handle, window, Self::handle_focus)
2039 .detach();
2040 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2041 .detach();
2042 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2043 .detach();
2044 cx.on_blur(&focus_handle, window, Self::handle_blur)
2045 .detach();
2046 cx.observe_pending_input(window, Self::observe_pending_input)
2047 .detach();
2048 }
2049
2050 let show_indent_guides =
2051 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2052 Some(false)
2053 } else {
2054 None
2055 };
2056
2057 let breakpoint_store = match (&mode, project.as_ref()) {
2058 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2059 _ => None,
2060 };
2061
2062 let mut code_action_providers = Vec::new();
2063 let mut load_uncommitted_diff = None;
2064 if let Some(project) = project.clone() {
2065 load_uncommitted_diff = Some(
2066 update_uncommitted_diff_for_buffer(
2067 cx.entity(),
2068 &project,
2069 buffer.read(cx).all_buffers(),
2070 buffer.clone(),
2071 cx,
2072 )
2073 .shared(),
2074 );
2075 code_action_providers.push(Rc::new(project) as Rc<_>);
2076 }
2077
2078 let mut editor = Self {
2079 focus_handle,
2080 show_cursor_when_unfocused: false,
2081 last_focused_descendant: None,
2082 buffer: buffer.clone(),
2083 display_map: display_map.clone(),
2084 placeholder_display_map: None,
2085 selections,
2086 scroll_manager: ScrollManager::new(cx),
2087 columnar_selection_state: None,
2088 add_selections_state: None,
2089 select_next_state: None,
2090 select_prev_state: None,
2091 selection_history: SelectionHistory::default(),
2092 defer_selection_effects: false,
2093 deferred_selection_effects_state: None,
2094 autoclose_regions: Vec::new(),
2095 snippet_stack: InvalidationStack::default(),
2096 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2097 ime_transaction: None,
2098 active_diagnostics: ActiveDiagnostic::None,
2099 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2100 inline_diagnostics_update: Task::ready(()),
2101 inline_diagnostics: Vec::new(),
2102 soft_wrap_mode_override,
2103 diagnostics_max_severity,
2104 hard_wrap: None,
2105 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2106 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2107 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2108 project,
2109 blink_manager: blink_manager.clone(),
2110 show_local_selections: true,
2111 show_scrollbars: ScrollbarAxes {
2112 horizontal: full_mode,
2113 vertical: full_mode,
2114 },
2115 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2116 offset_content: !matches!(mode, EditorMode::SingleLine),
2117 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2118 show_gutter: full_mode,
2119 show_line_numbers: (!full_mode).then_some(false),
2120 use_relative_line_numbers: None,
2121 disable_expand_excerpt_buttons: !full_mode,
2122 show_git_diff_gutter: None,
2123 show_code_actions: None,
2124 show_runnables: None,
2125 show_breakpoints: None,
2126 show_wrap_guides: None,
2127 show_indent_guides,
2128 highlight_order: 0,
2129 highlighted_rows: HashMap::default(),
2130 background_highlights: HashMap::default(),
2131 gutter_highlights: HashMap::default(),
2132 scrollbar_marker_state: ScrollbarMarkerState::default(),
2133 active_indent_guides_state: ActiveIndentGuidesState::default(),
2134 nav_history: None,
2135 context_menu: RefCell::new(None),
2136 context_menu_options: None,
2137 mouse_context_menu: None,
2138 completion_tasks: Vec::new(),
2139 inline_blame_popover: None,
2140 inline_blame_popover_show_task: None,
2141 signature_help_state: SignatureHelpState::default(),
2142 auto_signature_help: None,
2143 find_all_references_task_sources: Vec::new(),
2144 next_completion_id: 0,
2145 next_inlay_id: 0,
2146 code_action_providers,
2147 available_code_actions: None,
2148 code_actions_task: None,
2149 quick_selection_highlight_task: None,
2150 debounced_selection_highlight_task: None,
2151 document_highlights_task: None,
2152 linked_editing_range_task: None,
2153 pending_rename: None,
2154 searchable: !is_minimap,
2155 cursor_shape: EditorSettings::get_global(cx)
2156 .cursor_shape
2157 .unwrap_or_default(),
2158 current_line_highlight: None,
2159 autoindent_mode: Some(AutoindentMode::EachLine),
2160 collapse_matches: false,
2161 workspace: None,
2162 input_enabled: !is_minimap,
2163 use_modal_editing: full_mode,
2164 read_only: is_minimap,
2165 use_autoclose: true,
2166 use_auto_surround: true,
2167 auto_replace_emoji_shortcode: false,
2168 jsx_tag_auto_close_enabled_in_any_buffer: false,
2169 leader_id: None,
2170 remote_id: None,
2171 hover_state: HoverState::default(),
2172 pending_mouse_down: None,
2173 hovered_link_state: None,
2174 edit_prediction_provider: None,
2175 active_edit_prediction: None,
2176 stale_edit_prediction_in_menu: None,
2177 edit_prediction_preview: EditPredictionPreview::Inactive {
2178 released_too_fast: false,
2179 },
2180 inline_diagnostics_enabled: full_mode,
2181 diagnostics_enabled: full_mode,
2182 word_completions_enabled: full_mode,
2183 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2184 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2185 gutter_hovered: false,
2186 pixel_position_of_newest_cursor: None,
2187 last_bounds: None,
2188 last_position_map: None,
2189 expect_bounds_change: None,
2190 gutter_dimensions: GutterDimensions::default(),
2191 style: None,
2192 show_cursor_names: false,
2193 hovered_cursors: HashMap::default(),
2194 next_editor_action_id: EditorActionId::default(),
2195 editor_actions: Rc::default(),
2196 edit_predictions_hidden_for_vim_mode: false,
2197 show_edit_predictions_override: None,
2198 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2199 edit_prediction_settings: EditPredictionSettings::Disabled,
2200 edit_prediction_indent_conflict: false,
2201 edit_prediction_requires_modifier_in_indent_conflict: true,
2202 custom_context_menu: None,
2203 show_git_blame_gutter: false,
2204 show_git_blame_inline: false,
2205 show_selection_menu: None,
2206 show_git_blame_inline_delay_task: None,
2207 git_blame_inline_enabled: full_mode
2208 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2209 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2210 serialize_dirty_buffers: !is_minimap
2211 && ProjectSettings::get_global(cx)
2212 .session
2213 .restore_unsaved_buffers,
2214 blame: None,
2215 blame_subscription: None,
2216 tasks: BTreeMap::default(),
2217
2218 breakpoint_store,
2219 gutter_breakpoint_indicator: (None, None),
2220 hovered_diff_hunk_row: None,
2221 _subscriptions: (!is_minimap)
2222 .then(|| {
2223 vec![
2224 cx.observe(&buffer, Self::on_buffer_changed),
2225 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2226 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2227 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2228 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2229 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2230 cx.observe_window_activation(window, |editor, window, cx| {
2231 let active = window.is_window_active();
2232 editor.blink_manager.update(cx, |blink_manager, cx| {
2233 if active {
2234 blink_manager.enable(cx);
2235 } else {
2236 blink_manager.disable(cx);
2237 }
2238 });
2239 if active {
2240 editor.show_mouse_cursor(cx);
2241 }
2242 }),
2243 ]
2244 })
2245 .unwrap_or_default(),
2246 tasks_update_task: None,
2247 pull_diagnostics_task: Task::ready(()),
2248 colors: None,
2249 refresh_colors_task: Task::ready(()),
2250 next_color_inlay_id: 0,
2251 linked_edit_ranges: Default::default(),
2252 in_project_search: false,
2253 previous_search_ranges: None,
2254 breadcrumb_header: None,
2255 focused_block: None,
2256 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2257 addons: HashMap::default(),
2258 registered_buffers: HashMap::default(),
2259 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2260 selection_mark_mode: false,
2261 toggle_fold_multiple_buffers: Task::ready(()),
2262 serialize_selections: Task::ready(()),
2263 serialize_folds: Task::ready(()),
2264 text_style_refinement: None,
2265 load_diff_task: load_uncommitted_diff,
2266 temporary_diff_override: false,
2267 mouse_cursor_hidden: false,
2268 minimap: None,
2269 hide_mouse_mode: EditorSettings::get_global(cx)
2270 .hide_mouse
2271 .unwrap_or_default(),
2272 change_list: ChangeList::new(),
2273 mode,
2274 selection_drag_state: SelectionDragState::None,
2275 folding_newlines: Task::ready(()),
2276 lookup_key: None,
2277 };
2278
2279 if is_minimap {
2280 return editor;
2281 }
2282
2283 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2284 editor
2285 ._subscriptions
2286 .push(cx.observe(breakpoints, |_, _, cx| {
2287 cx.notify();
2288 }));
2289 }
2290 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2291 editor._subscriptions.extend(project_subscriptions);
2292
2293 editor._subscriptions.push(cx.subscribe_in(
2294 &cx.entity(),
2295 window,
2296 |editor, _, e: &EditorEvent, window, cx| match e {
2297 EditorEvent::ScrollPositionChanged { local, .. } => {
2298 if *local {
2299 let new_anchor = editor.scroll_manager.anchor();
2300 let snapshot = editor.snapshot(window, cx);
2301 editor.update_restoration_data(cx, move |data| {
2302 data.scroll_position = (
2303 new_anchor.top_row(snapshot.buffer_snapshot()),
2304 new_anchor.offset,
2305 );
2306 });
2307 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2308 editor.inline_blame_popover.take();
2309 }
2310 }
2311 EditorEvent::Edited { .. } => {
2312 if !vim_enabled(cx) {
2313 let (map, selections) = editor.selections.all_adjusted_display(cx);
2314 let pop_state = editor
2315 .change_list
2316 .last()
2317 .map(|previous| {
2318 previous.len() == selections.len()
2319 && previous.iter().enumerate().all(|(ix, p)| {
2320 p.to_display_point(&map).row()
2321 == selections[ix].head().row()
2322 })
2323 })
2324 .unwrap_or(false);
2325 let new_positions = selections
2326 .into_iter()
2327 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2328 .collect();
2329 editor
2330 .change_list
2331 .push_to_change_list(pop_state, new_positions);
2332 }
2333 }
2334 _ => (),
2335 },
2336 ));
2337
2338 if let Some(dap_store) = editor
2339 .project
2340 .as_ref()
2341 .map(|project| project.read(cx).dap_store())
2342 {
2343 let weak_editor = cx.weak_entity();
2344
2345 editor
2346 ._subscriptions
2347 .push(
2348 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2349 let session_entity = cx.entity();
2350 weak_editor
2351 .update(cx, |editor, cx| {
2352 editor._subscriptions.push(
2353 cx.subscribe(&session_entity, Self::on_debug_session_event),
2354 );
2355 })
2356 .ok();
2357 }),
2358 );
2359
2360 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2361 editor
2362 ._subscriptions
2363 .push(cx.subscribe(&session, Self::on_debug_session_event));
2364 }
2365 }
2366
2367 // skip adding the initial selection to selection history
2368 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2369 editor.end_selection(window, cx);
2370 editor.selection_history.mode = SelectionHistoryMode::Normal;
2371
2372 editor.scroll_manager.show_scrollbars(window, cx);
2373 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2374
2375 if full_mode {
2376 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2377 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2378
2379 if editor.git_blame_inline_enabled {
2380 editor.start_git_blame_inline(false, window, cx);
2381 }
2382
2383 editor.go_to_active_debug_line(window, cx);
2384
2385 if let Some(buffer) = buffer.read(cx).as_singleton()
2386 && let Some(project) = editor.project()
2387 {
2388 let handle = project.update(cx, |project, cx| {
2389 project.register_buffer_with_language_servers(&buffer, cx)
2390 });
2391 editor
2392 .registered_buffers
2393 .insert(buffer.read(cx).remote_id(), handle);
2394 }
2395
2396 editor.minimap =
2397 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2398 editor.colors = Some(LspColorData::new(cx));
2399 editor.update_lsp_data(false, None, window, cx);
2400 }
2401
2402 if editor.mode.is_full() {
2403 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2404 }
2405
2406 editor
2407 }
2408
2409 pub fn deploy_mouse_context_menu(
2410 &mut self,
2411 position: gpui::Point<Pixels>,
2412 context_menu: Entity<ContextMenu>,
2413 window: &mut Window,
2414 cx: &mut Context<Self>,
2415 ) {
2416 self.mouse_context_menu = Some(MouseContextMenu::new(
2417 self,
2418 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2419 context_menu,
2420 window,
2421 cx,
2422 ));
2423 }
2424
2425 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2426 self.mouse_context_menu
2427 .as_ref()
2428 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2429 }
2430
2431 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2432 if self
2433 .selections
2434 .pending_anchor()
2435 .is_some_and(|pending_selection| {
2436 let snapshot = self.buffer().read(cx).snapshot(cx);
2437 pending_selection.range().includes(range, &snapshot)
2438 })
2439 {
2440 return true;
2441 }
2442
2443 self.selections
2444 .disjoint_in_range::<usize>(range.clone(), cx)
2445 .into_iter()
2446 .any(|selection| {
2447 // This is needed to cover a corner case, if we just check for an existing
2448 // selection in the fold range, having a cursor at the start of the fold
2449 // marks it as selected. Non-empty selections don't cause this.
2450 let length = selection.end - selection.start;
2451 length > 0
2452 })
2453 }
2454
2455 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2456 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2457 }
2458
2459 fn key_context_internal(
2460 &self,
2461 has_active_edit_prediction: bool,
2462 window: &Window,
2463 cx: &App,
2464 ) -> KeyContext {
2465 let mut key_context = KeyContext::new_with_defaults();
2466 key_context.add("Editor");
2467 let mode = match self.mode {
2468 EditorMode::SingleLine => "single_line",
2469 EditorMode::AutoHeight { .. } => "auto_height",
2470 EditorMode::Minimap { .. } => "minimap",
2471 EditorMode::Full { .. } => "full",
2472 };
2473
2474 if EditorSettings::jupyter_enabled(cx) {
2475 key_context.add("jupyter");
2476 }
2477
2478 key_context.set("mode", mode);
2479 if self.pending_rename.is_some() {
2480 key_context.add("renaming");
2481 }
2482
2483 match self.context_menu.borrow().as_ref() {
2484 Some(CodeContextMenu::Completions(menu)) => {
2485 if menu.visible() {
2486 key_context.add("menu");
2487 key_context.add("showing_completions");
2488 }
2489 }
2490 Some(CodeContextMenu::CodeActions(menu)) => {
2491 if menu.visible() {
2492 key_context.add("menu");
2493 key_context.add("showing_code_actions")
2494 }
2495 }
2496 None => {}
2497 }
2498
2499 if self.signature_help_state.has_multiple_signatures() {
2500 key_context.add("showing_signature_help");
2501 }
2502
2503 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2504 if !self.focus_handle(cx).contains_focused(window, cx)
2505 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2506 {
2507 for addon in self.addons.values() {
2508 addon.extend_key_context(&mut key_context, cx)
2509 }
2510 }
2511
2512 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2513 if let Some(extension) = singleton_buffer
2514 .read(cx)
2515 .file()
2516 .and_then(|file| file.path().extension())
2517 {
2518 key_context.set("extension", extension.to_string());
2519 }
2520 } else {
2521 key_context.add("multibuffer");
2522 }
2523
2524 if has_active_edit_prediction {
2525 if self.edit_prediction_in_conflict() {
2526 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2527 } else {
2528 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2529 key_context.add("copilot_suggestion");
2530 }
2531 }
2532
2533 if self.selection_mark_mode {
2534 key_context.add("selection_mode");
2535 }
2536
2537 key_context
2538 }
2539
2540 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2541 self.last_bounds.as_ref()
2542 }
2543
2544 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2545 if self.mouse_cursor_hidden {
2546 self.mouse_cursor_hidden = false;
2547 cx.notify();
2548 }
2549 }
2550
2551 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2552 let hide_mouse_cursor = match origin {
2553 HideMouseCursorOrigin::TypingAction => {
2554 matches!(
2555 self.hide_mouse_mode,
2556 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2557 )
2558 }
2559 HideMouseCursorOrigin::MovementAction => {
2560 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2561 }
2562 };
2563 if self.mouse_cursor_hidden != hide_mouse_cursor {
2564 self.mouse_cursor_hidden = hide_mouse_cursor;
2565 cx.notify();
2566 }
2567 }
2568
2569 pub fn edit_prediction_in_conflict(&self) -> bool {
2570 if !self.show_edit_predictions_in_menu() {
2571 return false;
2572 }
2573
2574 let showing_completions = self
2575 .context_menu
2576 .borrow()
2577 .as_ref()
2578 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2579
2580 showing_completions
2581 || self.edit_prediction_requires_modifier()
2582 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2583 // bindings to insert tab characters.
2584 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2585 }
2586
2587 pub fn accept_edit_prediction_keybind(
2588 &self,
2589 accept_partial: bool,
2590 window: &Window,
2591 cx: &App,
2592 ) -> AcceptEditPredictionBinding {
2593 let key_context = self.key_context_internal(true, window, cx);
2594 let in_conflict = self.edit_prediction_in_conflict();
2595
2596 let bindings = if accept_partial {
2597 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2598 } else {
2599 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2600 };
2601
2602 // TODO: if the binding contains multiple keystrokes, display all of them, not
2603 // just the first one.
2604 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2605 !in_conflict
2606 || binding
2607 .keystrokes()
2608 .first()
2609 .is_some_and(|keystroke| keystroke.modifiers().modified())
2610 }))
2611 }
2612
2613 pub fn new_file(
2614 workspace: &mut Workspace,
2615 _: &workspace::NewFile,
2616 window: &mut Window,
2617 cx: &mut Context<Workspace>,
2618 ) {
2619 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2620 "Failed to create buffer",
2621 window,
2622 cx,
2623 |e, _, _| match e.error_code() {
2624 ErrorCode::RemoteUpgradeRequired => Some(format!(
2625 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2626 e.error_tag("required").unwrap_or("the latest version")
2627 )),
2628 _ => None,
2629 },
2630 );
2631 }
2632
2633 pub fn new_in_workspace(
2634 workspace: &mut Workspace,
2635 window: &mut Window,
2636 cx: &mut Context<Workspace>,
2637 ) -> Task<Result<Entity<Editor>>> {
2638 let project = workspace.project().clone();
2639 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2640
2641 cx.spawn_in(window, async move |workspace, cx| {
2642 let buffer = create.await?;
2643 workspace.update_in(cx, |workspace, window, cx| {
2644 let editor =
2645 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2646 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2647 editor
2648 })
2649 })
2650 }
2651
2652 fn new_file_vertical(
2653 workspace: &mut Workspace,
2654 _: &workspace::NewFileSplitVertical,
2655 window: &mut Window,
2656 cx: &mut Context<Workspace>,
2657 ) {
2658 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2659 }
2660
2661 fn new_file_horizontal(
2662 workspace: &mut Workspace,
2663 _: &workspace::NewFileSplitHorizontal,
2664 window: &mut Window,
2665 cx: &mut Context<Workspace>,
2666 ) {
2667 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2668 }
2669
2670 fn new_file_in_direction(
2671 workspace: &mut Workspace,
2672 direction: SplitDirection,
2673 window: &mut Window,
2674 cx: &mut Context<Workspace>,
2675 ) {
2676 let project = workspace.project().clone();
2677 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2678
2679 cx.spawn_in(window, async move |workspace, cx| {
2680 let buffer = create.await?;
2681 workspace.update_in(cx, move |workspace, window, cx| {
2682 workspace.split_item(
2683 direction,
2684 Box::new(
2685 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2686 ),
2687 window,
2688 cx,
2689 )
2690 })?;
2691 anyhow::Ok(())
2692 })
2693 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2694 match e.error_code() {
2695 ErrorCode::RemoteUpgradeRequired => Some(format!(
2696 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2697 e.error_tag("required").unwrap_or("the latest version")
2698 )),
2699 _ => None,
2700 }
2701 });
2702 }
2703
2704 pub fn leader_id(&self) -> Option<CollaboratorId> {
2705 self.leader_id
2706 }
2707
2708 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2709 &self.buffer
2710 }
2711
2712 pub fn project(&self) -> Option<&Entity<Project>> {
2713 self.project.as_ref()
2714 }
2715
2716 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2717 self.workspace.as_ref()?.0.upgrade()
2718 }
2719
2720 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2721 self.buffer().read(cx).title(cx)
2722 }
2723
2724 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2725 let git_blame_gutter_max_author_length = self
2726 .render_git_blame_gutter(cx)
2727 .then(|| {
2728 if let Some(blame) = self.blame.as_ref() {
2729 let max_author_length =
2730 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2731 Some(max_author_length)
2732 } else {
2733 None
2734 }
2735 })
2736 .flatten();
2737
2738 EditorSnapshot {
2739 mode: self.mode.clone(),
2740 show_gutter: self.show_gutter,
2741 show_line_numbers: self.show_line_numbers,
2742 show_git_diff_gutter: self.show_git_diff_gutter,
2743 show_code_actions: self.show_code_actions,
2744 show_runnables: self.show_runnables,
2745 show_breakpoints: self.show_breakpoints,
2746 git_blame_gutter_max_author_length,
2747 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2748 placeholder_display_snapshot: self
2749 .placeholder_display_map
2750 .as_ref()
2751 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2752 scroll_anchor: self.scroll_manager.anchor(),
2753 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2754 is_focused: self.focus_handle.is_focused(window),
2755 current_line_highlight: self
2756 .current_line_highlight
2757 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2758 gutter_hovered: self.gutter_hovered,
2759 }
2760 }
2761
2762 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2763 self.buffer.read(cx).language_at(point, cx)
2764 }
2765
2766 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2767 self.buffer.read(cx).read(cx).file_at(point).cloned()
2768 }
2769
2770 pub fn active_excerpt(
2771 &self,
2772 cx: &App,
2773 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2774 self.buffer
2775 .read(cx)
2776 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2777 }
2778
2779 pub fn mode(&self) -> &EditorMode {
2780 &self.mode
2781 }
2782
2783 pub fn set_mode(&mut self, mode: EditorMode) {
2784 self.mode = mode;
2785 }
2786
2787 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2788 self.collaboration_hub.as_deref()
2789 }
2790
2791 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2792 self.collaboration_hub = Some(hub);
2793 }
2794
2795 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2796 self.in_project_search = in_project_search;
2797 }
2798
2799 pub fn set_custom_context_menu(
2800 &mut self,
2801 f: impl 'static
2802 + Fn(
2803 &mut Self,
2804 DisplayPoint,
2805 &mut Window,
2806 &mut Context<Self>,
2807 ) -> Option<Entity<ui::ContextMenu>>,
2808 ) {
2809 self.custom_context_menu = Some(Box::new(f))
2810 }
2811
2812 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2813 self.completion_provider = provider;
2814 }
2815
2816 #[cfg(any(test, feature = "test-support"))]
2817 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2818 self.completion_provider.clone()
2819 }
2820
2821 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2822 self.semantics_provider.clone()
2823 }
2824
2825 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2826 self.semantics_provider = provider;
2827 }
2828
2829 pub fn set_edit_prediction_provider<T>(
2830 &mut self,
2831 provider: Option<Entity<T>>,
2832 window: &mut Window,
2833 cx: &mut Context<Self>,
2834 ) where
2835 T: EditPredictionProvider,
2836 {
2837 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2838 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2839 if this.focus_handle.is_focused(window) {
2840 this.update_visible_edit_prediction(window, cx);
2841 }
2842 }),
2843 provider: Arc::new(provider),
2844 });
2845 self.update_edit_prediction_settings(cx);
2846 self.refresh_edit_prediction(false, false, window, cx);
2847 }
2848
2849 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2850 self.placeholder_display_map
2851 .as_ref()
2852 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2853 }
2854
2855 pub fn set_placeholder_text(
2856 &mut self,
2857 placeholder_text: &str,
2858 window: &mut Window,
2859 cx: &mut Context<Self>,
2860 ) {
2861 let multibuffer = cx
2862 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2863
2864 let style = window.text_style();
2865
2866 self.placeholder_display_map = Some(cx.new(|cx| {
2867 DisplayMap::new(
2868 multibuffer,
2869 style.font(),
2870 style.font_size.to_pixels(window.rem_size()),
2871 None,
2872 FILE_HEADER_HEIGHT,
2873 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2874 Default::default(),
2875 DiagnosticSeverity::Off,
2876 cx,
2877 )
2878 }));
2879 cx.notify();
2880 }
2881
2882 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2883 self.cursor_shape = cursor_shape;
2884
2885 // Disrupt blink for immediate user feedback that the cursor shape has changed
2886 self.blink_manager.update(cx, BlinkManager::show_cursor);
2887
2888 cx.notify();
2889 }
2890
2891 pub fn set_current_line_highlight(
2892 &mut self,
2893 current_line_highlight: Option<CurrentLineHighlight>,
2894 ) {
2895 self.current_line_highlight = current_line_highlight;
2896 }
2897
2898 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2899 self.collapse_matches = collapse_matches;
2900 }
2901
2902 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2903 let buffers = self.buffer.read(cx).all_buffers();
2904 let Some(project) = self.project.as_ref() else {
2905 return;
2906 };
2907 project.update(cx, |project, cx| {
2908 for buffer in buffers {
2909 self.registered_buffers
2910 .entry(buffer.read(cx).remote_id())
2911 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2912 }
2913 })
2914 }
2915
2916 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2917 if self.collapse_matches {
2918 return range.start..range.start;
2919 }
2920 range.clone()
2921 }
2922
2923 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2924 if self.display_map.read(cx).clip_at_line_ends != clip {
2925 self.display_map
2926 .update(cx, |map, _| map.clip_at_line_ends = clip);
2927 }
2928 }
2929
2930 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2931 self.input_enabled = input_enabled;
2932 }
2933
2934 pub fn set_edit_predictions_hidden_for_vim_mode(
2935 &mut self,
2936 hidden: bool,
2937 window: &mut Window,
2938 cx: &mut Context<Self>,
2939 ) {
2940 if hidden != self.edit_predictions_hidden_for_vim_mode {
2941 self.edit_predictions_hidden_for_vim_mode = hidden;
2942 if hidden {
2943 self.update_visible_edit_prediction(window, cx);
2944 } else {
2945 self.refresh_edit_prediction(true, false, window, cx);
2946 }
2947 }
2948 }
2949
2950 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2951 self.menu_edit_predictions_policy = value;
2952 }
2953
2954 pub fn set_autoindent(&mut self, autoindent: bool) {
2955 if autoindent {
2956 self.autoindent_mode = Some(AutoindentMode::EachLine);
2957 } else {
2958 self.autoindent_mode = None;
2959 }
2960 }
2961
2962 pub fn read_only(&self, cx: &App) -> bool {
2963 self.read_only || self.buffer.read(cx).read_only()
2964 }
2965
2966 pub fn set_read_only(&mut self, read_only: bool) {
2967 self.read_only = read_only;
2968 }
2969
2970 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2971 self.use_autoclose = autoclose;
2972 }
2973
2974 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2975 self.use_auto_surround = auto_surround;
2976 }
2977
2978 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2979 self.auto_replace_emoji_shortcode = auto_replace;
2980 }
2981
2982 pub fn toggle_edit_predictions(
2983 &mut self,
2984 _: &ToggleEditPrediction,
2985 window: &mut Window,
2986 cx: &mut Context<Self>,
2987 ) {
2988 if self.show_edit_predictions_override.is_some() {
2989 self.set_show_edit_predictions(None, window, cx);
2990 } else {
2991 let show_edit_predictions = !self.edit_predictions_enabled();
2992 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2993 }
2994 }
2995
2996 pub fn set_show_edit_predictions(
2997 &mut self,
2998 show_edit_predictions: Option<bool>,
2999 window: &mut Window,
3000 cx: &mut Context<Self>,
3001 ) {
3002 self.show_edit_predictions_override = show_edit_predictions;
3003 self.update_edit_prediction_settings(cx);
3004
3005 if let Some(false) = show_edit_predictions {
3006 self.discard_edit_prediction(false, cx);
3007 } else {
3008 self.refresh_edit_prediction(false, true, window, cx);
3009 }
3010 }
3011
3012 fn edit_predictions_disabled_in_scope(
3013 &self,
3014 buffer: &Entity<Buffer>,
3015 buffer_position: language::Anchor,
3016 cx: &App,
3017 ) -> bool {
3018 let snapshot = buffer.read(cx).snapshot();
3019 let settings = snapshot.settings_at(buffer_position, cx);
3020
3021 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3022 return false;
3023 };
3024
3025 scope.override_name().is_some_and(|scope_name| {
3026 settings
3027 .edit_predictions_disabled_in
3028 .iter()
3029 .any(|s| s == scope_name)
3030 })
3031 }
3032
3033 pub fn set_use_modal_editing(&mut self, to: bool) {
3034 self.use_modal_editing = to;
3035 }
3036
3037 pub fn use_modal_editing(&self) -> bool {
3038 self.use_modal_editing
3039 }
3040
3041 fn selections_did_change(
3042 &mut self,
3043 local: bool,
3044 old_cursor_position: &Anchor,
3045 effects: SelectionEffects,
3046 window: &mut Window,
3047 cx: &mut Context<Self>,
3048 ) {
3049 window.invalidate_character_coordinates();
3050
3051 // Copy selections to primary selection buffer
3052 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3053 if local {
3054 let selections = self.selections.all::<usize>(cx);
3055 let buffer_handle = self.buffer.read(cx).read(cx);
3056
3057 let mut text = String::new();
3058 for (index, selection) in selections.iter().enumerate() {
3059 let text_for_selection = buffer_handle
3060 .text_for_range(selection.start..selection.end)
3061 .collect::<String>();
3062
3063 text.push_str(&text_for_selection);
3064 if index != selections.len() - 1 {
3065 text.push('\n');
3066 }
3067 }
3068
3069 if !text.is_empty() {
3070 cx.write_to_primary(ClipboardItem::new_string(text));
3071 }
3072 }
3073
3074 let selection_anchors = self.selections.disjoint_anchors_arc();
3075
3076 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3077 self.buffer.update(cx, |buffer, cx| {
3078 buffer.set_active_selections(
3079 &selection_anchors,
3080 self.selections.line_mode(),
3081 self.cursor_shape,
3082 cx,
3083 )
3084 });
3085 }
3086 let display_map = self
3087 .display_map
3088 .update(cx, |display_map, cx| display_map.snapshot(cx));
3089 let buffer = display_map.buffer_snapshot();
3090 if self.selections.count() == 1 {
3091 self.add_selections_state = None;
3092 }
3093 self.select_next_state = None;
3094 self.select_prev_state = None;
3095 self.select_syntax_node_history.try_clear();
3096 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3097 self.snippet_stack.invalidate(&selection_anchors, buffer);
3098 self.take_rename(false, window, cx);
3099
3100 let newest_selection = self.selections.newest_anchor();
3101 let new_cursor_position = newest_selection.head();
3102 let selection_start = newest_selection.start;
3103
3104 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3105 self.push_to_nav_history(
3106 *old_cursor_position,
3107 Some(new_cursor_position.to_point(buffer)),
3108 false,
3109 effects.nav_history == Some(true),
3110 cx,
3111 );
3112 }
3113
3114 if local {
3115 if let Some(buffer_id) = new_cursor_position.buffer_id
3116 && !self.registered_buffers.contains_key(&buffer_id)
3117 && let Some(project) = self.project.as_ref()
3118 {
3119 project.update(cx, |project, cx| {
3120 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
3121 return;
3122 };
3123 self.registered_buffers.insert(
3124 buffer_id,
3125 project.register_buffer_with_language_servers(&buffer, cx),
3126 );
3127 })
3128 }
3129
3130 let mut context_menu = self.context_menu.borrow_mut();
3131 let completion_menu = match context_menu.as_ref() {
3132 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3133 Some(CodeContextMenu::CodeActions(_)) => {
3134 *context_menu = None;
3135 None
3136 }
3137 None => None,
3138 };
3139 let completion_position = completion_menu.map(|menu| menu.initial_position);
3140 drop(context_menu);
3141
3142 if effects.completions
3143 && let Some(completion_position) = completion_position
3144 {
3145 let start_offset = selection_start.to_offset(buffer);
3146 let position_matches = start_offset == completion_position.to_offset(buffer);
3147 let continue_showing = if position_matches {
3148 if self.snippet_stack.is_empty() {
3149 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3150 == Some(CharKind::Word)
3151 } else {
3152 // Snippet choices can be shown even when the cursor is in whitespace.
3153 // Dismissing the menu with actions like backspace is handled by
3154 // invalidation regions.
3155 true
3156 }
3157 } else {
3158 false
3159 };
3160
3161 if continue_showing {
3162 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3163 } else {
3164 self.hide_context_menu(window, cx);
3165 }
3166 }
3167
3168 hide_hover(self, cx);
3169
3170 if old_cursor_position.to_display_point(&display_map).row()
3171 != new_cursor_position.to_display_point(&display_map).row()
3172 {
3173 self.available_code_actions.take();
3174 }
3175 self.refresh_code_actions(window, cx);
3176 self.refresh_document_highlights(cx);
3177 self.refresh_selected_text_highlights(false, window, cx);
3178 refresh_matching_bracket_highlights(self, cx);
3179 self.update_visible_edit_prediction(window, cx);
3180 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3181 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3182 self.inline_blame_popover.take();
3183 if self.git_blame_inline_enabled {
3184 self.start_inline_blame_timer(window, cx);
3185 }
3186 }
3187
3188 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3189 cx.emit(EditorEvent::SelectionsChanged { local });
3190
3191 let selections = &self.selections.disjoint_anchors_arc();
3192 if selections.len() == 1 {
3193 cx.emit(SearchEvent::ActiveMatchChanged)
3194 }
3195 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3196 let inmemory_selections = selections
3197 .iter()
3198 .map(|s| {
3199 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3200 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3201 })
3202 .collect();
3203 self.update_restoration_data(cx, |data| {
3204 data.selections = inmemory_selections;
3205 });
3206
3207 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3208 && let Some(workspace_id) =
3209 self.workspace.as_ref().and_then(|workspace| workspace.1)
3210 {
3211 let snapshot = self.buffer().read(cx).snapshot(cx);
3212 let selections = selections.clone();
3213 let background_executor = cx.background_executor().clone();
3214 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3215 self.serialize_selections = cx.background_spawn(async move {
3216 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3217 let db_selections = selections
3218 .iter()
3219 .map(|selection| {
3220 (
3221 selection.start.to_offset(&snapshot),
3222 selection.end.to_offset(&snapshot),
3223 )
3224 })
3225 .collect();
3226
3227 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3228 .await
3229 .with_context(|| {
3230 format!(
3231 "persisting editor selections for editor {editor_id}, \
3232 workspace {workspace_id:?}"
3233 )
3234 })
3235 .log_err();
3236 });
3237 }
3238 }
3239
3240 cx.notify();
3241 }
3242
3243 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3244 use text::ToOffset as _;
3245 use text::ToPoint as _;
3246
3247 if self.mode.is_minimap()
3248 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3249 {
3250 return;
3251 }
3252
3253 if !self.buffer().read(cx).is_singleton() {
3254 return;
3255 }
3256
3257 let display_snapshot = self
3258 .display_map
3259 .update(cx, |display_map, cx| display_map.snapshot(cx));
3260 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3261 return;
3262 };
3263 let inmemory_folds = display_snapshot
3264 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3265 .map(|fold| {
3266 fold.range.start.text_anchor.to_point(&snapshot)
3267 ..fold.range.end.text_anchor.to_point(&snapshot)
3268 })
3269 .collect();
3270 self.update_restoration_data(cx, |data| {
3271 data.folds = inmemory_folds;
3272 });
3273
3274 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3275 return;
3276 };
3277 let background_executor = cx.background_executor().clone();
3278 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3279 let db_folds = display_snapshot
3280 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3281 .map(|fold| {
3282 (
3283 fold.range.start.text_anchor.to_offset(&snapshot),
3284 fold.range.end.text_anchor.to_offset(&snapshot),
3285 )
3286 })
3287 .collect();
3288 self.serialize_folds = cx.background_spawn(async move {
3289 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3290 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3291 .await
3292 .with_context(|| {
3293 format!(
3294 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3295 )
3296 })
3297 .log_err();
3298 });
3299 }
3300
3301 pub fn sync_selections(
3302 &mut self,
3303 other: Entity<Editor>,
3304 cx: &mut Context<Self>,
3305 ) -> gpui::Subscription {
3306 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3307 if !other_selections.is_empty() {
3308 self.selections.change_with(cx, |selections| {
3309 selections.select_anchors(other_selections);
3310 });
3311 }
3312
3313 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3314 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3315 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3316 if other_selections.is_empty() {
3317 return;
3318 }
3319 this.selections.change_with(cx, |selections| {
3320 selections.select_anchors(other_selections);
3321 });
3322 }
3323 });
3324
3325 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3326 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3327 let these_selections = this.selections.disjoint_anchors().to_vec();
3328 if these_selections.is_empty() {
3329 return;
3330 }
3331 other.update(cx, |other_editor, cx| {
3332 other_editor.selections.change_with(cx, |selections| {
3333 selections.select_anchors(these_selections);
3334 })
3335 });
3336 }
3337 });
3338
3339 Subscription::join(other_subscription, this_subscription)
3340 }
3341
3342 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3343 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3344 /// effects of selection change occur at the end of the transaction.
3345 pub fn change_selections<R>(
3346 &mut self,
3347 effects: SelectionEffects,
3348 window: &mut Window,
3349 cx: &mut Context<Self>,
3350 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3351 ) -> R {
3352 if let Some(state) = &mut self.deferred_selection_effects_state {
3353 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3354 state.effects.completions = effects.completions;
3355 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3356 let (changed, result) = self.selections.change_with(cx, change);
3357 state.changed |= changed;
3358 return result;
3359 }
3360 let mut state = DeferredSelectionEffectsState {
3361 changed: false,
3362 effects,
3363 old_cursor_position: self.selections.newest_anchor().head(),
3364 history_entry: SelectionHistoryEntry {
3365 selections: self.selections.disjoint_anchors_arc(),
3366 select_next_state: self.select_next_state.clone(),
3367 select_prev_state: self.select_prev_state.clone(),
3368 add_selections_state: self.add_selections_state.clone(),
3369 },
3370 };
3371 let (changed, result) = self.selections.change_with(cx, change);
3372 state.changed = state.changed || changed;
3373 if self.defer_selection_effects {
3374 self.deferred_selection_effects_state = Some(state);
3375 } else {
3376 self.apply_selection_effects(state, window, cx);
3377 }
3378 result
3379 }
3380
3381 /// Defers the effects of selection change, so that the effects of multiple calls to
3382 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3383 /// to selection history and the state of popovers based on selection position aren't
3384 /// erroneously updated.
3385 pub fn with_selection_effects_deferred<R>(
3386 &mut self,
3387 window: &mut Window,
3388 cx: &mut Context<Self>,
3389 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3390 ) -> R {
3391 let already_deferred = self.defer_selection_effects;
3392 self.defer_selection_effects = true;
3393 let result = update(self, window, cx);
3394 if !already_deferred {
3395 self.defer_selection_effects = false;
3396 if let Some(state) = self.deferred_selection_effects_state.take() {
3397 self.apply_selection_effects(state, window, cx);
3398 }
3399 }
3400 result
3401 }
3402
3403 fn apply_selection_effects(
3404 &mut self,
3405 state: DeferredSelectionEffectsState,
3406 window: &mut Window,
3407 cx: &mut Context<Self>,
3408 ) {
3409 if state.changed {
3410 self.selection_history.push(state.history_entry);
3411
3412 if let Some(autoscroll) = state.effects.scroll {
3413 self.request_autoscroll(autoscroll, cx);
3414 }
3415
3416 let old_cursor_position = &state.old_cursor_position;
3417
3418 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3419
3420 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3421 self.show_signature_help(&ShowSignatureHelp, window, cx);
3422 }
3423 }
3424 }
3425
3426 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3427 where
3428 I: IntoIterator<Item = (Range<S>, T)>,
3429 S: ToOffset,
3430 T: Into<Arc<str>>,
3431 {
3432 if self.read_only(cx) {
3433 return;
3434 }
3435
3436 self.buffer
3437 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3438 }
3439
3440 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3441 where
3442 I: IntoIterator<Item = (Range<S>, T)>,
3443 S: ToOffset,
3444 T: Into<Arc<str>>,
3445 {
3446 if self.read_only(cx) {
3447 return;
3448 }
3449
3450 self.buffer.update(cx, |buffer, cx| {
3451 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3452 });
3453 }
3454
3455 pub fn edit_with_block_indent<I, S, T>(
3456 &mut self,
3457 edits: I,
3458 original_indent_columns: Vec<Option<u32>>,
3459 cx: &mut Context<Self>,
3460 ) where
3461 I: IntoIterator<Item = (Range<S>, T)>,
3462 S: ToOffset,
3463 T: Into<Arc<str>>,
3464 {
3465 if self.read_only(cx) {
3466 return;
3467 }
3468
3469 self.buffer.update(cx, |buffer, cx| {
3470 buffer.edit(
3471 edits,
3472 Some(AutoindentMode::Block {
3473 original_indent_columns,
3474 }),
3475 cx,
3476 )
3477 });
3478 }
3479
3480 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3481 self.hide_context_menu(window, cx);
3482
3483 match phase {
3484 SelectPhase::Begin {
3485 position,
3486 add,
3487 click_count,
3488 } => self.begin_selection(position, add, click_count, window, cx),
3489 SelectPhase::BeginColumnar {
3490 position,
3491 goal_column,
3492 reset,
3493 mode,
3494 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3495 SelectPhase::Extend {
3496 position,
3497 click_count,
3498 } => self.extend_selection(position, click_count, window, cx),
3499 SelectPhase::Update {
3500 position,
3501 goal_column,
3502 scroll_delta,
3503 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3504 SelectPhase::End => self.end_selection(window, cx),
3505 }
3506 }
3507
3508 fn extend_selection(
3509 &mut self,
3510 position: DisplayPoint,
3511 click_count: usize,
3512 window: &mut Window,
3513 cx: &mut Context<Self>,
3514 ) {
3515 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3516 let tail = self.selections.newest::<usize>(cx).tail();
3517 let click_count = click_count.max(match self.selections.select_mode() {
3518 SelectMode::Character => 1,
3519 SelectMode::Word(_) => 2,
3520 SelectMode::Line(_) => 3,
3521 SelectMode::All => 4,
3522 });
3523 self.begin_selection(position, false, click_count, window, cx);
3524
3525 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3526
3527 let current_selection = match self.selections.select_mode() {
3528 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3529 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3530 };
3531
3532 let mut pending_selection = self
3533 .selections
3534 .pending_anchor()
3535 .cloned()
3536 .expect("extend_selection not called with pending selection");
3537
3538 if pending_selection
3539 .start
3540 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3541 == Ordering::Greater
3542 {
3543 pending_selection.start = current_selection.start;
3544 }
3545 if pending_selection
3546 .end
3547 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3548 == Ordering::Less
3549 {
3550 pending_selection.end = current_selection.end;
3551 pending_selection.reversed = true;
3552 }
3553
3554 let mut pending_mode = self.selections.pending_mode().unwrap();
3555 match &mut pending_mode {
3556 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3557 _ => {}
3558 }
3559
3560 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3561 SelectionEffects::scroll(Autoscroll::fit())
3562 } else {
3563 SelectionEffects::no_scroll()
3564 };
3565
3566 self.change_selections(effects, window, cx, |s| {
3567 s.set_pending(pending_selection.clone(), pending_mode);
3568 s.set_is_extending(true);
3569 });
3570 }
3571
3572 fn begin_selection(
3573 &mut self,
3574 position: DisplayPoint,
3575 add: bool,
3576 click_count: usize,
3577 window: &mut Window,
3578 cx: &mut Context<Self>,
3579 ) {
3580 if !self.focus_handle.is_focused(window) {
3581 self.last_focused_descendant = None;
3582 window.focus(&self.focus_handle);
3583 }
3584
3585 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3586 let buffer = display_map.buffer_snapshot();
3587 let position = display_map.clip_point(position, Bias::Left);
3588
3589 let start;
3590 let end;
3591 let mode;
3592 let mut auto_scroll;
3593 match click_count {
3594 1 => {
3595 start = buffer.anchor_before(position.to_point(&display_map));
3596 end = start;
3597 mode = SelectMode::Character;
3598 auto_scroll = true;
3599 }
3600 2 => {
3601 let position = display_map
3602 .clip_point(position, Bias::Left)
3603 .to_offset(&display_map, Bias::Left);
3604 let (range, _) = buffer.surrounding_word(position, None);
3605 start = buffer.anchor_before(range.start);
3606 end = buffer.anchor_before(range.end);
3607 mode = SelectMode::Word(start..end);
3608 auto_scroll = true;
3609 }
3610 3 => {
3611 let position = display_map
3612 .clip_point(position, Bias::Left)
3613 .to_point(&display_map);
3614 let line_start = display_map.prev_line_boundary(position).0;
3615 let next_line_start = buffer.clip_point(
3616 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3617 Bias::Left,
3618 );
3619 start = buffer.anchor_before(line_start);
3620 end = buffer.anchor_before(next_line_start);
3621 mode = SelectMode::Line(start..end);
3622 auto_scroll = true;
3623 }
3624 _ => {
3625 start = buffer.anchor_before(0);
3626 end = buffer.anchor_before(buffer.len());
3627 mode = SelectMode::All;
3628 auto_scroll = false;
3629 }
3630 }
3631 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3632
3633 let point_to_delete: Option<usize> = {
3634 let selected_points: Vec<Selection<Point>> =
3635 self.selections.disjoint_in_range(start..end, cx);
3636
3637 if !add || click_count > 1 {
3638 None
3639 } else if !selected_points.is_empty() {
3640 Some(selected_points[0].id)
3641 } else {
3642 let clicked_point_already_selected =
3643 self.selections.disjoint_anchors().iter().find(|selection| {
3644 selection.start.to_point(buffer) == start.to_point(buffer)
3645 || selection.end.to_point(buffer) == end.to_point(buffer)
3646 });
3647
3648 clicked_point_already_selected.map(|selection| selection.id)
3649 }
3650 };
3651
3652 let selections_count = self.selections.count();
3653 let effects = if auto_scroll {
3654 SelectionEffects::default()
3655 } else {
3656 SelectionEffects::no_scroll()
3657 };
3658
3659 self.change_selections(effects, window, cx, |s| {
3660 if let Some(point_to_delete) = point_to_delete {
3661 s.delete(point_to_delete);
3662
3663 if selections_count == 1 {
3664 s.set_pending_anchor_range(start..end, mode);
3665 }
3666 } else {
3667 if !add {
3668 s.clear_disjoint();
3669 }
3670
3671 s.set_pending_anchor_range(start..end, mode);
3672 }
3673 });
3674 }
3675
3676 fn begin_columnar_selection(
3677 &mut self,
3678 position: DisplayPoint,
3679 goal_column: u32,
3680 reset: bool,
3681 mode: ColumnarMode,
3682 window: &mut Window,
3683 cx: &mut Context<Self>,
3684 ) {
3685 if !self.focus_handle.is_focused(window) {
3686 self.last_focused_descendant = None;
3687 window.focus(&self.focus_handle);
3688 }
3689
3690 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3691
3692 if reset {
3693 let pointer_position = display_map
3694 .buffer_snapshot()
3695 .anchor_before(position.to_point(&display_map));
3696
3697 self.change_selections(
3698 SelectionEffects::scroll(Autoscroll::newest()),
3699 window,
3700 cx,
3701 |s| {
3702 s.clear_disjoint();
3703 s.set_pending_anchor_range(
3704 pointer_position..pointer_position,
3705 SelectMode::Character,
3706 );
3707 },
3708 );
3709 };
3710
3711 let tail = self.selections.newest::<Point>(cx).tail();
3712 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3713 self.columnar_selection_state = match mode {
3714 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3715 selection_tail: selection_anchor,
3716 display_point: if reset {
3717 if position.column() != goal_column {
3718 Some(DisplayPoint::new(position.row(), goal_column))
3719 } else {
3720 None
3721 }
3722 } else {
3723 None
3724 },
3725 }),
3726 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3727 selection_tail: selection_anchor,
3728 }),
3729 };
3730
3731 if !reset {
3732 self.select_columns(position, goal_column, &display_map, window, cx);
3733 }
3734 }
3735
3736 fn update_selection(
3737 &mut self,
3738 position: DisplayPoint,
3739 goal_column: u32,
3740 scroll_delta: gpui::Point<f32>,
3741 window: &mut Window,
3742 cx: &mut Context<Self>,
3743 ) {
3744 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3745
3746 if self.columnar_selection_state.is_some() {
3747 self.select_columns(position, goal_column, &display_map, window, cx);
3748 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3749 let buffer = display_map.buffer_snapshot();
3750 let head;
3751 let tail;
3752 let mode = self.selections.pending_mode().unwrap();
3753 match &mode {
3754 SelectMode::Character => {
3755 head = position.to_point(&display_map);
3756 tail = pending.tail().to_point(buffer);
3757 }
3758 SelectMode::Word(original_range) => {
3759 let offset = display_map
3760 .clip_point(position, Bias::Left)
3761 .to_offset(&display_map, Bias::Left);
3762 let original_range = original_range.to_offset(buffer);
3763
3764 let head_offset = if buffer.is_inside_word(offset, None)
3765 || original_range.contains(&offset)
3766 {
3767 let (word_range, _) = buffer.surrounding_word(offset, None);
3768 if word_range.start < original_range.start {
3769 word_range.start
3770 } else {
3771 word_range.end
3772 }
3773 } else {
3774 offset
3775 };
3776
3777 head = head_offset.to_point(buffer);
3778 if head_offset <= original_range.start {
3779 tail = original_range.end.to_point(buffer);
3780 } else {
3781 tail = original_range.start.to_point(buffer);
3782 }
3783 }
3784 SelectMode::Line(original_range) => {
3785 let original_range = original_range.to_point(display_map.buffer_snapshot());
3786
3787 let position = display_map
3788 .clip_point(position, Bias::Left)
3789 .to_point(&display_map);
3790 let line_start = display_map.prev_line_boundary(position).0;
3791 let next_line_start = buffer.clip_point(
3792 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3793 Bias::Left,
3794 );
3795
3796 if line_start < original_range.start {
3797 head = line_start
3798 } else {
3799 head = next_line_start
3800 }
3801
3802 if head <= original_range.start {
3803 tail = original_range.end;
3804 } else {
3805 tail = original_range.start;
3806 }
3807 }
3808 SelectMode::All => {
3809 return;
3810 }
3811 };
3812
3813 if head < tail {
3814 pending.start = buffer.anchor_before(head);
3815 pending.end = buffer.anchor_before(tail);
3816 pending.reversed = true;
3817 } else {
3818 pending.start = buffer.anchor_before(tail);
3819 pending.end = buffer.anchor_before(head);
3820 pending.reversed = false;
3821 }
3822
3823 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3824 s.set_pending(pending.clone(), mode);
3825 });
3826 } else {
3827 log::error!("update_selection dispatched with no pending selection");
3828 return;
3829 }
3830
3831 self.apply_scroll_delta(scroll_delta, window, cx);
3832 cx.notify();
3833 }
3834
3835 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3836 self.columnar_selection_state.take();
3837 if let Some(pending_mode) = self.selections.pending_mode() {
3838 let selections = self.selections.all::<usize>(cx);
3839 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3840 s.select(selections);
3841 s.clear_pending();
3842 if s.is_extending() {
3843 s.set_is_extending(false);
3844 } else {
3845 s.set_select_mode(pending_mode);
3846 }
3847 });
3848 }
3849 }
3850
3851 fn select_columns(
3852 &mut self,
3853 head: DisplayPoint,
3854 goal_column: u32,
3855 display_map: &DisplaySnapshot,
3856 window: &mut Window,
3857 cx: &mut Context<Self>,
3858 ) {
3859 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3860 return;
3861 };
3862
3863 let tail = match columnar_state {
3864 ColumnarSelectionState::FromMouse {
3865 selection_tail,
3866 display_point,
3867 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3868 ColumnarSelectionState::FromSelection { selection_tail } => {
3869 selection_tail.to_display_point(display_map)
3870 }
3871 };
3872
3873 let start_row = cmp::min(tail.row(), head.row());
3874 let end_row = cmp::max(tail.row(), head.row());
3875 let start_column = cmp::min(tail.column(), goal_column);
3876 let end_column = cmp::max(tail.column(), goal_column);
3877 let reversed = start_column < tail.column();
3878
3879 let selection_ranges = (start_row.0..=end_row.0)
3880 .map(DisplayRow)
3881 .filter_map(|row| {
3882 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3883 || start_column <= display_map.line_len(row))
3884 && !display_map.is_block_line(row)
3885 {
3886 let start = display_map
3887 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3888 .to_point(display_map);
3889 let end = display_map
3890 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3891 .to_point(display_map);
3892 if reversed {
3893 Some(end..start)
3894 } else {
3895 Some(start..end)
3896 }
3897 } else {
3898 None
3899 }
3900 })
3901 .collect::<Vec<_>>();
3902
3903 let ranges = match columnar_state {
3904 ColumnarSelectionState::FromMouse { .. } => {
3905 let mut non_empty_ranges = selection_ranges
3906 .iter()
3907 .filter(|selection_range| selection_range.start != selection_range.end)
3908 .peekable();
3909 if non_empty_ranges.peek().is_some() {
3910 non_empty_ranges.cloned().collect()
3911 } else {
3912 selection_ranges
3913 }
3914 }
3915 _ => selection_ranges,
3916 };
3917
3918 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3919 s.select_ranges(ranges);
3920 });
3921 cx.notify();
3922 }
3923
3924 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3925 self.selections
3926 .all_adjusted(cx)
3927 .iter()
3928 .any(|selection| !selection.is_empty())
3929 }
3930
3931 pub fn has_pending_nonempty_selection(&self) -> bool {
3932 let pending_nonempty_selection = match self.selections.pending_anchor() {
3933 Some(Selection { start, end, .. }) => start != end,
3934 None => false,
3935 };
3936
3937 pending_nonempty_selection
3938 || (self.columnar_selection_state.is_some()
3939 && self.selections.disjoint_anchors().len() > 1)
3940 }
3941
3942 pub fn has_pending_selection(&self) -> bool {
3943 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3944 }
3945
3946 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3947 self.selection_mark_mode = false;
3948 self.selection_drag_state = SelectionDragState::None;
3949
3950 if self.clear_expanded_diff_hunks(cx) {
3951 cx.notify();
3952 return;
3953 }
3954 if self.dismiss_menus_and_popups(true, window, cx) {
3955 return;
3956 }
3957
3958 if self.mode.is_full()
3959 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3960 {
3961 return;
3962 }
3963
3964 cx.propagate();
3965 }
3966
3967 pub fn dismiss_menus_and_popups(
3968 &mut self,
3969 is_user_requested: bool,
3970 window: &mut Window,
3971 cx: &mut Context<Self>,
3972 ) -> bool {
3973 if self.take_rename(false, window, cx).is_some() {
3974 return true;
3975 }
3976
3977 if hide_hover(self, cx) {
3978 return true;
3979 }
3980
3981 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3982 return true;
3983 }
3984
3985 if self.hide_context_menu(window, cx).is_some() {
3986 return true;
3987 }
3988
3989 if self.mouse_context_menu.take().is_some() {
3990 return true;
3991 }
3992
3993 if is_user_requested && self.discard_edit_prediction(true, cx) {
3994 return true;
3995 }
3996
3997 if self.snippet_stack.pop().is_some() {
3998 return true;
3999 }
4000
4001 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4002 self.dismiss_diagnostics(cx);
4003 return true;
4004 }
4005
4006 false
4007 }
4008
4009 fn linked_editing_ranges_for(
4010 &self,
4011 selection: Range<text::Anchor>,
4012 cx: &App,
4013 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4014 if self.linked_edit_ranges.is_empty() {
4015 return None;
4016 }
4017 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4018 selection.end.buffer_id.and_then(|end_buffer_id| {
4019 if selection.start.buffer_id != Some(end_buffer_id) {
4020 return None;
4021 }
4022 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4023 let snapshot = buffer.read(cx).snapshot();
4024 self.linked_edit_ranges
4025 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4026 .map(|ranges| (ranges, snapshot, buffer))
4027 })?;
4028 use text::ToOffset as TO;
4029 // find offset from the start of current range to current cursor position
4030 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4031
4032 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4033 let start_difference = start_offset - start_byte_offset;
4034 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4035 let end_difference = end_offset - start_byte_offset;
4036 // Current range has associated linked ranges.
4037 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4038 for range in linked_ranges.iter() {
4039 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4040 let end_offset = start_offset + end_difference;
4041 let start_offset = start_offset + start_difference;
4042 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4043 continue;
4044 }
4045 if self.selections.disjoint_anchor_ranges().any(|s| {
4046 if s.start.buffer_id != selection.start.buffer_id
4047 || s.end.buffer_id != selection.end.buffer_id
4048 {
4049 return false;
4050 }
4051 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4052 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4053 }) {
4054 continue;
4055 }
4056 let start = buffer_snapshot.anchor_after(start_offset);
4057 let end = buffer_snapshot.anchor_after(end_offset);
4058 linked_edits
4059 .entry(buffer.clone())
4060 .or_default()
4061 .push(start..end);
4062 }
4063 Some(linked_edits)
4064 }
4065
4066 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4067 let text: Arc<str> = text.into();
4068
4069 if self.read_only(cx) {
4070 return;
4071 }
4072
4073 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4074
4075 let selections = self.selections.all_adjusted(cx);
4076 let mut bracket_inserted = false;
4077 let mut edits = Vec::new();
4078 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4079 let mut new_selections = Vec::with_capacity(selections.len());
4080 let mut new_autoclose_regions = Vec::new();
4081 let snapshot = self.buffer.read(cx).read(cx);
4082 let mut clear_linked_edit_ranges = false;
4083
4084 for (selection, autoclose_region) in
4085 self.selections_with_autoclose_regions(selections, &snapshot)
4086 {
4087 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4088 // Determine if the inserted text matches the opening or closing
4089 // bracket of any of this language's bracket pairs.
4090 let mut bracket_pair = None;
4091 let mut is_bracket_pair_start = false;
4092 let mut is_bracket_pair_end = false;
4093 if !text.is_empty() {
4094 let mut bracket_pair_matching_end = None;
4095 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4096 // and they are removing the character that triggered IME popup.
4097 for (pair, enabled) in scope.brackets() {
4098 if !pair.close && !pair.surround {
4099 continue;
4100 }
4101
4102 if enabled && pair.start.ends_with(text.as_ref()) {
4103 let prefix_len = pair.start.len() - text.len();
4104 let preceding_text_matches_prefix = prefix_len == 0
4105 || (selection.start.column >= (prefix_len as u32)
4106 && snapshot.contains_str_at(
4107 Point::new(
4108 selection.start.row,
4109 selection.start.column - (prefix_len as u32),
4110 ),
4111 &pair.start[..prefix_len],
4112 ));
4113 if preceding_text_matches_prefix {
4114 bracket_pair = Some(pair.clone());
4115 is_bracket_pair_start = true;
4116 break;
4117 }
4118 }
4119 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4120 {
4121 // take first bracket pair matching end, but don't break in case a later bracket
4122 // pair matches start
4123 bracket_pair_matching_end = Some(pair.clone());
4124 }
4125 }
4126 if let Some(end) = bracket_pair_matching_end
4127 && bracket_pair.is_none()
4128 {
4129 bracket_pair = Some(end);
4130 is_bracket_pair_end = true;
4131 }
4132 }
4133
4134 if let Some(bracket_pair) = bracket_pair {
4135 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4136 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4137 let auto_surround =
4138 self.use_auto_surround && snapshot_settings.use_auto_surround;
4139 if selection.is_empty() {
4140 if is_bracket_pair_start {
4141 // If the inserted text is a suffix of an opening bracket and the
4142 // selection is preceded by the rest of the opening bracket, then
4143 // insert the closing bracket.
4144 let following_text_allows_autoclose = snapshot
4145 .chars_at(selection.start)
4146 .next()
4147 .is_none_or(|c| scope.should_autoclose_before(c));
4148
4149 let preceding_text_allows_autoclose = selection.start.column == 0
4150 || snapshot
4151 .reversed_chars_at(selection.start)
4152 .next()
4153 .is_none_or(|c| {
4154 bracket_pair.start != bracket_pair.end
4155 || !snapshot
4156 .char_classifier_at(selection.start)
4157 .is_word(c)
4158 });
4159
4160 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4161 && bracket_pair.start.len() == 1
4162 {
4163 let target = bracket_pair.start.chars().next().unwrap();
4164 let current_line_count = snapshot
4165 .reversed_chars_at(selection.start)
4166 .take_while(|&c| c != '\n')
4167 .filter(|&c| c == target)
4168 .count();
4169 current_line_count % 2 == 1
4170 } else {
4171 false
4172 };
4173
4174 if autoclose
4175 && bracket_pair.close
4176 && following_text_allows_autoclose
4177 && preceding_text_allows_autoclose
4178 && !is_closing_quote
4179 {
4180 let anchor = snapshot.anchor_before(selection.end);
4181 new_selections.push((selection.map(|_| anchor), text.len()));
4182 new_autoclose_regions.push((
4183 anchor,
4184 text.len(),
4185 selection.id,
4186 bracket_pair.clone(),
4187 ));
4188 edits.push((
4189 selection.range(),
4190 format!("{}{}", text, bracket_pair.end).into(),
4191 ));
4192 bracket_inserted = true;
4193 continue;
4194 }
4195 }
4196
4197 if let Some(region) = autoclose_region {
4198 // If the selection is followed by an auto-inserted closing bracket,
4199 // then don't insert that closing bracket again; just move the selection
4200 // past the closing bracket.
4201 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4202 && text.as_ref() == region.pair.end.as_str()
4203 && snapshot.contains_str_at(region.range.end, text.as_ref());
4204 if should_skip {
4205 let anchor = snapshot.anchor_after(selection.end);
4206 new_selections
4207 .push((selection.map(|_| anchor), region.pair.end.len()));
4208 continue;
4209 }
4210 }
4211
4212 let always_treat_brackets_as_autoclosed = snapshot
4213 .language_settings_at(selection.start, cx)
4214 .always_treat_brackets_as_autoclosed;
4215 if always_treat_brackets_as_autoclosed
4216 && is_bracket_pair_end
4217 && snapshot.contains_str_at(selection.end, text.as_ref())
4218 {
4219 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4220 // and the inserted text is a closing bracket and the selection is followed
4221 // by the closing bracket then move the selection past the closing bracket.
4222 let anchor = snapshot.anchor_after(selection.end);
4223 new_selections.push((selection.map(|_| anchor), text.len()));
4224 continue;
4225 }
4226 }
4227 // If an opening bracket is 1 character long and is typed while
4228 // text is selected, then surround that text with the bracket pair.
4229 else if auto_surround
4230 && bracket_pair.surround
4231 && is_bracket_pair_start
4232 && bracket_pair.start.chars().count() == 1
4233 {
4234 edits.push((selection.start..selection.start, text.clone()));
4235 edits.push((
4236 selection.end..selection.end,
4237 bracket_pair.end.as_str().into(),
4238 ));
4239 bracket_inserted = true;
4240 new_selections.push((
4241 Selection {
4242 id: selection.id,
4243 start: snapshot.anchor_after(selection.start),
4244 end: snapshot.anchor_before(selection.end),
4245 reversed: selection.reversed,
4246 goal: selection.goal,
4247 },
4248 0,
4249 ));
4250 continue;
4251 }
4252 }
4253 }
4254
4255 if self.auto_replace_emoji_shortcode
4256 && selection.is_empty()
4257 && text.as_ref().ends_with(':')
4258 && let Some(possible_emoji_short_code) =
4259 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4260 && !possible_emoji_short_code.is_empty()
4261 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4262 {
4263 let emoji_shortcode_start = Point::new(
4264 selection.start.row,
4265 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4266 );
4267
4268 // Remove shortcode from buffer
4269 edits.push((
4270 emoji_shortcode_start..selection.start,
4271 "".to_string().into(),
4272 ));
4273 new_selections.push((
4274 Selection {
4275 id: selection.id,
4276 start: snapshot.anchor_after(emoji_shortcode_start),
4277 end: snapshot.anchor_before(selection.start),
4278 reversed: selection.reversed,
4279 goal: selection.goal,
4280 },
4281 0,
4282 ));
4283
4284 // Insert emoji
4285 let selection_start_anchor = snapshot.anchor_after(selection.start);
4286 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4287 edits.push((selection.start..selection.end, emoji.to_string().into()));
4288
4289 continue;
4290 }
4291
4292 // If not handling any auto-close operation, then just replace the selected
4293 // text with the given input and move the selection to the end of the
4294 // newly inserted text.
4295 let anchor = snapshot.anchor_after(selection.end);
4296 if !self.linked_edit_ranges.is_empty() {
4297 let start_anchor = snapshot.anchor_before(selection.start);
4298
4299 let is_word_char = text.chars().next().is_none_or(|char| {
4300 let classifier = snapshot
4301 .char_classifier_at(start_anchor.to_offset(&snapshot))
4302 .scope_context(Some(CharScopeContext::LinkedEdit));
4303 classifier.is_word(char)
4304 });
4305
4306 if is_word_char {
4307 if let Some(ranges) = self
4308 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4309 {
4310 for (buffer, edits) in ranges {
4311 linked_edits
4312 .entry(buffer.clone())
4313 .or_default()
4314 .extend(edits.into_iter().map(|range| (range, text.clone())));
4315 }
4316 }
4317 } else {
4318 clear_linked_edit_ranges = true;
4319 }
4320 }
4321
4322 new_selections.push((selection.map(|_| anchor), 0));
4323 edits.push((selection.start..selection.end, text.clone()));
4324 }
4325
4326 drop(snapshot);
4327
4328 self.transact(window, cx, |this, window, cx| {
4329 if clear_linked_edit_ranges {
4330 this.linked_edit_ranges.clear();
4331 }
4332 let initial_buffer_versions =
4333 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4334
4335 this.buffer.update(cx, |buffer, cx| {
4336 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4337 });
4338 for (buffer, edits) in linked_edits {
4339 buffer.update(cx, |buffer, cx| {
4340 let snapshot = buffer.snapshot();
4341 let edits = edits
4342 .into_iter()
4343 .map(|(range, text)| {
4344 use text::ToPoint as TP;
4345 let end_point = TP::to_point(&range.end, &snapshot);
4346 let start_point = TP::to_point(&range.start, &snapshot);
4347 (start_point..end_point, text)
4348 })
4349 .sorted_by_key(|(range, _)| range.start);
4350 buffer.edit(edits, None, cx);
4351 })
4352 }
4353 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4354 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4355 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4356 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4357 .zip(new_selection_deltas)
4358 .map(|(selection, delta)| Selection {
4359 id: selection.id,
4360 start: selection.start + delta,
4361 end: selection.end + delta,
4362 reversed: selection.reversed,
4363 goal: SelectionGoal::None,
4364 })
4365 .collect::<Vec<_>>();
4366
4367 let mut i = 0;
4368 for (position, delta, selection_id, pair) in new_autoclose_regions {
4369 let position = position.to_offset(map.buffer_snapshot()) + delta;
4370 let start = map.buffer_snapshot().anchor_before(position);
4371 let end = map.buffer_snapshot().anchor_after(position);
4372 while let Some(existing_state) = this.autoclose_regions.get(i) {
4373 match existing_state
4374 .range
4375 .start
4376 .cmp(&start, map.buffer_snapshot())
4377 {
4378 Ordering::Less => i += 1,
4379 Ordering::Greater => break,
4380 Ordering::Equal => {
4381 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4382 Ordering::Less => i += 1,
4383 Ordering::Equal => break,
4384 Ordering::Greater => break,
4385 }
4386 }
4387 }
4388 }
4389 this.autoclose_regions.insert(
4390 i,
4391 AutocloseRegion {
4392 selection_id,
4393 range: start..end,
4394 pair,
4395 },
4396 );
4397 }
4398
4399 let had_active_edit_prediction = this.has_active_edit_prediction();
4400 this.change_selections(
4401 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4402 window,
4403 cx,
4404 |s| s.select(new_selections),
4405 );
4406
4407 if !bracket_inserted
4408 && let Some(on_type_format_task) =
4409 this.trigger_on_type_formatting(text.to_string(), window, cx)
4410 {
4411 on_type_format_task.detach_and_log_err(cx);
4412 }
4413
4414 let editor_settings = EditorSettings::get_global(cx);
4415 if bracket_inserted
4416 && (editor_settings.auto_signature_help
4417 || editor_settings.show_signature_help_after_edits)
4418 {
4419 this.show_signature_help(&ShowSignatureHelp, window, cx);
4420 }
4421
4422 let trigger_in_words =
4423 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4424 if this.hard_wrap.is_some() {
4425 let latest: Range<Point> = this.selections.newest(cx).range();
4426 if latest.is_empty()
4427 && this
4428 .buffer()
4429 .read(cx)
4430 .snapshot(cx)
4431 .line_len(MultiBufferRow(latest.start.row))
4432 == latest.start.column
4433 {
4434 this.rewrap_impl(
4435 RewrapOptions {
4436 override_language_settings: true,
4437 preserve_existing_whitespace: true,
4438 },
4439 cx,
4440 )
4441 }
4442 }
4443 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4444 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4445 this.refresh_edit_prediction(true, false, window, cx);
4446 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4447 });
4448 }
4449
4450 fn find_possible_emoji_shortcode_at_position(
4451 snapshot: &MultiBufferSnapshot,
4452 position: Point,
4453 ) -> Option<String> {
4454 let mut chars = Vec::new();
4455 let mut found_colon = false;
4456 for char in snapshot.reversed_chars_at(position).take(100) {
4457 // Found a possible emoji shortcode in the middle of the buffer
4458 if found_colon {
4459 if char.is_whitespace() {
4460 chars.reverse();
4461 return Some(chars.iter().collect());
4462 }
4463 // If the previous character is not a whitespace, we are in the middle of a word
4464 // and we only want to complete the shortcode if the word is made up of other emojis
4465 let mut containing_word = String::new();
4466 for ch in snapshot
4467 .reversed_chars_at(position)
4468 .skip(chars.len() + 1)
4469 .take(100)
4470 {
4471 if ch.is_whitespace() {
4472 break;
4473 }
4474 containing_word.push(ch);
4475 }
4476 let containing_word = containing_word.chars().rev().collect::<String>();
4477 if util::word_consists_of_emojis(containing_word.as_str()) {
4478 chars.reverse();
4479 return Some(chars.iter().collect());
4480 }
4481 }
4482
4483 if char.is_whitespace() || !char.is_ascii() {
4484 return None;
4485 }
4486 if char == ':' {
4487 found_colon = true;
4488 } else {
4489 chars.push(char);
4490 }
4491 }
4492 // Found a possible emoji shortcode at the beginning of the buffer
4493 chars.reverse();
4494 Some(chars.iter().collect())
4495 }
4496
4497 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4498 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4499 self.transact(window, cx, |this, window, cx| {
4500 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4501 let selections = this.selections.all::<usize>(cx);
4502 let multi_buffer = this.buffer.read(cx);
4503 let buffer = multi_buffer.snapshot(cx);
4504 selections
4505 .iter()
4506 .map(|selection| {
4507 let start_point = selection.start.to_point(&buffer);
4508 let mut existing_indent =
4509 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4510 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4511 let start = selection.start;
4512 let end = selection.end;
4513 let selection_is_empty = start == end;
4514 let language_scope = buffer.language_scope_at(start);
4515 let (
4516 comment_delimiter,
4517 doc_delimiter,
4518 insert_extra_newline,
4519 indent_on_newline,
4520 indent_on_extra_newline,
4521 ) = if let Some(language) = &language_scope {
4522 let mut insert_extra_newline =
4523 insert_extra_newline_brackets(&buffer, start..end, language)
4524 || insert_extra_newline_tree_sitter(&buffer, start..end);
4525
4526 // Comment extension on newline is allowed only for cursor selections
4527 let comment_delimiter = maybe!({
4528 if !selection_is_empty {
4529 return None;
4530 }
4531
4532 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4533 return None;
4534 }
4535
4536 let delimiters = language.line_comment_prefixes();
4537 let max_len_of_delimiter =
4538 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4539 let (snapshot, range) =
4540 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4541
4542 let num_of_whitespaces = snapshot
4543 .chars_for_range(range.clone())
4544 .take_while(|c| c.is_whitespace())
4545 .count();
4546 let comment_candidate = snapshot
4547 .chars_for_range(range.clone())
4548 .skip(num_of_whitespaces)
4549 .take(max_len_of_delimiter)
4550 .collect::<String>();
4551 let (delimiter, trimmed_len) = delimiters
4552 .iter()
4553 .filter_map(|delimiter| {
4554 let prefix = delimiter.trim_end();
4555 if comment_candidate.starts_with(prefix) {
4556 Some((delimiter, prefix.len()))
4557 } else {
4558 None
4559 }
4560 })
4561 .max_by_key(|(_, len)| *len)?;
4562
4563 if let Some(BlockCommentConfig {
4564 start: block_start, ..
4565 }) = language.block_comment()
4566 {
4567 let block_start_trimmed = block_start.trim_end();
4568 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4569 let line_content = snapshot
4570 .chars_for_range(range)
4571 .skip(num_of_whitespaces)
4572 .take(block_start_trimmed.len())
4573 .collect::<String>();
4574
4575 if line_content.starts_with(block_start_trimmed) {
4576 return None;
4577 }
4578 }
4579 }
4580
4581 let cursor_is_placed_after_comment_marker =
4582 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4583 if cursor_is_placed_after_comment_marker {
4584 Some(delimiter.clone())
4585 } else {
4586 None
4587 }
4588 });
4589
4590 let mut indent_on_newline = IndentSize::spaces(0);
4591 let mut indent_on_extra_newline = IndentSize::spaces(0);
4592
4593 let doc_delimiter = maybe!({
4594 if !selection_is_empty {
4595 return None;
4596 }
4597
4598 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4599 return None;
4600 }
4601
4602 let BlockCommentConfig {
4603 start: start_tag,
4604 end: end_tag,
4605 prefix: delimiter,
4606 tab_size: len,
4607 } = language.documentation_comment()?;
4608 let is_within_block_comment = buffer
4609 .language_scope_at(start_point)
4610 .is_some_and(|scope| scope.override_name() == Some("comment"));
4611 if !is_within_block_comment {
4612 return None;
4613 }
4614
4615 let (snapshot, range) =
4616 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4617
4618 let num_of_whitespaces = snapshot
4619 .chars_for_range(range.clone())
4620 .take_while(|c| c.is_whitespace())
4621 .count();
4622
4623 // 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.
4624 let column = start_point.column;
4625 let cursor_is_after_start_tag = {
4626 let start_tag_len = start_tag.len();
4627 let start_tag_line = snapshot
4628 .chars_for_range(range.clone())
4629 .skip(num_of_whitespaces)
4630 .take(start_tag_len)
4631 .collect::<String>();
4632 if start_tag_line.starts_with(start_tag.as_ref()) {
4633 num_of_whitespaces + start_tag_len <= column as usize
4634 } else {
4635 false
4636 }
4637 };
4638
4639 let cursor_is_after_delimiter = {
4640 let delimiter_trim = delimiter.trim_end();
4641 let delimiter_line = snapshot
4642 .chars_for_range(range.clone())
4643 .skip(num_of_whitespaces)
4644 .take(delimiter_trim.len())
4645 .collect::<String>();
4646 if delimiter_line.starts_with(delimiter_trim) {
4647 num_of_whitespaces + delimiter_trim.len() <= column as usize
4648 } else {
4649 false
4650 }
4651 };
4652
4653 let cursor_is_before_end_tag_if_exists = {
4654 let mut char_position = 0u32;
4655 let mut end_tag_offset = None;
4656
4657 'outer: for chunk in snapshot.text_for_range(range) {
4658 if let Some(byte_pos) = chunk.find(&**end_tag) {
4659 let chars_before_match =
4660 chunk[..byte_pos].chars().count() as u32;
4661 end_tag_offset =
4662 Some(char_position + chars_before_match);
4663 break 'outer;
4664 }
4665 char_position += chunk.chars().count() as u32;
4666 }
4667
4668 if let Some(end_tag_offset) = end_tag_offset {
4669 let cursor_is_before_end_tag = column <= end_tag_offset;
4670 if cursor_is_after_start_tag {
4671 if cursor_is_before_end_tag {
4672 insert_extra_newline = true;
4673 }
4674 let cursor_is_at_start_of_end_tag =
4675 column == end_tag_offset;
4676 if cursor_is_at_start_of_end_tag {
4677 indent_on_extra_newline.len = *len;
4678 }
4679 }
4680 cursor_is_before_end_tag
4681 } else {
4682 true
4683 }
4684 };
4685
4686 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4687 && cursor_is_before_end_tag_if_exists
4688 {
4689 if cursor_is_after_start_tag {
4690 indent_on_newline.len = *len;
4691 }
4692 Some(delimiter.clone())
4693 } else {
4694 None
4695 }
4696 });
4697
4698 (
4699 comment_delimiter,
4700 doc_delimiter,
4701 insert_extra_newline,
4702 indent_on_newline,
4703 indent_on_extra_newline,
4704 )
4705 } else {
4706 (
4707 None,
4708 None,
4709 false,
4710 IndentSize::default(),
4711 IndentSize::default(),
4712 )
4713 };
4714
4715 let prevent_auto_indent = doc_delimiter.is_some();
4716 let delimiter = comment_delimiter.or(doc_delimiter);
4717
4718 let capacity_for_delimiter =
4719 delimiter.as_deref().map(str::len).unwrap_or_default();
4720 let mut new_text = String::with_capacity(
4721 1 + capacity_for_delimiter
4722 + existing_indent.len as usize
4723 + indent_on_newline.len as usize
4724 + indent_on_extra_newline.len as usize,
4725 );
4726 new_text.push('\n');
4727 new_text.extend(existing_indent.chars());
4728 new_text.extend(indent_on_newline.chars());
4729
4730 if let Some(delimiter) = &delimiter {
4731 new_text.push_str(delimiter);
4732 }
4733
4734 if insert_extra_newline {
4735 new_text.push('\n');
4736 new_text.extend(existing_indent.chars());
4737 new_text.extend(indent_on_extra_newline.chars());
4738 }
4739
4740 let anchor = buffer.anchor_after(end);
4741 let new_selection = selection.map(|_| anchor);
4742 (
4743 ((start..end, new_text), prevent_auto_indent),
4744 (insert_extra_newline, new_selection),
4745 )
4746 })
4747 .unzip()
4748 };
4749
4750 let mut auto_indent_edits = Vec::new();
4751 let mut edits = Vec::new();
4752 for (edit, prevent_auto_indent) in edits_with_flags {
4753 if prevent_auto_indent {
4754 edits.push(edit);
4755 } else {
4756 auto_indent_edits.push(edit);
4757 }
4758 }
4759 if !edits.is_empty() {
4760 this.edit(edits, cx);
4761 }
4762 if !auto_indent_edits.is_empty() {
4763 this.edit_with_autoindent(auto_indent_edits, cx);
4764 }
4765
4766 let buffer = this.buffer.read(cx).snapshot(cx);
4767 let new_selections = selection_info
4768 .into_iter()
4769 .map(|(extra_newline_inserted, new_selection)| {
4770 let mut cursor = new_selection.end.to_point(&buffer);
4771 if extra_newline_inserted {
4772 cursor.row -= 1;
4773 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4774 }
4775 new_selection.map(|_| cursor)
4776 })
4777 .collect();
4778
4779 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4780 this.refresh_edit_prediction(true, false, window, cx);
4781 });
4782 }
4783
4784 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4785 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4786
4787 let buffer = self.buffer.read(cx);
4788 let snapshot = buffer.snapshot(cx);
4789
4790 let mut edits = Vec::new();
4791 let mut rows = Vec::new();
4792
4793 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
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(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(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>(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 if let Some(InlaySplice {
5306 to_remove,
5307 to_insert,
5308 }) = self.inlay_hint_cache.spawn_hint_refresh(
5309 reason_description,
5310 self.visible_excerpts(required_languages.as_ref(), cx),
5311 invalidate_cache,
5312 ignore_debounce,
5313 cx,
5314 ) {
5315 self.splice_inlays(&to_remove, to_insert, cx);
5316 }
5317 }
5318
5319 pub fn clear_inlay_hints(&self, cx: &mut Context<Editor>) {
5320 self.splice_inlays(
5321 &self
5322 .visible_inlay_hints(cx)
5323 .map(|inlay| inlay.id)
5324 .collect::<Vec<_>>(),
5325 Vec::new(),
5326 cx,
5327 );
5328 }
5329
5330 fn visible_inlay_hints<'a>(
5331 &'a self,
5332 cx: &'a Context<Editor>,
5333 ) -> impl Iterator<Item = &'a Inlay> {
5334 self.display_map
5335 .read(cx)
5336 .current_inlays()
5337 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5338 }
5339
5340 pub fn visible_excerpts(
5341 &self,
5342 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5343 cx: &mut Context<Editor>,
5344 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5345 let Some(project) = self.project() else {
5346 return HashMap::default();
5347 };
5348 let project = project.read(cx);
5349 let multi_buffer = self.buffer().read(cx);
5350 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5351 let multi_buffer_visible_start = self
5352 .scroll_manager
5353 .anchor()
5354 .anchor
5355 .to_point(&multi_buffer_snapshot);
5356 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5357 multi_buffer_visible_start
5358 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5359 Bias::Left,
5360 );
5361 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5362 multi_buffer_snapshot
5363 .range_to_buffer_ranges(multi_buffer_visible_range)
5364 .into_iter()
5365 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5366 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5367 let buffer_file = project::File::from_dyn(buffer.file())?;
5368 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5369 let worktree_entry = buffer_worktree
5370 .read(cx)
5371 .entry_for_id(buffer_file.project_entry_id()?)?;
5372 if worktree_entry.is_ignored {
5373 return None;
5374 }
5375
5376 let language = buffer.language()?;
5377 if let Some(restrict_to_languages) = restrict_to_languages
5378 && !restrict_to_languages.contains(language)
5379 {
5380 return None;
5381 }
5382 Some((
5383 excerpt_id,
5384 (
5385 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5386 buffer.version().clone(),
5387 excerpt_visible_range,
5388 ),
5389 ))
5390 })
5391 .collect()
5392 }
5393
5394 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5395 TextLayoutDetails {
5396 text_system: window.text_system().clone(),
5397 editor_style: self.style.clone().unwrap(),
5398 rem_size: window.rem_size(),
5399 scroll_anchor: self.scroll_manager.anchor(),
5400 visible_rows: self.visible_line_count(),
5401 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5402 }
5403 }
5404
5405 pub fn splice_inlays(
5406 &self,
5407 to_remove: &[InlayId],
5408 to_insert: Vec<Inlay>,
5409 cx: &mut Context<Self>,
5410 ) {
5411 self.display_map.update(cx, |display_map, cx| {
5412 display_map.splice_inlays(to_remove, to_insert, cx)
5413 });
5414 cx.notify();
5415 }
5416
5417 fn trigger_on_type_formatting(
5418 &self,
5419 input: String,
5420 window: &mut Window,
5421 cx: &mut Context<Self>,
5422 ) -> Option<Task<Result<()>>> {
5423 if input.len() != 1 {
5424 return None;
5425 }
5426
5427 let project = self.project()?;
5428 let position = self.selections.newest_anchor().head();
5429 let (buffer, buffer_position) = self
5430 .buffer
5431 .read(cx)
5432 .text_anchor_for_position(position, cx)?;
5433
5434 let settings = language_settings::language_settings(
5435 buffer
5436 .read(cx)
5437 .language_at(buffer_position)
5438 .map(|l| l.name()),
5439 buffer.read(cx).file(),
5440 cx,
5441 );
5442 if !settings.use_on_type_format {
5443 return None;
5444 }
5445
5446 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5447 // hence we do LSP request & edit on host side only — add formats to host's history.
5448 let push_to_lsp_host_history = true;
5449 // If this is not the host, append its history with new edits.
5450 let push_to_client_history = project.read(cx).is_via_collab();
5451
5452 let on_type_formatting = project.update(cx, |project, cx| {
5453 project.on_type_format(
5454 buffer.clone(),
5455 buffer_position,
5456 input,
5457 push_to_lsp_host_history,
5458 cx,
5459 )
5460 });
5461 Some(cx.spawn_in(window, async move |editor, cx| {
5462 if let Some(transaction) = on_type_formatting.await? {
5463 if push_to_client_history {
5464 buffer
5465 .update(cx, |buffer, _| {
5466 buffer.push_transaction(transaction, Instant::now());
5467 buffer.finalize_last_transaction();
5468 })
5469 .ok();
5470 }
5471 editor.update(cx, |editor, cx| {
5472 editor.refresh_document_highlights(cx);
5473 })?;
5474 }
5475 Ok(())
5476 }))
5477 }
5478
5479 pub fn show_word_completions(
5480 &mut self,
5481 _: &ShowWordCompletions,
5482 window: &mut Window,
5483 cx: &mut Context<Self>,
5484 ) {
5485 self.open_or_update_completions_menu(
5486 Some(CompletionsMenuSource::Words {
5487 ignore_threshold: true,
5488 }),
5489 None,
5490 window,
5491 cx,
5492 );
5493 }
5494
5495 pub fn show_completions(
5496 &mut self,
5497 options: &ShowCompletions,
5498 window: &mut Window,
5499 cx: &mut Context<Self>,
5500 ) {
5501 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5502 }
5503
5504 fn open_or_update_completions_menu(
5505 &mut self,
5506 requested_source: Option<CompletionsMenuSource>,
5507 trigger: Option<&str>,
5508 window: &mut Window,
5509 cx: &mut Context<Self>,
5510 ) {
5511 if self.pending_rename.is_some() {
5512 return;
5513 }
5514
5515 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5516
5517 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5518 // inserted and selected. To handle that case, the start of the selection is used so that
5519 // the menu starts with all choices.
5520 let position = self
5521 .selections
5522 .newest_anchor()
5523 .start
5524 .bias_right(&multibuffer_snapshot);
5525 if position.diff_base_anchor.is_some() {
5526 return;
5527 }
5528 let buffer_position = multibuffer_snapshot.anchor_before(position);
5529 let Some(buffer) = buffer_position
5530 .buffer_id
5531 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5532 else {
5533 return;
5534 };
5535 let buffer_snapshot = buffer.read(cx).snapshot();
5536
5537 let query: Option<Arc<String>> =
5538 Self::completion_query(&multibuffer_snapshot, buffer_position)
5539 .map(|query| query.into());
5540
5541 drop(multibuffer_snapshot);
5542
5543 // Hide the current completions menu when query is empty. Without this, cached
5544 // completions from before the trigger char may be reused (#32774).
5545 if query.is_none() {
5546 let menu_is_open = matches!(
5547 self.context_menu.borrow().as_ref(),
5548 Some(CodeContextMenu::Completions(_))
5549 );
5550 if menu_is_open {
5551 self.hide_context_menu(window, cx);
5552 }
5553 }
5554
5555 let mut ignore_word_threshold = false;
5556 let provider = match requested_source {
5557 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5558 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5559 ignore_word_threshold = ignore_threshold;
5560 None
5561 }
5562 Some(CompletionsMenuSource::SnippetChoices) => {
5563 log::error!("bug: SnippetChoices requested_source is not handled");
5564 None
5565 }
5566 };
5567
5568 let sort_completions = provider
5569 .as_ref()
5570 .is_some_and(|provider| provider.sort_completions());
5571
5572 let filter_completions = provider
5573 .as_ref()
5574 .is_none_or(|provider| provider.filter_completions());
5575
5576 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5577 if filter_completions {
5578 menu.filter(query.clone(), provider.clone(), window, cx);
5579 }
5580 // When `is_incomplete` is false, no need to re-query completions when the current query
5581 // is a suffix of the initial query.
5582 if !menu.is_incomplete {
5583 // If the new query is a suffix of the old query (typing more characters) and
5584 // the previous result was complete, the existing completions can be filtered.
5585 //
5586 // Note that this is always true for snippet completions.
5587 let query_matches = match (&menu.initial_query, &query) {
5588 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5589 (None, _) => true,
5590 _ => false,
5591 };
5592 if query_matches {
5593 let position_matches = if menu.initial_position == position {
5594 true
5595 } else {
5596 let snapshot = self.buffer.read(cx).read(cx);
5597 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5598 };
5599 if position_matches {
5600 return;
5601 }
5602 }
5603 }
5604 };
5605
5606 let trigger_kind = match trigger {
5607 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5608 CompletionTriggerKind::TRIGGER_CHARACTER
5609 }
5610 _ => CompletionTriggerKind::INVOKED,
5611 };
5612 let completion_context = CompletionContext {
5613 trigger_character: trigger.and_then(|trigger| {
5614 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5615 Some(String::from(trigger))
5616 } else {
5617 None
5618 }
5619 }),
5620 trigger_kind,
5621 };
5622
5623 let Anchor {
5624 excerpt_id: buffer_excerpt_id,
5625 text_anchor: buffer_position,
5626 ..
5627 } = buffer_position;
5628
5629 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5630 buffer_snapshot.surrounding_word(buffer_position, None)
5631 {
5632 let word_to_exclude = buffer_snapshot
5633 .text_for_range(word_range.clone())
5634 .collect::<String>();
5635 (
5636 buffer_snapshot.anchor_before(word_range.start)
5637 ..buffer_snapshot.anchor_after(buffer_position),
5638 Some(word_to_exclude),
5639 )
5640 } else {
5641 (buffer_position..buffer_position, None)
5642 };
5643
5644 let language = buffer_snapshot
5645 .language_at(buffer_position)
5646 .map(|language| language.name());
5647
5648 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5649 .completions
5650 .clone();
5651
5652 let show_completion_documentation = buffer_snapshot
5653 .settings_at(buffer_position, cx)
5654 .show_completion_documentation;
5655
5656 // The document can be large, so stay in reasonable bounds when searching for words,
5657 // otherwise completion pop-up might be slow to appear.
5658 const WORD_LOOKUP_ROWS: u32 = 5_000;
5659 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5660 let min_word_search = buffer_snapshot.clip_point(
5661 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5662 Bias::Left,
5663 );
5664 let max_word_search = buffer_snapshot.clip_point(
5665 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5666 Bias::Right,
5667 );
5668 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5669 ..buffer_snapshot.point_to_offset(max_word_search);
5670
5671 let skip_digits = query
5672 .as_ref()
5673 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5674
5675 let omit_word_completions = !self.word_completions_enabled
5676 || (!ignore_word_threshold
5677 && match &query {
5678 Some(query) => query.chars().count() < completion_settings.words_min_length,
5679 None => completion_settings.words_min_length != 0,
5680 });
5681
5682 let (mut words, provider_responses) = match &provider {
5683 Some(provider) => {
5684 let provider_responses = provider.completions(
5685 buffer_excerpt_id,
5686 &buffer,
5687 buffer_position,
5688 completion_context,
5689 window,
5690 cx,
5691 );
5692
5693 let words = match (omit_word_completions, completion_settings.words) {
5694 (true, _) | (_, WordsCompletionMode::Disabled) => {
5695 Task::ready(BTreeMap::default())
5696 }
5697 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5698 .background_spawn(async move {
5699 buffer_snapshot.words_in_range(WordsQuery {
5700 fuzzy_contents: None,
5701 range: word_search_range,
5702 skip_digits,
5703 })
5704 }),
5705 };
5706
5707 (words, provider_responses)
5708 }
5709 None => {
5710 let words = if omit_word_completions {
5711 Task::ready(BTreeMap::default())
5712 } else {
5713 cx.background_spawn(async move {
5714 buffer_snapshot.words_in_range(WordsQuery {
5715 fuzzy_contents: None,
5716 range: word_search_range,
5717 skip_digits,
5718 })
5719 })
5720 };
5721 (words, Task::ready(Ok(Vec::new())))
5722 }
5723 };
5724
5725 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5726
5727 let id = post_inc(&mut self.next_completion_id);
5728 let task = cx.spawn_in(window, async move |editor, cx| {
5729 let Ok(()) = editor.update(cx, |this, _| {
5730 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5731 }) else {
5732 return;
5733 };
5734
5735 // TODO: Ideally completions from different sources would be selectively re-queried, so
5736 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5737 let mut completions = Vec::new();
5738 let mut is_incomplete = false;
5739 let mut display_options: Option<CompletionDisplayOptions> = None;
5740 if let Some(provider_responses) = provider_responses.await.log_err()
5741 && !provider_responses.is_empty()
5742 {
5743 for response in provider_responses {
5744 completions.extend(response.completions);
5745 is_incomplete = is_incomplete || response.is_incomplete;
5746 match display_options.as_mut() {
5747 None => {
5748 display_options = Some(response.display_options);
5749 }
5750 Some(options) => options.merge(&response.display_options),
5751 }
5752 }
5753 if completion_settings.words == WordsCompletionMode::Fallback {
5754 words = Task::ready(BTreeMap::default());
5755 }
5756 }
5757 let display_options = display_options.unwrap_or_default();
5758
5759 let mut words = words.await;
5760 if let Some(word_to_exclude) = &word_to_exclude {
5761 words.remove(word_to_exclude);
5762 }
5763 for lsp_completion in &completions {
5764 words.remove(&lsp_completion.new_text);
5765 }
5766 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5767 replace_range: word_replace_range.clone(),
5768 new_text: word.clone(),
5769 label: CodeLabel::plain(word, None),
5770 icon_path: None,
5771 documentation: None,
5772 source: CompletionSource::BufferWord {
5773 word_range,
5774 resolved: false,
5775 },
5776 insert_text_mode: Some(InsertTextMode::AS_IS),
5777 confirm: None,
5778 }));
5779
5780 let menu = if completions.is_empty() {
5781 None
5782 } else {
5783 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5784 let languages = editor
5785 .workspace
5786 .as_ref()
5787 .and_then(|(workspace, _)| workspace.upgrade())
5788 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5789 let menu = CompletionsMenu::new(
5790 id,
5791 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5792 sort_completions,
5793 show_completion_documentation,
5794 position,
5795 query.clone(),
5796 is_incomplete,
5797 buffer.clone(),
5798 completions.into(),
5799 display_options,
5800 snippet_sort_order,
5801 languages,
5802 language,
5803 cx,
5804 );
5805
5806 let query = if filter_completions { query } else { None };
5807 let matches_task = if let Some(query) = query {
5808 menu.do_async_filtering(query, cx)
5809 } else {
5810 Task::ready(menu.unfiltered_matches())
5811 };
5812 (menu, matches_task)
5813 }) else {
5814 return;
5815 };
5816
5817 let matches = matches_task.await;
5818
5819 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5820 // Newer menu already set, so exit.
5821 if let Some(CodeContextMenu::Completions(prev_menu)) =
5822 editor.context_menu.borrow().as_ref()
5823 && prev_menu.id > id
5824 {
5825 return;
5826 };
5827
5828 // Only valid to take prev_menu because it the new menu is immediately set
5829 // below, or the menu is hidden.
5830 if let Some(CodeContextMenu::Completions(prev_menu)) =
5831 editor.context_menu.borrow_mut().take()
5832 {
5833 let position_matches =
5834 if prev_menu.initial_position == menu.initial_position {
5835 true
5836 } else {
5837 let snapshot = editor.buffer.read(cx).read(cx);
5838 prev_menu.initial_position.to_offset(&snapshot)
5839 == menu.initial_position.to_offset(&snapshot)
5840 };
5841 if position_matches {
5842 // Preserve markdown cache before `set_filter_results` because it will
5843 // try to populate the documentation cache.
5844 menu.preserve_markdown_cache(prev_menu);
5845 }
5846 };
5847
5848 menu.set_filter_results(matches, provider, window, cx);
5849 }) else {
5850 return;
5851 };
5852
5853 menu.visible().then_some(menu)
5854 };
5855
5856 editor
5857 .update_in(cx, |editor, window, cx| {
5858 if editor.focus_handle.is_focused(window)
5859 && let Some(menu) = menu
5860 {
5861 *editor.context_menu.borrow_mut() =
5862 Some(CodeContextMenu::Completions(menu));
5863
5864 crate::hover_popover::hide_hover(editor, cx);
5865 if editor.show_edit_predictions_in_menu() {
5866 editor.update_visible_edit_prediction(window, cx);
5867 } else {
5868 editor.discard_edit_prediction(false, cx);
5869 }
5870
5871 cx.notify();
5872 return;
5873 }
5874
5875 if editor.completion_tasks.len() <= 1 {
5876 // If there are no more completion tasks and the last menu was empty, we should hide it.
5877 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5878 // If it was already hidden and we don't show edit predictions in the menu,
5879 // we should also show the edit prediction when available.
5880 if was_hidden && editor.show_edit_predictions_in_menu() {
5881 editor.update_visible_edit_prediction(window, cx);
5882 }
5883 }
5884 })
5885 .ok();
5886 });
5887
5888 self.completion_tasks.push((id, task));
5889 }
5890
5891 #[cfg(feature = "test-support")]
5892 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5893 let menu = self.context_menu.borrow();
5894 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5895 let completions = menu.completions.borrow();
5896 Some(completions.to_vec())
5897 } else {
5898 None
5899 }
5900 }
5901
5902 pub fn with_completions_menu_matching_id<R>(
5903 &self,
5904 id: CompletionId,
5905 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5906 ) -> R {
5907 let mut context_menu = self.context_menu.borrow_mut();
5908 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5909 return f(None);
5910 };
5911 if completions_menu.id != id {
5912 return f(None);
5913 }
5914 f(Some(completions_menu))
5915 }
5916
5917 pub fn confirm_completion(
5918 &mut self,
5919 action: &ConfirmCompletion,
5920 window: &mut Window,
5921 cx: &mut Context<Self>,
5922 ) -> Option<Task<Result<()>>> {
5923 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5924 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5925 }
5926
5927 pub fn confirm_completion_insert(
5928 &mut self,
5929 _: &ConfirmCompletionInsert,
5930 window: &mut Window,
5931 cx: &mut Context<Self>,
5932 ) -> Option<Task<Result<()>>> {
5933 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5934 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5935 }
5936
5937 pub fn confirm_completion_replace(
5938 &mut self,
5939 _: &ConfirmCompletionReplace,
5940 window: &mut Window,
5941 cx: &mut Context<Self>,
5942 ) -> Option<Task<Result<()>>> {
5943 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5944 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5945 }
5946
5947 pub fn compose_completion(
5948 &mut self,
5949 action: &ComposeCompletion,
5950 window: &mut Window,
5951 cx: &mut Context<Self>,
5952 ) -> Option<Task<Result<()>>> {
5953 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5954 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5955 }
5956
5957 fn do_completion(
5958 &mut self,
5959 item_ix: Option<usize>,
5960 intent: CompletionIntent,
5961 window: &mut Window,
5962 cx: &mut Context<Editor>,
5963 ) -> Option<Task<Result<()>>> {
5964 use language::ToOffset as _;
5965
5966 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5967 else {
5968 return None;
5969 };
5970
5971 let candidate_id = {
5972 let entries = completions_menu.entries.borrow();
5973 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5974 if self.show_edit_predictions_in_menu() {
5975 self.discard_edit_prediction(true, cx);
5976 }
5977 mat.candidate_id
5978 };
5979
5980 let completion = completions_menu
5981 .completions
5982 .borrow()
5983 .get(candidate_id)?
5984 .clone();
5985 cx.stop_propagation();
5986
5987 let buffer_handle = completions_menu.buffer.clone();
5988
5989 let CompletionEdit {
5990 new_text,
5991 snippet,
5992 replace_range,
5993 } = process_completion_for_edit(
5994 &completion,
5995 intent,
5996 &buffer_handle,
5997 &completions_menu.initial_position.text_anchor,
5998 cx,
5999 );
6000
6001 let buffer = buffer_handle.read(cx);
6002 let snapshot = self.buffer.read(cx).snapshot(cx);
6003 let newest_anchor = self.selections.newest_anchor();
6004 let replace_range_multibuffer = {
6005 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6006 let multibuffer_anchor = snapshot
6007 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
6008 .unwrap()
6009 ..snapshot
6010 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
6011 .unwrap();
6012 multibuffer_anchor.start.to_offset(&snapshot)
6013 ..multibuffer_anchor.end.to_offset(&snapshot)
6014 };
6015 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6016 return None;
6017 }
6018
6019 let old_text = buffer
6020 .text_for_range(replace_range.clone())
6021 .collect::<String>();
6022 let lookbehind = newest_anchor
6023 .start
6024 .text_anchor
6025 .to_offset(buffer)
6026 .saturating_sub(replace_range.start);
6027 let lookahead = replace_range
6028 .end
6029 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6030 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6031 let suffix = &old_text[lookbehind.min(old_text.len())..];
6032
6033 let selections = self.selections.all::<usize>(cx);
6034 let mut ranges = Vec::new();
6035 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6036
6037 for selection in &selections {
6038 let range = if selection.id == newest_anchor.id {
6039 replace_range_multibuffer.clone()
6040 } else {
6041 let mut range = selection.range();
6042
6043 // if prefix is present, don't duplicate it
6044 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
6045 range.start = range.start.saturating_sub(lookbehind);
6046
6047 // if suffix is also present, mimic the newest cursor and replace it
6048 if selection.id != newest_anchor.id
6049 && snapshot.contains_str_at(range.end, suffix)
6050 {
6051 range.end += lookahead;
6052 }
6053 }
6054 range
6055 };
6056
6057 ranges.push(range.clone());
6058
6059 if !self.linked_edit_ranges.is_empty() {
6060 let start_anchor = snapshot.anchor_before(range.start);
6061 let end_anchor = snapshot.anchor_after(range.end);
6062 if let Some(ranges) = self
6063 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6064 {
6065 for (buffer, edits) in ranges {
6066 linked_edits
6067 .entry(buffer.clone())
6068 .or_default()
6069 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6070 }
6071 }
6072 }
6073 }
6074
6075 let common_prefix_len = old_text
6076 .chars()
6077 .zip(new_text.chars())
6078 .take_while(|(a, b)| a == b)
6079 .map(|(a, _)| a.len_utf8())
6080 .sum::<usize>();
6081
6082 cx.emit(EditorEvent::InputHandled {
6083 utf16_range_to_replace: None,
6084 text: new_text[common_prefix_len..].into(),
6085 });
6086
6087 self.transact(window, cx, |editor, window, cx| {
6088 if let Some(mut snippet) = snippet {
6089 snippet.text = new_text.to_string();
6090 editor
6091 .insert_snippet(&ranges, snippet, window, cx)
6092 .log_err();
6093 } else {
6094 editor.buffer.update(cx, |multi_buffer, cx| {
6095 let auto_indent = match completion.insert_text_mode {
6096 Some(InsertTextMode::AS_IS) => None,
6097 _ => editor.autoindent_mode.clone(),
6098 };
6099 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6100 multi_buffer.edit(edits, auto_indent, cx);
6101 });
6102 }
6103 for (buffer, edits) in linked_edits {
6104 buffer.update(cx, |buffer, cx| {
6105 let snapshot = buffer.snapshot();
6106 let edits = edits
6107 .into_iter()
6108 .map(|(range, text)| {
6109 use text::ToPoint as TP;
6110 let end_point = TP::to_point(&range.end, &snapshot);
6111 let start_point = TP::to_point(&range.start, &snapshot);
6112 (start_point..end_point, text)
6113 })
6114 .sorted_by_key(|(range, _)| range.start);
6115 buffer.edit(edits, None, cx);
6116 })
6117 }
6118
6119 editor.refresh_edit_prediction(true, false, window, cx);
6120 });
6121 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6122
6123 let show_new_completions_on_confirm = completion
6124 .confirm
6125 .as_ref()
6126 .is_some_and(|confirm| confirm(intent, window, cx));
6127 if show_new_completions_on_confirm {
6128 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6129 }
6130
6131 let provider = self.completion_provider.as_ref()?;
6132 drop(completion);
6133 let apply_edits = provider.apply_additional_edits_for_completion(
6134 buffer_handle,
6135 completions_menu.completions.clone(),
6136 candidate_id,
6137 true,
6138 cx,
6139 );
6140
6141 let editor_settings = EditorSettings::get_global(cx);
6142 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6143 // After the code completion is finished, users often want to know what signatures are needed.
6144 // so we should automatically call signature_help
6145 self.show_signature_help(&ShowSignatureHelp, window, cx);
6146 }
6147
6148 Some(cx.foreground_executor().spawn(async move {
6149 apply_edits.await?;
6150 Ok(())
6151 }))
6152 }
6153
6154 pub fn toggle_code_actions(
6155 &mut self,
6156 action: &ToggleCodeActions,
6157 window: &mut Window,
6158 cx: &mut Context<Self>,
6159 ) {
6160 let quick_launch = action.quick_launch;
6161 let mut context_menu = self.context_menu.borrow_mut();
6162 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6163 if code_actions.deployed_from == action.deployed_from {
6164 // Toggle if we're selecting the same one
6165 *context_menu = None;
6166 cx.notify();
6167 return;
6168 } else {
6169 // Otherwise, clear it and start a new one
6170 *context_menu = None;
6171 cx.notify();
6172 }
6173 }
6174 drop(context_menu);
6175 let snapshot = self.snapshot(window, cx);
6176 let deployed_from = action.deployed_from.clone();
6177 let action = action.clone();
6178 self.completion_tasks.clear();
6179 self.discard_edit_prediction(false, cx);
6180
6181 let multibuffer_point = match &action.deployed_from {
6182 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6183 DisplayPoint::new(*row, 0).to_point(&snapshot)
6184 }
6185 _ => self.selections.newest::<Point>(cx).head(),
6186 };
6187 let Some((buffer, buffer_row)) = snapshot
6188 .buffer_snapshot()
6189 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6190 .and_then(|(buffer_snapshot, range)| {
6191 self.buffer()
6192 .read(cx)
6193 .buffer(buffer_snapshot.remote_id())
6194 .map(|buffer| (buffer, range.start.row))
6195 })
6196 else {
6197 return;
6198 };
6199 let buffer_id = buffer.read(cx).remote_id();
6200 let tasks = self
6201 .tasks
6202 .get(&(buffer_id, buffer_row))
6203 .map(|t| Arc::new(t.to_owned()));
6204
6205 if !self.focus_handle.is_focused(window) {
6206 return;
6207 }
6208 let project = self.project.clone();
6209
6210 let code_actions_task = match deployed_from {
6211 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6212 _ => self.code_actions(buffer_row, window, cx),
6213 };
6214
6215 let runnable_task = match deployed_from {
6216 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6217 _ => {
6218 let mut task_context_task = Task::ready(None);
6219 if let Some(tasks) = &tasks
6220 && let Some(project) = project
6221 {
6222 task_context_task =
6223 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6224 }
6225
6226 cx.spawn_in(window, {
6227 let buffer = buffer.clone();
6228 async move |editor, cx| {
6229 let task_context = task_context_task.await;
6230
6231 let resolved_tasks =
6232 tasks
6233 .zip(task_context.clone())
6234 .map(|(tasks, task_context)| ResolvedTasks {
6235 templates: tasks.resolve(&task_context).collect(),
6236 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6237 multibuffer_point.row,
6238 tasks.column,
6239 )),
6240 });
6241 let debug_scenarios = editor
6242 .update(cx, |editor, cx| {
6243 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6244 })?
6245 .await;
6246 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6247 }
6248 })
6249 }
6250 };
6251
6252 cx.spawn_in(window, async move |editor, cx| {
6253 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6254 let code_actions = code_actions_task.await;
6255 let spawn_straight_away = quick_launch
6256 && resolved_tasks
6257 .as_ref()
6258 .is_some_and(|tasks| tasks.templates.len() == 1)
6259 && code_actions
6260 .as_ref()
6261 .is_none_or(|actions| actions.is_empty())
6262 && debug_scenarios.is_empty();
6263
6264 editor.update_in(cx, |editor, window, cx| {
6265 crate::hover_popover::hide_hover(editor, cx);
6266 let actions = CodeActionContents::new(
6267 resolved_tasks,
6268 code_actions,
6269 debug_scenarios,
6270 task_context.unwrap_or_default(),
6271 );
6272
6273 // Don't show the menu if there are no actions available
6274 if actions.is_empty() {
6275 cx.notify();
6276 return Task::ready(Ok(()));
6277 }
6278
6279 *editor.context_menu.borrow_mut() =
6280 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6281 buffer,
6282 actions,
6283 selected_item: Default::default(),
6284 scroll_handle: UniformListScrollHandle::default(),
6285 deployed_from,
6286 }));
6287 cx.notify();
6288 if spawn_straight_away
6289 && let Some(task) = editor.confirm_code_action(
6290 &ConfirmCodeAction { item_ix: Some(0) },
6291 window,
6292 cx,
6293 )
6294 {
6295 return task;
6296 }
6297
6298 Task::ready(Ok(()))
6299 })
6300 })
6301 .detach_and_log_err(cx);
6302 }
6303
6304 fn debug_scenarios(
6305 &mut self,
6306 resolved_tasks: &Option<ResolvedTasks>,
6307 buffer: &Entity<Buffer>,
6308 cx: &mut App,
6309 ) -> Task<Vec<task::DebugScenario>> {
6310 maybe!({
6311 let project = self.project()?;
6312 let dap_store = project.read(cx).dap_store();
6313 let mut scenarios = vec![];
6314 let resolved_tasks = resolved_tasks.as_ref()?;
6315 let buffer = buffer.read(cx);
6316 let language = buffer.language()?;
6317 let file = buffer.file();
6318 let debug_adapter = language_settings(language.name().into(), file, cx)
6319 .debuggers
6320 .first()
6321 .map(SharedString::from)
6322 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6323
6324 dap_store.update(cx, |dap_store, cx| {
6325 for (_, task) in &resolved_tasks.templates {
6326 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6327 task.original_task().clone(),
6328 debug_adapter.clone().into(),
6329 task.display_label().to_owned().into(),
6330 cx,
6331 );
6332 scenarios.push(maybe_scenario);
6333 }
6334 });
6335 Some(cx.background_spawn(async move {
6336 futures::future::join_all(scenarios)
6337 .await
6338 .into_iter()
6339 .flatten()
6340 .collect::<Vec<_>>()
6341 }))
6342 })
6343 .unwrap_or_else(|| Task::ready(vec![]))
6344 }
6345
6346 fn code_actions(
6347 &mut self,
6348 buffer_row: u32,
6349 window: &mut Window,
6350 cx: &mut Context<Self>,
6351 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6352 let mut task = self.code_actions_task.take();
6353 cx.spawn_in(window, async move |editor, cx| {
6354 while let Some(prev_task) = task {
6355 prev_task.await.log_err();
6356 task = editor
6357 .update(cx, |this, _| this.code_actions_task.take())
6358 .ok()?;
6359 }
6360
6361 editor
6362 .update(cx, |editor, cx| {
6363 editor
6364 .available_code_actions
6365 .clone()
6366 .and_then(|(location, code_actions)| {
6367 let snapshot = location.buffer.read(cx).snapshot();
6368 let point_range = location.range.to_point(&snapshot);
6369 let point_range = point_range.start.row..=point_range.end.row;
6370 if point_range.contains(&buffer_row) {
6371 Some(code_actions)
6372 } else {
6373 None
6374 }
6375 })
6376 })
6377 .ok()
6378 .flatten()
6379 })
6380 }
6381
6382 pub fn confirm_code_action(
6383 &mut self,
6384 action: &ConfirmCodeAction,
6385 window: &mut Window,
6386 cx: &mut Context<Self>,
6387 ) -> Option<Task<Result<()>>> {
6388 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6389
6390 let actions_menu =
6391 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6392 menu
6393 } else {
6394 return None;
6395 };
6396
6397 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6398 let action = actions_menu.actions.get(action_ix)?;
6399 let title = action.label();
6400 let buffer = actions_menu.buffer;
6401 let workspace = self.workspace()?;
6402
6403 match action {
6404 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6405 workspace.update(cx, |workspace, cx| {
6406 workspace.schedule_resolved_task(
6407 task_source_kind,
6408 resolved_task,
6409 false,
6410 window,
6411 cx,
6412 );
6413
6414 Some(Task::ready(Ok(())))
6415 })
6416 }
6417 CodeActionsItem::CodeAction {
6418 excerpt_id,
6419 action,
6420 provider,
6421 } => {
6422 let apply_code_action =
6423 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6424 let workspace = workspace.downgrade();
6425 Some(cx.spawn_in(window, async move |editor, cx| {
6426 let project_transaction = apply_code_action.await?;
6427 Self::open_project_transaction(
6428 &editor,
6429 workspace,
6430 project_transaction,
6431 title,
6432 cx,
6433 )
6434 .await
6435 }))
6436 }
6437 CodeActionsItem::DebugScenario(scenario) => {
6438 let context = actions_menu.actions.context;
6439
6440 workspace.update(cx, |workspace, cx| {
6441 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6442 workspace.start_debug_session(
6443 scenario,
6444 context,
6445 Some(buffer),
6446 None,
6447 window,
6448 cx,
6449 );
6450 });
6451 Some(Task::ready(Ok(())))
6452 }
6453 }
6454 }
6455
6456 pub async fn open_project_transaction(
6457 editor: &WeakEntity<Editor>,
6458 workspace: WeakEntity<Workspace>,
6459 transaction: ProjectTransaction,
6460 title: String,
6461 cx: &mut AsyncWindowContext,
6462 ) -> Result<()> {
6463 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6464 cx.update(|_, cx| {
6465 entries.sort_unstable_by_key(|(buffer, _)| {
6466 buffer.read(cx).file().map(|f| f.path().clone())
6467 });
6468 })?;
6469 if entries.is_empty() {
6470 return Ok(());
6471 }
6472
6473 // If the project transaction's edits are all contained within this editor, then
6474 // avoid opening a new editor to display them.
6475
6476 if let [(buffer, transaction)] = &*entries {
6477 let excerpt = editor.update(cx, |editor, cx| {
6478 editor
6479 .buffer()
6480 .read(cx)
6481 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6482 })?;
6483 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6484 && excerpted_buffer == *buffer
6485 {
6486 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6487 let excerpt_range = excerpt_range.to_offset(buffer);
6488 buffer
6489 .edited_ranges_for_transaction::<usize>(transaction)
6490 .all(|range| {
6491 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6492 })
6493 })?;
6494
6495 if all_edits_within_excerpt {
6496 return Ok(());
6497 }
6498 }
6499 }
6500
6501 let mut ranges_to_highlight = Vec::new();
6502 let excerpt_buffer = cx.new(|cx| {
6503 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6504 for (buffer_handle, transaction) in &entries {
6505 let edited_ranges = buffer_handle
6506 .read(cx)
6507 .edited_ranges_for_transaction::<Point>(transaction)
6508 .collect::<Vec<_>>();
6509 let (ranges, _) = multibuffer.set_excerpts_for_path(
6510 PathKey::for_buffer(buffer_handle, cx),
6511 buffer_handle.clone(),
6512 edited_ranges,
6513 multibuffer_context_lines(cx),
6514 cx,
6515 );
6516
6517 ranges_to_highlight.extend(ranges);
6518 }
6519 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6520 multibuffer
6521 })?;
6522
6523 workspace.update_in(cx, |workspace, window, cx| {
6524 let project = workspace.project().clone();
6525 let editor =
6526 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6527 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6528 editor.update(cx, |editor, cx| {
6529 editor.highlight_background::<Self>(
6530 &ranges_to_highlight,
6531 |theme| theme.colors().editor_highlighted_line_background,
6532 cx,
6533 );
6534 });
6535 })?;
6536
6537 Ok(())
6538 }
6539
6540 pub fn clear_code_action_providers(&mut self) {
6541 self.code_action_providers.clear();
6542 self.available_code_actions.take();
6543 }
6544
6545 pub fn add_code_action_provider(
6546 &mut self,
6547 provider: Rc<dyn CodeActionProvider>,
6548 window: &mut Window,
6549 cx: &mut Context<Self>,
6550 ) {
6551 if self
6552 .code_action_providers
6553 .iter()
6554 .any(|existing_provider| existing_provider.id() == provider.id())
6555 {
6556 return;
6557 }
6558
6559 self.code_action_providers.push(provider);
6560 self.refresh_code_actions(window, cx);
6561 }
6562
6563 pub fn remove_code_action_provider(
6564 &mut self,
6565 id: Arc<str>,
6566 window: &mut Window,
6567 cx: &mut Context<Self>,
6568 ) {
6569 self.code_action_providers
6570 .retain(|provider| provider.id() != id);
6571 self.refresh_code_actions(window, cx);
6572 }
6573
6574 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6575 !self.code_action_providers.is_empty()
6576 && EditorSettings::get_global(cx).toolbar.code_actions
6577 }
6578
6579 pub fn has_available_code_actions(&self) -> bool {
6580 self.available_code_actions
6581 .as_ref()
6582 .is_some_and(|(_, actions)| !actions.is_empty())
6583 }
6584
6585 fn render_inline_code_actions(
6586 &self,
6587 icon_size: ui::IconSize,
6588 display_row: DisplayRow,
6589 is_active: bool,
6590 cx: &mut Context<Self>,
6591 ) -> AnyElement {
6592 let show_tooltip = !self.context_menu_visible();
6593 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6594 .icon_size(icon_size)
6595 .shape(ui::IconButtonShape::Square)
6596 .icon_color(ui::Color::Hidden)
6597 .toggle_state(is_active)
6598 .when(show_tooltip, |this| {
6599 this.tooltip({
6600 let focus_handle = self.focus_handle.clone();
6601 move |window, cx| {
6602 Tooltip::for_action_in(
6603 "Toggle Code Actions",
6604 &ToggleCodeActions {
6605 deployed_from: None,
6606 quick_launch: false,
6607 },
6608 &focus_handle,
6609 window,
6610 cx,
6611 )
6612 }
6613 })
6614 })
6615 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6616 window.focus(&editor.focus_handle(cx));
6617 editor.toggle_code_actions(
6618 &crate::actions::ToggleCodeActions {
6619 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6620 display_row,
6621 )),
6622 quick_launch: false,
6623 },
6624 window,
6625 cx,
6626 );
6627 }))
6628 .into_any_element()
6629 }
6630
6631 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6632 &self.context_menu
6633 }
6634
6635 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6636 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6637 cx.background_executor()
6638 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6639 .await;
6640
6641 let (start_buffer, start, _, end, newest_selection) = this
6642 .update(cx, |this, cx| {
6643 let newest_selection = this.selections.newest_anchor().clone();
6644 if newest_selection.head().diff_base_anchor.is_some() {
6645 return None;
6646 }
6647 let newest_selection_adjusted = this.selections.newest_adjusted(cx);
6648 let buffer = this.buffer.read(cx);
6649
6650 let (start_buffer, start) =
6651 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6652 let (end_buffer, end) =
6653 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6654
6655 Some((start_buffer, start, end_buffer, end, newest_selection))
6656 })?
6657 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6658 .context(
6659 "Expected selection to lie in a single buffer when refreshing code actions",
6660 )?;
6661 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6662 let providers = this.code_action_providers.clone();
6663 let tasks = this
6664 .code_action_providers
6665 .iter()
6666 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6667 .collect::<Vec<_>>();
6668 (providers, tasks)
6669 })?;
6670
6671 let mut actions = Vec::new();
6672 for (provider, provider_actions) in
6673 providers.into_iter().zip(future::join_all(tasks).await)
6674 {
6675 if let Some(provider_actions) = provider_actions.log_err() {
6676 actions.extend(provider_actions.into_iter().map(|action| {
6677 AvailableCodeAction {
6678 excerpt_id: newest_selection.start.excerpt_id,
6679 action,
6680 provider: provider.clone(),
6681 }
6682 }));
6683 }
6684 }
6685
6686 this.update(cx, |this, cx| {
6687 this.available_code_actions = if actions.is_empty() {
6688 None
6689 } else {
6690 Some((
6691 Location {
6692 buffer: start_buffer,
6693 range: start..end,
6694 },
6695 actions.into(),
6696 ))
6697 };
6698 cx.notify();
6699 })
6700 }));
6701 }
6702
6703 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6704 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6705 self.show_git_blame_inline = false;
6706
6707 self.show_git_blame_inline_delay_task =
6708 Some(cx.spawn_in(window, async move |this, cx| {
6709 cx.background_executor().timer(delay).await;
6710
6711 this.update(cx, |this, cx| {
6712 this.show_git_blame_inline = true;
6713 cx.notify();
6714 })
6715 .log_err();
6716 }));
6717 }
6718 }
6719
6720 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6721 let snapshot = self.snapshot(window, cx);
6722 let cursor = self.selections.newest::<Point>(cx).head();
6723 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6724 else {
6725 return;
6726 };
6727
6728 let Some(blame) = self.blame.as_ref() else {
6729 return;
6730 };
6731
6732 let row_info = RowInfo {
6733 buffer_id: Some(buffer.remote_id()),
6734 buffer_row: Some(point.row),
6735 ..Default::default()
6736 };
6737 let Some((buffer, blame_entry)) = blame
6738 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6739 .flatten()
6740 else {
6741 return;
6742 };
6743
6744 let anchor = self.selections.newest_anchor().head();
6745 let position = self.to_pixel_point(anchor, &snapshot, window);
6746 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6747 self.show_blame_popover(
6748 buffer,
6749 &blame_entry,
6750 position + last_bounds.origin,
6751 true,
6752 cx,
6753 );
6754 };
6755 }
6756
6757 fn show_blame_popover(
6758 &mut self,
6759 buffer: BufferId,
6760 blame_entry: &BlameEntry,
6761 position: gpui::Point<Pixels>,
6762 ignore_timeout: bool,
6763 cx: &mut Context<Self>,
6764 ) {
6765 if let Some(state) = &mut self.inline_blame_popover {
6766 state.hide_task.take();
6767 } else {
6768 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6769 let blame_entry = blame_entry.clone();
6770 let show_task = cx.spawn(async move |editor, cx| {
6771 if !ignore_timeout {
6772 cx.background_executor()
6773 .timer(std::time::Duration::from_millis(blame_popover_delay))
6774 .await;
6775 }
6776 editor
6777 .update(cx, |editor, cx| {
6778 editor.inline_blame_popover_show_task.take();
6779 let Some(blame) = editor.blame.as_ref() else {
6780 return;
6781 };
6782 let blame = blame.read(cx);
6783 let details = blame.details_for_entry(buffer, &blame_entry);
6784 let markdown = cx.new(|cx| {
6785 Markdown::new(
6786 details
6787 .as_ref()
6788 .map(|message| message.message.clone())
6789 .unwrap_or_default(),
6790 None,
6791 None,
6792 cx,
6793 )
6794 });
6795 editor.inline_blame_popover = Some(InlineBlamePopover {
6796 position,
6797 hide_task: None,
6798 popover_bounds: None,
6799 popover_state: InlineBlamePopoverState {
6800 scroll_handle: ScrollHandle::new(),
6801 commit_message: details,
6802 markdown,
6803 },
6804 keyboard_grace: ignore_timeout,
6805 });
6806 cx.notify();
6807 })
6808 .ok();
6809 });
6810 self.inline_blame_popover_show_task = Some(show_task);
6811 }
6812 }
6813
6814 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6815 self.inline_blame_popover_show_task.take();
6816 if let Some(state) = &mut self.inline_blame_popover {
6817 let hide_task = cx.spawn(async move |editor, cx| {
6818 cx.background_executor()
6819 .timer(std::time::Duration::from_millis(100))
6820 .await;
6821 editor
6822 .update(cx, |editor, cx| {
6823 editor.inline_blame_popover.take();
6824 cx.notify();
6825 })
6826 .ok();
6827 });
6828 state.hide_task = Some(hide_task);
6829 }
6830 }
6831
6832 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6833 if self.pending_rename.is_some() {
6834 return None;
6835 }
6836
6837 let provider = self.semantics_provider.clone()?;
6838 let buffer = self.buffer.read(cx);
6839 let newest_selection = self.selections.newest_anchor().clone();
6840 let cursor_position = newest_selection.head();
6841 let (cursor_buffer, cursor_buffer_position) =
6842 buffer.text_anchor_for_position(cursor_position, cx)?;
6843 let (tail_buffer, tail_buffer_position) =
6844 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6845 if cursor_buffer != tail_buffer {
6846 return None;
6847 }
6848
6849 let snapshot = cursor_buffer.read(cx).snapshot();
6850 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6851 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6852 if start_word_range != end_word_range {
6853 self.document_highlights_task.take();
6854 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6855 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6856 return None;
6857 }
6858
6859 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6860 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6861 cx.background_executor()
6862 .timer(Duration::from_millis(debounce))
6863 .await;
6864
6865 let highlights = if let Some(highlights) = cx
6866 .update(|cx| {
6867 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6868 })
6869 .ok()
6870 .flatten()
6871 {
6872 highlights.await.log_err()
6873 } else {
6874 None
6875 };
6876
6877 if let Some(highlights) = highlights {
6878 this.update(cx, |this, cx| {
6879 if this.pending_rename.is_some() {
6880 return;
6881 }
6882
6883 let buffer = this.buffer.read(cx);
6884 if buffer
6885 .text_anchor_for_position(cursor_position, cx)
6886 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6887 {
6888 return;
6889 }
6890
6891 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6892 let mut write_ranges = Vec::new();
6893 let mut read_ranges = Vec::new();
6894 for highlight in highlights {
6895 let buffer_id = cursor_buffer.read(cx).remote_id();
6896 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6897 {
6898 let start = highlight
6899 .range
6900 .start
6901 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6902 let end = highlight
6903 .range
6904 .end
6905 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6906 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6907 continue;
6908 }
6909
6910 let range = Anchor::range_in_buffer(excerpt_id, buffer_id, start..end);
6911 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6912 write_ranges.push(range);
6913 } else {
6914 read_ranges.push(range);
6915 }
6916 }
6917 }
6918
6919 this.highlight_background::<DocumentHighlightRead>(
6920 &read_ranges,
6921 |theme| theme.colors().editor_document_highlight_read_background,
6922 cx,
6923 );
6924 this.highlight_background::<DocumentHighlightWrite>(
6925 &write_ranges,
6926 |theme| theme.colors().editor_document_highlight_write_background,
6927 cx,
6928 );
6929 cx.notify();
6930 })
6931 .log_err();
6932 }
6933 }));
6934 None
6935 }
6936
6937 fn prepare_highlight_query_from_selection(
6938 &mut self,
6939 cx: &mut Context<Editor>,
6940 ) -> Option<(String, Range<Anchor>)> {
6941 if matches!(self.mode, EditorMode::SingleLine) {
6942 return None;
6943 }
6944 if !EditorSettings::get_global(cx).selection_highlight {
6945 return None;
6946 }
6947 if self.selections.count() != 1 || self.selections.line_mode() {
6948 return None;
6949 }
6950 let selection = self.selections.newest_anchor();
6951 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6952 let selection_point_range = selection.start.to_point(&multi_buffer_snapshot)
6953 ..selection.end.to_point(&multi_buffer_snapshot);
6954 // If the selection spans multiple rows OR it is empty
6955 if selection_point_range.start.row != selection_point_range.end.row
6956 || selection_point_range.start.column == selection_point_range.end.column
6957 {
6958 return None;
6959 }
6960
6961 let query = multi_buffer_snapshot
6962 .text_for_range(selection.range())
6963 .collect::<String>();
6964 if query.trim().is_empty() {
6965 return None;
6966 }
6967 Some((query, selection.range()))
6968 }
6969
6970 fn update_selection_occurrence_highlights(
6971 &mut self,
6972 query_text: String,
6973 query_range: Range<Anchor>,
6974 multi_buffer_range_to_query: Range<Point>,
6975 use_debounce: bool,
6976 window: &mut Window,
6977 cx: &mut Context<Editor>,
6978 ) -> Task<()> {
6979 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6980 cx.spawn_in(window, async move |editor, cx| {
6981 if use_debounce {
6982 cx.background_executor()
6983 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6984 .await;
6985 }
6986 let match_task = cx.background_spawn(async move {
6987 let buffer_ranges = multi_buffer_snapshot
6988 .range_to_buffer_ranges(multi_buffer_range_to_query)
6989 .into_iter()
6990 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6991 let mut match_ranges = Vec::new();
6992 let Ok(regex) = project::search::SearchQuery::text(
6993 query_text.clone(),
6994 false,
6995 false,
6996 false,
6997 Default::default(),
6998 Default::default(),
6999 false,
7000 None,
7001 ) else {
7002 return Vec::default();
7003 };
7004 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7005 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7006 match_ranges.extend(
7007 regex
7008 .search(buffer_snapshot, Some(search_range.clone()))
7009 .await
7010 .into_iter()
7011 .filter_map(|match_range| {
7012 let match_start = buffer_snapshot
7013 .anchor_after(search_range.start + match_range.start);
7014 let match_end = buffer_snapshot
7015 .anchor_before(search_range.start + match_range.end);
7016 let match_anchor_range = Anchor::range_in_buffer(
7017 excerpt_id,
7018 buffer_snapshot.remote_id(),
7019 match_start..match_end,
7020 );
7021 (match_anchor_range != query_range).then_some(match_anchor_range)
7022 }),
7023 );
7024 }
7025 match_ranges
7026 });
7027 let match_ranges = match_task.await;
7028 editor
7029 .update_in(cx, |editor, _, cx| {
7030 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7031 if !match_ranges.is_empty() {
7032 editor.highlight_background::<SelectedTextHighlight>(
7033 &match_ranges,
7034 |theme| theme.colors().editor_document_highlight_bracket_background,
7035 cx,
7036 )
7037 }
7038 })
7039 .log_err();
7040 })
7041 }
7042
7043 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7044 struct NewlineFold;
7045 let type_id = std::any::TypeId::of::<NewlineFold>();
7046 if !self.mode.is_single_line() {
7047 return;
7048 }
7049 let snapshot = self.snapshot(window, cx);
7050 if snapshot.buffer_snapshot().max_point().row == 0 {
7051 return;
7052 }
7053 let task = cx.background_spawn(async move {
7054 let new_newlines = snapshot
7055 .buffer_chars_at(0)
7056 .filter_map(|(c, i)| {
7057 if c == '\n' {
7058 Some(
7059 snapshot.buffer_snapshot().anchor_after(i)
7060 ..snapshot.buffer_snapshot().anchor_before(i + 1),
7061 )
7062 } else {
7063 None
7064 }
7065 })
7066 .collect::<Vec<_>>();
7067 let existing_newlines = snapshot
7068 .folds_in_range(0..snapshot.buffer_snapshot().len())
7069 .filter_map(|fold| {
7070 if fold.placeholder.type_tag == Some(type_id) {
7071 Some(fold.range.start..fold.range.end)
7072 } else {
7073 None
7074 }
7075 })
7076 .collect::<Vec<_>>();
7077
7078 (new_newlines, existing_newlines)
7079 });
7080 self.folding_newlines = cx.spawn(async move |this, cx| {
7081 let (new_newlines, existing_newlines) = task.await;
7082 if new_newlines == existing_newlines {
7083 return;
7084 }
7085 let placeholder = FoldPlaceholder {
7086 render: Arc::new(move |_, _, cx| {
7087 div()
7088 .bg(cx.theme().status().hint_background)
7089 .border_b_1()
7090 .size_full()
7091 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7092 .border_color(cx.theme().status().hint)
7093 .child("\\n")
7094 .into_any()
7095 }),
7096 constrain_width: false,
7097 merge_adjacent: false,
7098 type_tag: Some(type_id),
7099 };
7100 let creases = new_newlines
7101 .into_iter()
7102 .map(|range| Crease::simple(range, placeholder.clone()))
7103 .collect();
7104 this.update(cx, |this, cx| {
7105 this.display_map.update(cx, |display_map, cx| {
7106 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7107 display_map.fold(creases, cx);
7108 });
7109 })
7110 .ok();
7111 });
7112 }
7113
7114 fn refresh_selected_text_highlights(
7115 &mut self,
7116 on_buffer_edit: bool,
7117 window: &mut Window,
7118 cx: &mut Context<Editor>,
7119 ) {
7120 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
7121 else {
7122 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7123 self.quick_selection_highlight_task.take();
7124 self.debounced_selection_highlight_task.take();
7125 return;
7126 };
7127 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7128 if on_buffer_edit
7129 || self
7130 .quick_selection_highlight_task
7131 .as_ref()
7132 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7133 {
7134 let multi_buffer_visible_start = self
7135 .scroll_manager
7136 .anchor()
7137 .anchor
7138 .to_point(&multi_buffer_snapshot);
7139 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7140 multi_buffer_visible_start
7141 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7142 Bias::Left,
7143 );
7144 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7145 self.quick_selection_highlight_task = Some((
7146 query_range.clone(),
7147 self.update_selection_occurrence_highlights(
7148 query_text.clone(),
7149 query_range.clone(),
7150 multi_buffer_visible_range,
7151 false,
7152 window,
7153 cx,
7154 ),
7155 ));
7156 }
7157 if on_buffer_edit
7158 || self
7159 .debounced_selection_highlight_task
7160 .as_ref()
7161 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7162 {
7163 let multi_buffer_start = multi_buffer_snapshot
7164 .anchor_before(0)
7165 .to_point(&multi_buffer_snapshot);
7166 let multi_buffer_end = multi_buffer_snapshot
7167 .anchor_after(multi_buffer_snapshot.len())
7168 .to_point(&multi_buffer_snapshot);
7169 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7170 self.debounced_selection_highlight_task = Some((
7171 query_range.clone(),
7172 self.update_selection_occurrence_highlights(
7173 query_text,
7174 query_range,
7175 multi_buffer_full_range,
7176 true,
7177 window,
7178 cx,
7179 ),
7180 ));
7181 }
7182 }
7183
7184 pub fn refresh_edit_prediction(
7185 &mut self,
7186 debounce: bool,
7187 user_requested: bool,
7188 window: &mut Window,
7189 cx: &mut Context<Self>,
7190 ) -> Option<()> {
7191 if DisableAiSettings::get_global(cx).disable_ai {
7192 return None;
7193 }
7194
7195 let provider = self.edit_prediction_provider()?;
7196 let cursor = self.selections.newest_anchor().head();
7197 let (buffer, cursor_buffer_position) =
7198 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7199
7200 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7201 self.discard_edit_prediction(false, cx);
7202 return None;
7203 }
7204
7205 self.update_visible_edit_prediction(window, cx);
7206
7207 if !user_requested
7208 && (!self.should_show_edit_predictions()
7209 || !self.is_focused(window)
7210 || buffer.read(cx).is_empty())
7211 {
7212 self.discard_edit_prediction(false, cx);
7213 return None;
7214 }
7215
7216 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7217 Some(())
7218 }
7219
7220 fn show_edit_predictions_in_menu(&self) -> bool {
7221 match self.edit_prediction_settings {
7222 EditPredictionSettings::Disabled => false,
7223 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7224 }
7225 }
7226
7227 pub fn edit_predictions_enabled(&self) -> bool {
7228 match self.edit_prediction_settings {
7229 EditPredictionSettings::Disabled => false,
7230 EditPredictionSettings::Enabled { .. } => true,
7231 }
7232 }
7233
7234 fn edit_prediction_requires_modifier(&self) -> bool {
7235 match self.edit_prediction_settings {
7236 EditPredictionSettings::Disabled => false,
7237 EditPredictionSettings::Enabled {
7238 preview_requires_modifier,
7239 ..
7240 } => preview_requires_modifier,
7241 }
7242 }
7243
7244 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7245 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7246 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7247 self.discard_edit_prediction(false, cx);
7248 } else {
7249 let selection = self.selections.newest_anchor();
7250 let cursor = selection.head();
7251
7252 if let Some((buffer, cursor_buffer_position)) =
7253 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7254 {
7255 self.edit_prediction_settings =
7256 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7257 }
7258 }
7259 }
7260
7261 fn edit_prediction_settings_at_position(
7262 &self,
7263 buffer: &Entity<Buffer>,
7264 buffer_position: language::Anchor,
7265 cx: &App,
7266 ) -> EditPredictionSettings {
7267 if !self.mode.is_full()
7268 || !self.show_edit_predictions_override.unwrap_or(true)
7269 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7270 {
7271 return EditPredictionSettings::Disabled;
7272 }
7273
7274 let buffer = buffer.read(cx);
7275
7276 let file = buffer.file();
7277
7278 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7279 return EditPredictionSettings::Disabled;
7280 };
7281
7282 let by_provider = matches!(
7283 self.menu_edit_predictions_policy,
7284 MenuEditPredictionsPolicy::ByProvider
7285 );
7286
7287 let show_in_menu = by_provider
7288 && self
7289 .edit_prediction_provider
7290 .as_ref()
7291 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7292
7293 let preview_requires_modifier =
7294 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7295
7296 EditPredictionSettings::Enabled {
7297 show_in_menu,
7298 preview_requires_modifier,
7299 }
7300 }
7301
7302 fn should_show_edit_predictions(&self) -> bool {
7303 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7304 }
7305
7306 pub fn edit_prediction_preview_is_active(&self) -> bool {
7307 matches!(
7308 self.edit_prediction_preview,
7309 EditPredictionPreview::Active { .. }
7310 )
7311 }
7312
7313 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7314 let cursor = self.selections.newest_anchor().head();
7315 if let Some((buffer, cursor_position)) =
7316 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7317 {
7318 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7319 } else {
7320 false
7321 }
7322 }
7323
7324 pub fn supports_minimap(&self, cx: &App) -> bool {
7325 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7326 }
7327
7328 fn edit_predictions_enabled_in_buffer(
7329 &self,
7330 buffer: &Entity<Buffer>,
7331 buffer_position: language::Anchor,
7332 cx: &App,
7333 ) -> bool {
7334 maybe!({
7335 if self.read_only(cx) {
7336 return Some(false);
7337 }
7338 let provider = self.edit_prediction_provider()?;
7339 if !provider.is_enabled(buffer, buffer_position, cx) {
7340 return Some(false);
7341 }
7342 let buffer = buffer.read(cx);
7343 let Some(file) = buffer.file() else {
7344 return Some(true);
7345 };
7346 let settings = all_language_settings(Some(file), cx);
7347 Some(settings.edit_predictions_enabled_for_file(file, cx))
7348 })
7349 .unwrap_or(false)
7350 }
7351
7352 fn cycle_edit_prediction(
7353 &mut self,
7354 direction: Direction,
7355 window: &mut Window,
7356 cx: &mut Context<Self>,
7357 ) -> Option<()> {
7358 let provider = self.edit_prediction_provider()?;
7359 let cursor = self.selections.newest_anchor().head();
7360 let (buffer, cursor_buffer_position) =
7361 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7362 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7363 return None;
7364 }
7365
7366 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7367 self.update_visible_edit_prediction(window, cx);
7368
7369 Some(())
7370 }
7371
7372 pub fn show_edit_prediction(
7373 &mut self,
7374 _: &ShowEditPrediction,
7375 window: &mut Window,
7376 cx: &mut Context<Self>,
7377 ) {
7378 if !self.has_active_edit_prediction() {
7379 self.refresh_edit_prediction(false, true, window, cx);
7380 return;
7381 }
7382
7383 self.update_visible_edit_prediction(window, cx);
7384 }
7385
7386 pub fn display_cursor_names(
7387 &mut self,
7388 _: &DisplayCursorNames,
7389 window: &mut Window,
7390 cx: &mut Context<Self>,
7391 ) {
7392 self.show_cursor_names(window, cx);
7393 }
7394
7395 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7396 self.show_cursor_names = true;
7397 cx.notify();
7398 cx.spawn_in(window, async move |this, cx| {
7399 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7400 this.update(cx, |this, cx| {
7401 this.show_cursor_names = false;
7402 cx.notify()
7403 })
7404 .ok()
7405 })
7406 .detach();
7407 }
7408
7409 pub fn next_edit_prediction(
7410 &mut self,
7411 _: &NextEditPrediction,
7412 window: &mut Window,
7413 cx: &mut Context<Self>,
7414 ) {
7415 if self.has_active_edit_prediction() {
7416 self.cycle_edit_prediction(Direction::Next, window, cx);
7417 } else {
7418 let is_copilot_disabled = self
7419 .refresh_edit_prediction(false, true, window, cx)
7420 .is_none();
7421 if is_copilot_disabled {
7422 cx.propagate();
7423 }
7424 }
7425 }
7426
7427 pub fn previous_edit_prediction(
7428 &mut self,
7429 _: &PreviousEditPrediction,
7430 window: &mut Window,
7431 cx: &mut Context<Self>,
7432 ) {
7433 if self.has_active_edit_prediction() {
7434 self.cycle_edit_prediction(Direction::Prev, window, cx);
7435 } else {
7436 let is_copilot_disabled = self
7437 .refresh_edit_prediction(false, true, window, cx)
7438 .is_none();
7439 if is_copilot_disabled {
7440 cx.propagate();
7441 }
7442 }
7443 }
7444
7445 pub fn accept_edit_prediction(
7446 &mut self,
7447 _: &AcceptEditPrediction,
7448 window: &mut Window,
7449 cx: &mut Context<Self>,
7450 ) {
7451 if self.show_edit_predictions_in_menu() {
7452 self.hide_context_menu(window, cx);
7453 }
7454
7455 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7456 return;
7457 };
7458
7459 match &active_edit_prediction.completion {
7460 EditPrediction::MoveWithin { target, .. } => {
7461 let target = *target;
7462
7463 if let Some(position_map) = &self.last_position_map {
7464 if position_map
7465 .visible_row_range
7466 .contains(&target.to_display_point(&position_map.snapshot).row())
7467 || !self.edit_prediction_requires_modifier()
7468 {
7469 self.unfold_ranges(&[target..target], true, false, cx);
7470 // Note that this is also done in vim's handler of the Tab action.
7471 self.change_selections(
7472 SelectionEffects::scroll(Autoscroll::newest()),
7473 window,
7474 cx,
7475 |selections| {
7476 selections.select_anchor_ranges([target..target]);
7477 },
7478 );
7479 self.clear_row_highlights::<EditPredictionPreview>();
7480
7481 self.edit_prediction_preview
7482 .set_previous_scroll_position(None);
7483 } else {
7484 self.edit_prediction_preview
7485 .set_previous_scroll_position(Some(
7486 position_map.snapshot.scroll_anchor,
7487 ));
7488
7489 self.highlight_rows::<EditPredictionPreview>(
7490 target..target,
7491 cx.theme().colors().editor_highlighted_line_background,
7492 RowHighlightOptions {
7493 autoscroll: true,
7494 ..Default::default()
7495 },
7496 cx,
7497 );
7498 self.request_autoscroll(Autoscroll::fit(), cx);
7499 }
7500 }
7501 }
7502 EditPrediction::MoveOutside { snapshot, target } => {
7503 if let Some(workspace) = self.workspace() {
7504 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7505 .detach_and_log_err(cx);
7506 }
7507 }
7508 EditPrediction::Edit { edits, .. } => {
7509 self.report_edit_prediction_event(
7510 active_edit_prediction.completion_id.clone(),
7511 true,
7512 cx,
7513 );
7514
7515 if let Some(provider) = self.edit_prediction_provider() {
7516 provider.accept(cx);
7517 }
7518
7519 // Store the transaction ID and selections before applying the edit
7520 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7521
7522 let snapshot = self.buffer.read(cx).snapshot(cx);
7523 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7524
7525 self.buffer.update(cx, |buffer, cx| {
7526 buffer.edit(edits.iter().cloned(), None, cx)
7527 });
7528
7529 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7530 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7531 });
7532
7533 let selections = self.selections.disjoint_anchors_arc();
7534 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7535 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7536 if has_new_transaction {
7537 self.selection_history
7538 .insert_transaction(transaction_id_now, selections);
7539 }
7540 }
7541
7542 self.update_visible_edit_prediction(window, cx);
7543 if self.active_edit_prediction.is_none() {
7544 self.refresh_edit_prediction(true, true, window, cx);
7545 }
7546
7547 cx.notify();
7548 }
7549 }
7550
7551 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7552 }
7553
7554 pub fn accept_partial_edit_prediction(
7555 &mut self,
7556 _: &AcceptPartialEditPrediction,
7557 window: &mut Window,
7558 cx: &mut Context<Self>,
7559 ) {
7560 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7561 return;
7562 };
7563 if self.selections.count() != 1 {
7564 return;
7565 }
7566
7567 match &active_edit_prediction.completion {
7568 EditPrediction::MoveWithin { target, .. } => {
7569 let target = *target;
7570 self.change_selections(
7571 SelectionEffects::scroll(Autoscroll::newest()),
7572 window,
7573 cx,
7574 |selections| {
7575 selections.select_anchor_ranges([target..target]);
7576 },
7577 );
7578 }
7579 EditPrediction::MoveOutside { snapshot, target } => {
7580 if let Some(workspace) = self.workspace() {
7581 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7582 .detach_and_log_err(cx);
7583 }
7584 }
7585 EditPrediction::Edit { edits, .. } => {
7586 self.report_edit_prediction_event(
7587 active_edit_prediction.completion_id.clone(),
7588 true,
7589 cx,
7590 );
7591
7592 // Find an insertion that starts at the cursor position.
7593 let snapshot = self.buffer.read(cx).snapshot(cx);
7594 let cursor_offset = self.selections.newest::<usize>(cx).head();
7595 let insertion = edits.iter().find_map(|(range, text)| {
7596 let range = range.to_offset(&snapshot);
7597 if range.is_empty() && range.start == cursor_offset {
7598 Some(text)
7599 } else {
7600 None
7601 }
7602 });
7603
7604 if let Some(text) = insertion {
7605 let mut partial_completion = text
7606 .chars()
7607 .by_ref()
7608 .take_while(|c| c.is_alphabetic())
7609 .collect::<String>();
7610 if partial_completion.is_empty() {
7611 partial_completion = text
7612 .chars()
7613 .by_ref()
7614 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7615 .collect::<String>();
7616 }
7617
7618 cx.emit(EditorEvent::InputHandled {
7619 utf16_range_to_replace: None,
7620 text: partial_completion.clone().into(),
7621 });
7622
7623 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7624
7625 self.refresh_edit_prediction(true, true, window, cx);
7626 cx.notify();
7627 } else {
7628 self.accept_edit_prediction(&Default::default(), window, cx);
7629 }
7630 }
7631 }
7632 }
7633
7634 fn discard_edit_prediction(
7635 &mut self,
7636 should_report_edit_prediction_event: bool,
7637 cx: &mut Context<Self>,
7638 ) -> bool {
7639 if should_report_edit_prediction_event {
7640 let completion_id = self
7641 .active_edit_prediction
7642 .as_ref()
7643 .and_then(|active_completion| active_completion.completion_id.clone());
7644
7645 self.report_edit_prediction_event(completion_id, false, cx);
7646 }
7647
7648 if let Some(provider) = self.edit_prediction_provider() {
7649 provider.discard(cx);
7650 }
7651
7652 self.take_active_edit_prediction(cx)
7653 }
7654
7655 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7656 let Some(provider) = self.edit_prediction_provider() else {
7657 return;
7658 };
7659
7660 let Some((_, buffer, _)) = self
7661 .buffer
7662 .read(cx)
7663 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7664 else {
7665 return;
7666 };
7667
7668 let extension = buffer
7669 .read(cx)
7670 .file()
7671 .and_then(|file| Some(file.path().extension()?.to_string()));
7672
7673 let event_type = match accepted {
7674 true => "Edit Prediction Accepted",
7675 false => "Edit Prediction Discarded",
7676 };
7677 telemetry::event!(
7678 event_type,
7679 provider = provider.name(),
7680 prediction_id = id,
7681 suggestion_accepted = accepted,
7682 file_extension = extension,
7683 );
7684 }
7685
7686 fn open_editor_at_anchor(
7687 snapshot: &language::BufferSnapshot,
7688 target: language::Anchor,
7689 workspace: &Entity<Workspace>,
7690 window: &mut Window,
7691 cx: &mut App,
7692 ) -> Task<Result<()>> {
7693 workspace.update(cx, |workspace, cx| {
7694 let path = snapshot.file().map(|file| file.full_path(cx));
7695 let Some(path) =
7696 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7697 else {
7698 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7699 };
7700 let target = text::ToPoint::to_point(&target, snapshot);
7701 let item = workspace.open_path(path, None, true, window, cx);
7702 window.spawn(cx, async move |cx| {
7703 let Some(editor) = item.await?.downcast::<Editor>() else {
7704 return Ok(());
7705 };
7706 editor
7707 .update_in(cx, |editor, window, cx| {
7708 editor.go_to_singleton_buffer_point(target, window, cx);
7709 })
7710 .ok();
7711 anyhow::Ok(())
7712 })
7713 })
7714 }
7715
7716 pub fn has_active_edit_prediction(&self) -> bool {
7717 self.active_edit_prediction.is_some()
7718 }
7719
7720 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7721 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7722 return false;
7723 };
7724
7725 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7726 self.clear_highlights::<EditPredictionHighlight>(cx);
7727 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7728 true
7729 }
7730
7731 /// Returns true when we're displaying the edit prediction popover below the cursor
7732 /// like we are not previewing and the LSP autocomplete menu is visible
7733 /// or we are in `when_holding_modifier` mode.
7734 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7735 if self.edit_prediction_preview_is_active()
7736 || !self.show_edit_predictions_in_menu()
7737 || !self.edit_predictions_enabled()
7738 {
7739 return false;
7740 }
7741
7742 if self.has_visible_completions_menu() {
7743 return true;
7744 }
7745
7746 has_completion && self.edit_prediction_requires_modifier()
7747 }
7748
7749 fn handle_modifiers_changed(
7750 &mut self,
7751 modifiers: Modifiers,
7752 position_map: &PositionMap,
7753 window: &mut Window,
7754 cx: &mut Context<Self>,
7755 ) {
7756 if self.show_edit_predictions_in_menu() {
7757 self.update_edit_prediction_preview(&modifiers, window, cx);
7758 }
7759
7760 self.update_selection_mode(&modifiers, position_map, window, cx);
7761
7762 let mouse_position = window.mouse_position();
7763 if !position_map.text_hitbox.is_hovered(window) {
7764 return;
7765 }
7766
7767 self.update_hovered_link(
7768 position_map.point_for_position(mouse_position),
7769 &position_map.snapshot,
7770 modifiers,
7771 window,
7772 cx,
7773 )
7774 }
7775
7776 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7777 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7778 if invert {
7779 match multi_cursor_setting {
7780 MultiCursorModifier::Alt => modifiers.alt,
7781 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7782 }
7783 } else {
7784 match multi_cursor_setting {
7785 MultiCursorModifier::Alt => modifiers.secondary(),
7786 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7787 }
7788 }
7789 }
7790
7791 fn columnar_selection_mode(
7792 modifiers: &Modifiers,
7793 cx: &mut Context<Self>,
7794 ) -> Option<ColumnarMode> {
7795 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7796 if Self::multi_cursor_modifier(false, modifiers, cx) {
7797 Some(ColumnarMode::FromMouse)
7798 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7799 Some(ColumnarMode::FromSelection)
7800 } else {
7801 None
7802 }
7803 } else {
7804 None
7805 }
7806 }
7807
7808 fn update_selection_mode(
7809 &mut self,
7810 modifiers: &Modifiers,
7811 position_map: &PositionMap,
7812 window: &mut Window,
7813 cx: &mut Context<Self>,
7814 ) {
7815 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7816 return;
7817 };
7818 if self.selections.pending_anchor().is_none() {
7819 return;
7820 }
7821
7822 let mouse_position = window.mouse_position();
7823 let point_for_position = position_map.point_for_position(mouse_position);
7824 let position = point_for_position.previous_valid;
7825
7826 self.select(
7827 SelectPhase::BeginColumnar {
7828 position,
7829 reset: false,
7830 mode,
7831 goal_column: point_for_position.exact_unclipped.column(),
7832 },
7833 window,
7834 cx,
7835 );
7836 }
7837
7838 fn update_edit_prediction_preview(
7839 &mut self,
7840 modifiers: &Modifiers,
7841 window: &mut Window,
7842 cx: &mut Context<Self>,
7843 ) {
7844 let mut modifiers_held = false;
7845 if let Some(accept_keystroke) = self
7846 .accept_edit_prediction_keybind(false, window, cx)
7847 .keystroke()
7848 {
7849 modifiers_held = modifiers_held
7850 || (accept_keystroke.modifiers() == modifiers
7851 && accept_keystroke.modifiers().modified());
7852 };
7853 if let Some(accept_partial_keystroke) = self
7854 .accept_edit_prediction_keybind(true, window, cx)
7855 .keystroke()
7856 {
7857 modifiers_held = modifiers_held
7858 || (accept_partial_keystroke.modifiers() == modifiers
7859 && accept_partial_keystroke.modifiers().modified());
7860 }
7861
7862 if modifiers_held {
7863 if matches!(
7864 self.edit_prediction_preview,
7865 EditPredictionPreview::Inactive { .. }
7866 ) {
7867 self.edit_prediction_preview = EditPredictionPreview::Active {
7868 previous_scroll_position: None,
7869 since: Instant::now(),
7870 };
7871
7872 self.update_visible_edit_prediction(window, cx);
7873 cx.notify();
7874 }
7875 } else if let EditPredictionPreview::Active {
7876 previous_scroll_position,
7877 since,
7878 } = self.edit_prediction_preview
7879 {
7880 if let (Some(previous_scroll_position), Some(position_map)) =
7881 (previous_scroll_position, self.last_position_map.as_ref())
7882 {
7883 self.set_scroll_position(
7884 previous_scroll_position
7885 .scroll_position(&position_map.snapshot.display_snapshot),
7886 window,
7887 cx,
7888 );
7889 }
7890
7891 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7892 released_too_fast: since.elapsed() < Duration::from_millis(200),
7893 };
7894 self.clear_row_highlights::<EditPredictionPreview>();
7895 self.update_visible_edit_prediction(window, cx);
7896 cx.notify();
7897 }
7898 }
7899
7900 fn update_visible_edit_prediction(
7901 &mut self,
7902 _window: &mut Window,
7903 cx: &mut Context<Self>,
7904 ) -> Option<()> {
7905 if DisableAiSettings::get_global(cx).disable_ai {
7906 return None;
7907 }
7908
7909 if self.ime_transaction.is_some() {
7910 self.discard_edit_prediction(false, cx);
7911 return None;
7912 }
7913
7914 let selection = self.selections.newest_anchor();
7915 let cursor = selection.head();
7916 let multibuffer = self.buffer.read(cx).snapshot(cx);
7917 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7918 let excerpt_id = cursor.excerpt_id;
7919
7920 let show_in_menu = self.show_edit_predictions_in_menu();
7921 let completions_menu_has_precedence = !show_in_menu
7922 && (self.context_menu.borrow().is_some()
7923 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7924
7925 if completions_menu_has_precedence
7926 || !offset_selection.is_empty()
7927 || self
7928 .active_edit_prediction
7929 .as_ref()
7930 .is_some_and(|completion| {
7931 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7932 return false;
7933 };
7934 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7935 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7936 !invalidation_range.contains(&offset_selection.head())
7937 })
7938 {
7939 self.discard_edit_prediction(false, cx);
7940 return None;
7941 }
7942
7943 self.take_active_edit_prediction(cx);
7944 let Some(provider) = self.edit_prediction_provider() else {
7945 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7946 return None;
7947 };
7948
7949 let (buffer, cursor_buffer_position) =
7950 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7951
7952 self.edit_prediction_settings =
7953 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7954
7955 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7956
7957 if self.edit_prediction_indent_conflict {
7958 let cursor_point = cursor.to_point(&multibuffer);
7959
7960 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7961
7962 if let Some((_, indent)) = indents.iter().next()
7963 && indent.len == cursor_point.column
7964 {
7965 self.edit_prediction_indent_conflict = false;
7966 }
7967 }
7968
7969 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7970
7971 let (completion_id, edits, edit_preview) = match edit_prediction {
7972 edit_prediction::EditPrediction::Local {
7973 id,
7974 edits,
7975 edit_preview,
7976 } => (id, edits, edit_preview),
7977 edit_prediction::EditPrediction::Jump {
7978 id,
7979 snapshot,
7980 target,
7981 } => {
7982 self.stale_edit_prediction_in_menu = None;
7983 self.active_edit_prediction = Some(EditPredictionState {
7984 inlay_ids: vec![],
7985 completion: EditPrediction::MoveOutside { snapshot, target },
7986 completion_id: id,
7987 invalidation_range: None,
7988 });
7989 cx.notify();
7990 return Some(());
7991 }
7992 };
7993
7994 let edits = edits
7995 .into_iter()
7996 .flat_map(|(range, new_text)| {
7997 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7998 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7999 Some((start..end, new_text))
8000 })
8001 .collect::<Vec<_>>();
8002 if edits.is_empty() {
8003 return None;
8004 }
8005
8006 let first_edit_start = edits.first().unwrap().0.start;
8007 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8008 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8009
8010 let last_edit_end = edits.last().unwrap().0.end;
8011 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8012 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8013
8014 let cursor_row = cursor.to_point(&multibuffer).row;
8015
8016 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8017
8018 let mut inlay_ids = Vec::new();
8019 let invalidation_row_range;
8020 let move_invalidation_row_range = if cursor_row < edit_start_row {
8021 Some(cursor_row..edit_end_row)
8022 } else if cursor_row > edit_end_row {
8023 Some(edit_start_row..cursor_row)
8024 } else {
8025 None
8026 };
8027 let supports_jump = self
8028 .edit_prediction_provider
8029 .as_ref()
8030 .map(|provider| provider.provider.supports_jump_to_edit())
8031 .unwrap_or(true);
8032
8033 let is_move = supports_jump
8034 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8035 let completion = if is_move {
8036 invalidation_row_range =
8037 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8038 let target = first_edit_start;
8039 EditPrediction::MoveWithin { target, snapshot }
8040 } else {
8041 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8042 && !self.edit_predictions_hidden_for_vim_mode;
8043
8044 if show_completions_in_buffer {
8045 if edits
8046 .iter()
8047 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8048 {
8049 let mut inlays = Vec::new();
8050 for (range, new_text) in &edits {
8051 let inlay = Inlay::edit_prediction(
8052 post_inc(&mut self.next_inlay_id),
8053 range.start,
8054 new_text.as_str(),
8055 );
8056 inlay_ids.push(inlay.id);
8057 inlays.push(inlay);
8058 }
8059
8060 self.splice_inlays(&[], inlays, cx);
8061 } else {
8062 let background_color = cx.theme().status().deleted_background;
8063 self.highlight_text::<EditPredictionHighlight>(
8064 edits.iter().map(|(range, _)| range.clone()).collect(),
8065 HighlightStyle {
8066 background_color: Some(background_color),
8067 ..Default::default()
8068 },
8069 cx,
8070 );
8071 }
8072 }
8073
8074 invalidation_row_range = edit_start_row..edit_end_row;
8075
8076 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8077 if provider.show_tab_accept_marker() {
8078 EditDisplayMode::TabAccept
8079 } else {
8080 EditDisplayMode::Inline
8081 }
8082 } else {
8083 EditDisplayMode::DiffPopover
8084 };
8085
8086 EditPrediction::Edit {
8087 edits,
8088 edit_preview,
8089 display_mode,
8090 snapshot,
8091 }
8092 };
8093
8094 let invalidation_range = multibuffer
8095 .anchor_before(Point::new(invalidation_row_range.start, 0))
8096 ..multibuffer.anchor_after(Point::new(
8097 invalidation_row_range.end,
8098 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8099 ));
8100
8101 self.stale_edit_prediction_in_menu = None;
8102 self.active_edit_prediction = Some(EditPredictionState {
8103 inlay_ids,
8104 completion,
8105 completion_id,
8106 invalidation_range: Some(invalidation_range),
8107 });
8108
8109 cx.notify();
8110
8111 Some(())
8112 }
8113
8114 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8115 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8116 }
8117
8118 fn clear_tasks(&mut self) {
8119 self.tasks.clear()
8120 }
8121
8122 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8123 if self.tasks.insert(key, value).is_some() {
8124 // This case should hopefully be rare, but just in case...
8125 log::error!(
8126 "multiple different run targets found on a single line, only the last target will be rendered"
8127 )
8128 }
8129 }
8130
8131 /// Get all display points of breakpoints that will be rendered within editor
8132 ///
8133 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8134 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8135 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8136 fn active_breakpoints(
8137 &self,
8138 range: Range<DisplayRow>,
8139 window: &mut Window,
8140 cx: &mut Context<Self>,
8141 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8142 let mut breakpoint_display_points = HashMap::default();
8143
8144 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8145 return breakpoint_display_points;
8146 };
8147
8148 let snapshot = self.snapshot(window, cx);
8149
8150 let multi_buffer_snapshot = snapshot.display_snapshot.buffer_snapshot();
8151 let Some(project) = self.project() else {
8152 return breakpoint_display_points;
8153 };
8154
8155 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8156 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8157
8158 for (buffer_snapshot, range, excerpt_id) in
8159 multi_buffer_snapshot.range_to_buffer_ranges(range)
8160 {
8161 let Some(buffer) = project
8162 .read(cx)
8163 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8164 else {
8165 continue;
8166 };
8167 let breakpoints = breakpoint_store.read(cx).breakpoints(
8168 &buffer,
8169 Some(
8170 buffer_snapshot.anchor_before(range.start)
8171 ..buffer_snapshot.anchor_after(range.end),
8172 ),
8173 buffer_snapshot,
8174 cx,
8175 );
8176 for (breakpoint, state) in breakpoints {
8177 let multi_buffer_anchor =
8178 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8179 let position = multi_buffer_anchor
8180 .to_point(multi_buffer_snapshot)
8181 .to_display_point(&snapshot);
8182
8183 breakpoint_display_points.insert(
8184 position.row(),
8185 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8186 );
8187 }
8188 }
8189
8190 breakpoint_display_points
8191 }
8192
8193 fn breakpoint_context_menu(
8194 &self,
8195 anchor: Anchor,
8196 window: &mut Window,
8197 cx: &mut Context<Self>,
8198 ) -> Entity<ui::ContextMenu> {
8199 let weak_editor = cx.weak_entity();
8200 let focus_handle = self.focus_handle(cx);
8201
8202 let row = self
8203 .buffer
8204 .read(cx)
8205 .snapshot(cx)
8206 .summary_for_anchor::<Point>(&anchor)
8207 .row;
8208
8209 let breakpoint = self
8210 .breakpoint_at_row(row, window, cx)
8211 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8212
8213 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8214 "Edit Log Breakpoint"
8215 } else {
8216 "Set Log Breakpoint"
8217 };
8218
8219 let condition_breakpoint_msg = if breakpoint
8220 .as_ref()
8221 .is_some_and(|bp| bp.1.condition.is_some())
8222 {
8223 "Edit Condition Breakpoint"
8224 } else {
8225 "Set Condition Breakpoint"
8226 };
8227
8228 let hit_condition_breakpoint_msg = if breakpoint
8229 .as_ref()
8230 .is_some_and(|bp| bp.1.hit_condition.is_some())
8231 {
8232 "Edit Hit Condition Breakpoint"
8233 } else {
8234 "Set Hit Condition Breakpoint"
8235 };
8236
8237 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8238 "Unset Breakpoint"
8239 } else {
8240 "Set Breakpoint"
8241 };
8242
8243 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8244
8245 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8246 BreakpointState::Enabled => Some("Disable"),
8247 BreakpointState::Disabled => Some("Enable"),
8248 });
8249
8250 let (anchor, breakpoint) =
8251 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8252
8253 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8254 menu.on_blur_subscription(Subscription::new(|| {}))
8255 .context(focus_handle)
8256 .when(run_to_cursor, |this| {
8257 let weak_editor = weak_editor.clone();
8258 this.entry("Run to cursor", None, move |window, cx| {
8259 weak_editor
8260 .update(cx, |editor, cx| {
8261 editor.change_selections(
8262 SelectionEffects::no_scroll(),
8263 window,
8264 cx,
8265 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8266 );
8267 })
8268 .ok();
8269
8270 window.dispatch_action(Box::new(RunToCursor), cx);
8271 })
8272 .separator()
8273 })
8274 .when_some(toggle_state_msg, |this, msg| {
8275 this.entry(msg, None, {
8276 let weak_editor = weak_editor.clone();
8277 let breakpoint = breakpoint.clone();
8278 move |_window, cx| {
8279 weak_editor
8280 .update(cx, |this, cx| {
8281 this.edit_breakpoint_at_anchor(
8282 anchor,
8283 breakpoint.as_ref().clone(),
8284 BreakpointEditAction::InvertState,
8285 cx,
8286 );
8287 })
8288 .log_err();
8289 }
8290 })
8291 })
8292 .entry(set_breakpoint_msg, None, {
8293 let weak_editor = weak_editor.clone();
8294 let breakpoint = breakpoint.clone();
8295 move |_window, cx| {
8296 weak_editor
8297 .update(cx, |this, cx| {
8298 this.edit_breakpoint_at_anchor(
8299 anchor,
8300 breakpoint.as_ref().clone(),
8301 BreakpointEditAction::Toggle,
8302 cx,
8303 );
8304 })
8305 .log_err();
8306 }
8307 })
8308 .entry(log_breakpoint_msg, None, {
8309 let breakpoint = breakpoint.clone();
8310 let weak_editor = weak_editor.clone();
8311 move |window, cx| {
8312 weak_editor
8313 .update(cx, |this, cx| {
8314 this.add_edit_breakpoint_block(
8315 anchor,
8316 breakpoint.as_ref(),
8317 BreakpointPromptEditAction::Log,
8318 window,
8319 cx,
8320 );
8321 })
8322 .log_err();
8323 }
8324 })
8325 .entry(condition_breakpoint_msg, None, {
8326 let breakpoint = breakpoint.clone();
8327 let weak_editor = weak_editor.clone();
8328 move |window, cx| {
8329 weak_editor
8330 .update(cx, |this, cx| {
8331 this.add_edit_breakpoint_block(
8332 anchor,
8333 breakpoint.as_ref(),
8334 BreakpointPromptEditAction::Condition,
8335 window,
8336 cx,
8337 );
8338 })
8339 .log_err();
8340 }
8341 })
8342 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8343 weak_editor
8344 .update(cx, |this, cx| {
8345 this.add_edit_breakpoint_block(
8346 anchor,
8347 breakpoint.as_ref(),
8348 BreakpointPromptEditAction::HitCondition,
8349 window,
8350 cx,
8351 );
8352 })
8353 .log_err();
8354 })
8355 })
8356 }
8357
8358 fn render_breakpoint(
8359 &self,
8360 position: Anchor,
8361 row: DisplayRow,
8362 breakpoint: &Breakpoint,
8363 state: Option<BreakpointSessionState>,
8364 cx: &mut Context<Self>,
8365 ) -> IconButton {
8366 let is_rejected = state.is_some_and(|s| !s.verified);
8367 // Is it a breakpoint that shows up when hovering over gutter?
8368 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8369 (false, false),
8370 |PhantomBreakpointIndicator {
8371 is_active,
8372 display_row,
8373 collides_with_existing_breakpoint,
8374 }| {
8375 (
8376 is_active && display_row == row,
8377 collides_with_existing_breakpoint,
8378 )
8379 },
8380 );
8381
8382 let (color, icon) = {
8383 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8384 (false, false) => ui::IconName::DebugBreakpoint,
8385 (true, false) => ui::IconName::DebugLogBreakpoint,
8386 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8387 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8388 };
8389
8390 let color = if is_phantom {
8391 Color::Hint
8392 } else if is_rejected {
8393 Color::Disabled
8394 } else {
8395 Color::Debugger
8396 };
8397
8398 (color, icon)
8399 };
8400
8401 let breakpoint = Arc::from(breakpoint.clone());
8402
8403 let alt_as_text = gpui::Keystroke {
8404 modifiers: Modifiers::secondary_key(),
8405 ..Default::default()
8406 };
8407 let primary_action_text = if breakpoint.is_disabled() {
8408 "Enable breakpoint"
8409 } else if is_phantom && !collides_with_existing {
8410 "Set breakpoint"
8411 } else {
8412 "Unset breakpoint"
8413 };
8414 let focus_handle = self.focus_handle.clone();
8415
8416 let meta = if is_rejected {
8417 SharedString::from("No executable code is associated with this line.")
8418 } else if collides_with_existing && !breakpoint.is_disabled() {
8419 SharedString::from(format!(
8420 "{alt_as_text}-click to disable,\nright-click for more options."
8421 ))
8422 } else {
8423 SharedString::from("Right-click for more options.")
8424 };
8425 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8426 .icon_size(IconSize::XSmall)
8427 .size(ui::ButtonSize::None)
8428 .when(is_rejected, |this| {
8429 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8430 })
8431 .icon_color(color)
8432 .style(ButtonStyle::Transparent)
8433 .on_click(cx.listener({
8434 move |editor, event: &ClickEvent, window, cx| {
8435 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8436 BreakpointEditAction::InvertState
8437 } else {
8438 BreakpointEditAction::Toggle
8439 };
8440
8441 window.focus(&editor.focus_handle(cx));
8442 editor.edit_breakpoint_at_anchor(
8443 position,
8444 breakpoint.as_ref().clone(),
8445 edit_action,
8446 cx,
8447 );
8448 }
8449 }))
8450 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8451 editor.set_breakpoint_context_menu(
8452 row,
8453 Some(position),
8454 event.position(),
8455 window,
8456 cx,
8457 );
8458 }))
8459 .tooltip(move |window, cx| {
8460 Tooltip::with_meta_in(
8461 primary_action_text,
8462 Some(&ToggleBreakpoint),
8463 meta.clone(),
8464 &focus_handle,
8465 window,
8466 cx,
8467 )
8468 })
8469 }
8470
8471 fn build_tasks_context(
8472 project: &Entity<Project>,
8473 buffer: &Entity<Buffer>,
8474 buffer_row: u32,
8475 tasks: &Arc<RunnableTasks>,
8476 cx: &mut Context<Self>,
8477 ) -> Task<Option<task::TaskContext>> {
8478 let position = Point::new(buffer_row, tasks.column);
8479 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8480 let location = Location {
8481 buffer: buffer.clone(),
8482 range: range_start..range_start,
8483 };
8484 // Fill in the environmental variables from the tree-sitter captures
8485 let mut captured_task_variables = TaskVariables::default();
8486 for (capture_name, value) in tasks.extra_variables.clone() {
8487 captured_task_variables.insert(
8488 task::VariableName::Custom(capture_name.into()),
8489 value.clone(),
8490 );
8491 }
8492 project.update(cx, |project, cx| {
8493 project.task_store().update(cx, |task_store, cx| {
8494 task_store.task_context_for_location(captured_task_variables, location, cx)
8495 })
8496 })
8497 }
8498
8499 pub fn spawn_nearest_task(
8500 &mut self,
8501 action: &SpawnNearestTask,
8502 window: &mut Window,
8503 cx: &mut Context<Self>,
8504 ) {
8505 let Some((workspace, _)) = self.workspace.clone() else {
8506 return;
8507 };
8508 let Some(project) = self.project.clone() else {
8509 return;
8510 };
8511
8512 // Try to find a closest, enclosing node using tree-sitter that has a task
8513 let Some((buffer, buffer_row, tasks)) = self
8514 .find_enclosing_node_task(cx)
8515 // Or find the task that's closest in row-distance.
8516 .or_else(|| self.find_closest_task(cx))
8517 else {
8518 return;
8519 };
8520
8521 let reveal_strategy = action.reveal;
8522 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8523 cx.spawn_in(window, async move |_, cx| {
8524 let context = task_context.await?;
8525 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8526
8527 let resolved = &mut resolved_task.resolved;
8528 resolved.reveal = reveal_strategy;
8529
8530 workspace
8531 .update_in(cx, |workspace, window, cx| {
8532 workspace.schedule_resolved_task(
8533 task_source_kind,
8534 resolved_task,
8535 false,
8536 window,
8537 cx,
8538 );
8539 })
8540 .ok()
8541 })
8542 .detach();
8543 }
8544
8545 fn find_closest_task(
8546 &mut self,
8547 cx: &mut Context<Self>,
8548 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8549 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8550
8551 let ((buffer_id, row), tasks) = self
8552 .tasks
8553 .iter()
8554 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8555
8556 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8557 let tasks = Arc::new(tasks.to_owned());
8558 Some((buffer, *row, tasks))
8559 }
8560
8561 fn find_enclosing_node_task(
8562 &mut self,
8563 cx: &mut Context<Self>,
8564 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8565 let snapshot = self.buffer.read(cx).snapshot(cx);
8566 let offset = self.selections.newest::<usize>(cx).head();
8567 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8568 let buffer_id = excerpt.buffer().remote_id();
8569
8570 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8571 let mut cursor = layer.node().walk();
8572
8573 while cursor.goto_first_child_for_byte(offset).is_some() {
8574 if cursor.node().end_byte() == offset {
8575 cursor.goto_next_sibling();
8576 }
8577 }
8578
8579 // Ascend to the smallest ancestor that contains the range and has a task.
8580 loop {
8581 let node = cursor.node();
8582 let node_range = node.byte_range();
8583 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8584
8585 // Check if this node contains our offset
8586 if node_range.start <= offset && node_range.end >= offset {
8587 // If it contains offset, check for task
8588 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8589 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8590 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8591 }
8592 }
8593
8594 if !cursor.goto_parent() {
8595 break;
8596 }
8597 }
8598 None
8599 }
8600
8601 fn render_run_indicator(
8602 &self,
8603 _style: &EditorStyle,
8604 is_active: bool,
8605 row: DisplayRow,
8606 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8607 cx: &mut Context<Self>,
8608 ) -> IconButton {
8609 let color = Color::Muted;
8610 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8611
8612 IconButton::new(
8613 ("run_indicator", row.0 as usize),
8614 ui::IconName::PlayOutlined,
8615 )
8616 .shape(ui::IconButtonShape::Square)
8617 .icon_size(IconSize::XSmall)
8618 .icon_color(color)
8619 .toggle_state(is_active)
8620 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8621 let quick_launch = match e {
8622 ClickEvent::Keyboard(_) => true,
8623 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8624 };
8625
8626 window.focus(&editor.focus_handle(cx));
8627 editor.toggle_code_actions(
8628 &ToggleCodeActions {
8629 deployed_from: Some(CodeActionSource::RunMenu(row)),
8630 quick_launch,
8631 },
8632 window,
8633 cx,
8634 );
8635 }))
8636 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8637 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8638 }))
8639 }
8640
8641 pub fn context_menu_visible(&self) -> bool {
8642 !self.edit_prediction_preview_is_active()
8643 && self
8644 .context_menu
8645 .borrow()
8646 .as_ref()
8647 .is_some_and(|menu| menu.visible())
8648 }
8649
8650 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8651 self.context_menu
8652 .borrow()
8653 .as_ref()
8654 .map(|menu| menu.origin())
8655 }
8656
8657 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8658 self.context_menu_options = Some(options);
8659 }
8660
8661 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8662 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8663
8664 fn render_edit_prediction_popover(
8665 &mut self,
8666 text_bounds: &Bounds<Pixels>,
8667 content_origin: gpui::Point<Pixels>,
8668 right_margin: Pixels,
8669 editor_snapshot: &EditorSnapshot,
8670 visible_row_range: Range<DisplayRow>,
8671 scroll_top: ScrollOffset,
8672 scroll_bottom: ScrollOffset,
8673 line_layouts: &[LineWithInvisibles],
8674 line_height: Pixels,
8675 scroll_position: gpui::Point<ScrollOffset>,
8676 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8677 newest_selection_head: Option<DisplayPoint>,
8678 editor_width: Pixels,
8679 style: &EditorStyle,
8680 window: &mut Window,
8681 cx: &mut App,
8682 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8683 if self.mode().is_minimap() {
8684 return None;
8685 }
8686 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8687
8688 if self.edit_prediction_visible_in_cursor_popover(true) {
8689 return None;
8690 }
8691
8692 match &active_edit_prediction.completion {
8693 EditPrediction::MoveWithin { target, .. } => {
8694 let target_display_point = target.to_display_point(editor_snapshot);
8695
8696 if self.edit_prediction_requires_modifier() {
8697 if !self.edit_prediction_preview_is_active() {
8698 return None;
8699 }
8700
8701 self.render_edit_prediction_modifier_jump_popover(
8702 text_bounds,
8703 content_origin,
8704 visible_row_range,
8705 line_layouts,
8706 line_height,
8707 scroll_pixel_position,
8708 newest_selection_head,
8709 target_display_point,
8710 window,
8711 cx,
8712 )
8713 } else {
8714 self.render_edit_prediction_eager_jump_popover(
8715 text_bounds,
8716 content_origin,
8717 editor_snapshot,
8718 visible_row_range,
8719 scroll_top,
8720 scroll_bottom,
8721 line_height,
8722 scroll_pixel_position,
8723 target_display_point,
8724 editor_width,
8725 window,
8726 cx,
8727 )
8728 }
8729 }
8730 EditPrediction::Edit {
8731 display_mode: EditDisplayMode::Inline,
8732 ..
8733 } => None,
8734 EditPrediction::Edit {
8735 display_mode: EditDisplayMode::TabAccept,
8736 edits,
8737 ..
8738 } => {
8739 let range = &edits.first()?.0;
8740 let target_display_point = range.end.to_display_point(editor_snapshot);
8741
8742 self.render_edit_prediction_end_of_line_popover(
8743 "Accept",
8744 editor_snapshot,
8745 visible_row_range,
8746 target_display_point,
8747 line_height,
8748 scroll_pixel_position,
8749 content_origin,
8750 editor_width,
8751 window,
8752 cx,
8753 )
8754 }
8755 EditPrediction::Edit {
8756 edits,
8757 edit_preview,
8758 display_mode: EditDisplayMode::DiffPopover,
8759 snapshot,
8760 } => self.render_edit_prediction_diff_popover(
8761 text_bounds,
8762 content_origin,
8763 right_margin,
8764 editor_snapshot,
8765 visible_row_range,
8766 line_layouts,
8767 line_height,
8768 scroll_position,
8769 scroll_pixel_position,
8770 newest_selection_head,
8771 editor_width,
8772 style,
8773 edits,
8774 edit_preview,
8775 snapshot,
8776 window,
8777 cx,
8778 ),
8779 EditPrediction::MoveOutside { snapshot, .. } => {
8780 let file_name = snapshot
8781 .file()
8782 .map(|file| file.file_name(cx))
8783 .unwrap_or("untitled");
8784 let mut element = self
8785 .render_edit_prediction_line_popover(
8786 format!("Jump to {file_name}"),
8787 Some(IconName::ZedPredict),
8788 window,
8789 cx,
8790 )
8791 .into_any();
8792
8793 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8794 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8795 let origin_y = text_bounds.size.height - size.height - px(30.);
8796 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8797 element.prepaint_at(origin, window, cx);
8798
8799 Some((element, origin))
8800 }
8801 }
8802 }
8803
8804 fn render_edit_prediction_modifier_jump_popover(
8805 &mut self,
8806 text_bounds: &Bounds<Pixels>,
8807 content_origin: gpui::Point<Pixels>,
8808 visible_row_range: Range<DisplayRow>,
8809 line_layouts: &[LineWithInvisibles],
8810 line_height: Pixels,
8811 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8812 newest_selection_head: Option<DisplayPoint>,
8813 target_display_point: DisplayPoint,
8814 window: &mut Window,
8815 cx: &mut App,
8816 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8817 let scrolled_content_origin =
8818 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8819
8820 const SCROLL_PADDING_Y: Pixels = px(12.);
8821
8822 if target_display_point.row() < visible_row_range.start {
8823 return self.render_edit_prediction_scroll_popover(
8824 |_| SCROLL_PADDING_Y,
8825 IconName::ArrowUp,
8826 visible_row_range,
8827 line_layouts,
8828 newest_selection_head,
8829 scrolled_content_origin,
8830 window,
8831 cx,
8832 );
8833 } else if target_display_point.row() >= visible_row_range.end {
8834 return self.render_edit_prediction_scroll_popover(
8835 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8836 IconName::ArrowDown,
8837 visible_row_range,
8838 line_layouts,
8839 newest_selection_head,
8840 scrolled_content_origin,
8841 window,
8842 cx,
8843 );
8844 }
8845
8846 const POLE_WIDTH: Pixels = px(2.);
8847
8848 let line_layout =
8849 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8850 let target_column = target_display_point.column() as usize;
8851
8852 let target_x = line_layout.x_for_index(target_column);
8853 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8854 - scroll_pixel_position.y;
8855
8856 let flag_on_right = target_x < text_bounds.size.width / 2.;
8857
8858 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8859 border_color.l += 0.001;
8860
8861 let mut element = v_flex()
8862 .items_end()
8863 .when(flag_on_right, |el| el.items_start())
8864 .child(if flag_on_right {
8865 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8866 .rounded_bl(px(0.))
8867 .rounded_tl(px(0.))
8868 .border_l_2()
8869 .border_color(border_color)
8870 } else {
8871 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8872 .rounded_br(px(0.))
8873 .rounded_tr(px(0.))
8874 .border_r_2()
8875 .border_color(border_color)
8876 })
8877 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8878 .into_any();
8879
8880 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8881
8882 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8883 - point(
8884 if flag_on_right {
8885 POLE_WIDTH
8886 } else {
8887 size.width - POLE_WIDTH
8888 },
8889 size.height - line_height,
8890 );
8891
8892 origin.x = origin.x.max(content_origin.x);
8893
8894 element.prepaint_at(origin, window, cx);
8895
8896 Some((element, origin))
8897 }
8898
8899 fn render_edit_prediction_scroll_popover(
8900 &mut self,
8901 to_y: impl Fn(Size<Pixels>) -> Pixels,
8902 scroll_icon: IconName,
8903 visible_row_range: Range<DisplayRow>,
8904 line_layouts: &[LineWithInvisibles],
8905 newest_selection_head: Option<DisplayPoint>,
8906 scrolled_content_origin: gpui::Point<Pixels>,
8907 window: &mut Window,
8908 cx: &mut App,
8909 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8910 let mut element = self
8911 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8912 .into_any();
8913
8914 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8915
8916 let cursor = newest_selection_head?;
8917 let cursor_row_layout =
8918 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8919 let cursor_column = cursor.column() as usize;
8920
8921 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8922
8923 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8924
8925 element.prepaint_at(origin, window, cx);
8926 Some((element, origin))
8927 }
8928
8929 fn render_edit_prediction_eager_jump_popover(
8930 &mut self,
8931 text_bounds: &Bounds<Pixels>,
8932 content_origin: gpui::Point<Pixels>,
8933 editor_snapshot: &EditorSnapshot,
8934 visible_row_range: Range<DisplayRow>,
8935 scroll_top: ScrollOffset,
8936 scroll_bottom: ScrollOffset,
8937 line_height: Pixels,
8938 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8939 target_display_point: DisplayPoint,
8940 editor_width: Pixels,
8941 window: &mut Window,
8942 cx: &mut App,
8943 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8944 if target_display_point.row().as_f64() < scroll_top {
8945 let mut element = self
8946 .render_edit_prediction_line_popover(
8947 "Jump to Edit",
8948 Some(IconName::ArrowUp),
8949 window,
8950 cx,
8951 )
8952 .into_any();
8953
8954 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8955 let offset = point(
8956 (text_bounds.size.width - size.width) / 2.,
8957 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8958 );
8959
8960 let origin = text_bounds.origin + offset;
8961 element.prepaint_at(origin, window, cx);
8962 Some((element, origin))
8963 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8964 let mut element = self
8965 .render_edit_prediction_line_popover(
8966 "Jump to Edit",
8967 Some(IconName::ArrowDown),
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 text_bounds.size.height - size.height - 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 {
8983 self.render_edit_prediction_end_of_line_popover(
8984 "Jump to Edit",
8985 editor_snapshot,
8986 visible_row_range,
8987 target_display_point,
8988 line_height,
8989 scroll_pixel_position,
8990 content_origin,
8991 editor_width,
8992 window,
8993 cx,
8994 )
8995 }
8996 }
8997
8998 fn render_edit_prediction_end_of_line_popover(
8999 self: &mut Editor,
9000 label: &'static str,
9001 editor_snapshot: &EditorSnapshot,
9002 visible_row_range: Range<DisplayRow>,
9003 target_display_point: DisplayPoint,
9004 line_height: Pixels,
9005 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9006 content_origin: gpui::Point<Pixels>,
9007 editor_width: Pixels,
9008 window: &mut Window,
9009 cx: &mut App,
9010 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9011 let target_line_end = DisplayPoint::new(
9012 target_display_point.row(),
9013 editor_snapshot.line_len(target_display_point.row()),
9014 );
9015
9016 let mut element = self
9017 .render_edit_prediction_line_popover(label, None, window, cx)
9018 .into_any();
9019
9020 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9021
9022 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
9023
9024 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9025 let mut origin = start_point
9026 + line_origin
9027 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9028 origin.x = origin.x.max(content_origin.x);
9029
9030 let max_x = content_origin.x + editor_width - size.width;
9031
9032 if origin.x > max_x {
9033 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9034
9035 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9036 origin.y += offset;
9037 IconName::ArrowUp
9038 } else {
9039 origin.y -= offset;
9040 IconName::ArrowDown
9041 };
9042
9043 element = self
9044 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9045 .into_any();
9046
9047 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9048
9049 origin.x = content_origin.x + editor_width - size.width - px(2.);
9050 }
9051
9052 element.prepaint_at(origin, window, cx);
9053 Some((element, origin))
9054 }
9055
9056 fn render_edit_prediction_diff_popover(
9057 self: &Editor,
9058 text_bounds: &Bounds<Pixels>,
9059 content_origin: gpui::Point<Pixels>,
9060 right_margin: Pixels,
9061 editor_snapshot: &EditorSnapshot,
9062 visible_row_range: Range<DisplayRow>,
9063 line_layouts: &[LineWithInvisibles],
9064 line_height: Pixels,
9065 scroll_position: gpui::Point<ScrollOffset>,
9066 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9067 newest_selection_head: Option<DisplayPoint>,
9068 editor_width: Pixels,
9069 style: &EditorStyle,
9070 edits: &Vec<(Range<Anchor>, String)>,
9071 edit_preview: &Option<language::EditPreview>,
9072 snapshot: &language::BufferSnapshot,
9073 window: &mut Window,
9074 cx: &mut App,
9075 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9076 let edit_start = edits
9077 .first()
9078 .unwrap()
9079 .0
9080 .start
9081 .to_display_point(editor_snapshot);
9082 let edit_end = edits
9083 .last()
9084 .unwrap()
9085 .0
9086 .end
9087 .to_display_point(editor_snapshot);
9088
9089 let is_visible = visible_row_range.contains(&edit_start.row())
9090 || visible_row_range.contains(&edit_end.row());
9091 if !is_visible {
9092 return None;
9093 }
9094
9095 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9096 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9097 } else {
9098 // Fallback for providers without edit_preview
9099 crate::edit_prediction_fallback_text(edits, cx)
9100 };
9101
9102 let styled_text = highlighted_edits.to_styled_text(&style.text);
9103 let line_count = highlighted_edits.text.lines().count();
9104
9105 const BORDER_WIDTH: Pixels = px(1.);
9106
9107 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9108 let has_keybind = keybind.is_some();
9109
9110 let mut element = h_flex()
9111 .items_start()
9112 .child(
9113 h_flex()
9114 .bg(cx.theme().colors().editor_background)
9115 .border(BORDER_WIDTH)
9116 .shadow_xs()
9117 .border_color(cx.theme().colors().border)
9118 .rounded_l_lg()
9119 .when(line_count > 1, |el| el.rounded_br_lg())
9120 .pr_1()
9121 .child(styled_text),
9122 )
9123 .child(
9124 h_flex()
9125 .h(line_height + BORDER_WIDTH * 2.)
9126 .px_1p5()
9127 .gap_1()
9128 // Workaround: For some reason, there's a gap if we don't do this
9129 .ml(-BORDER_WIDTH)
9130 .shadow(vec![gpui::BoxShadow {
9131 color: gpui::black().opacity(0.05),
9132 offset: point(px(1.), px(1.)),
9133 blur_radius: px(2.),
9134 spread_radius: px(0.),
9135 }])
9136 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9137 .border(BORDER_WIDTH)
9138 .border_color(cx.theme().colors().border)
9139 .rounded_r_lg()
9140 .id("edit_prediction_diff_popover_keybind")
9141 .when(!has_keybind, |el| {
9142 let status_colors = cx.theme().status();
9143
9144 el.bg(status_colors.error_background)
9145 .border_color(status_colors.error.opacity(0.6))
9146 .child(Icon::new(IconName::Info).color(Color::Error))
9147 .cursor_default()
9148 .hoverable_tooltip(move |_window, cx| {
9149 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9150 })
9151 })
9152 .children(keybind),
9153 )
9154 .into_any();
9155
9156 let longest_row =
9157 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9158 let longest_line_width = if visible_row_range.contains(&longest_row) {
9159 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9160 } else {
9161 layout_line(
9162 longest_row,
9163 editor_snapshot,
9164 style,
9165 editor_width,
9166 |_| false,
9167 window,
9168 cx,
9169 )
9170 .width
9171 };
9172
9173 let viewport_bounds =
9174 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9175 right: -right_margin,
9176 ..Default::default()
9177 });
9178
9179 let x_after_longest = Pixels::from(
9180 ScrollPixelOffset::from(
9181 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9182 ) - scroll_pixel_position.x,
9183 );
9184
9185 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9186
9187 // Fully visible if it can be displayed within the window (allow overlapping other
9188 // panes). However, this is only allowed if the popover starts within text_bounds.
9189 let can_position_to_the_right = x_after_longest < text_bounds.right()
9190 && x_after_longest + element_bounds.width < viewport_bounds.right();
9191
9192 let mut origin = if can_position_to_the_right {
9193 point(
9194 x_after_longest,
9195 text_bounds.origin.y
9196 + Pixels::from(
9197 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9198 - scroll_pixel_position.y,
9199 ),
9200 )
9201 } else {
9202 let cursor_row = newest_selection_head.map(|head| head.row());
9203 let above_edit = edit_start
9204 .row()
9205 .0
9206 .checked_sub(line_count as u32)
9207 .map(DisplayRow);
9208 let below_edit = Some(edit_end.row() + 1);
9209 let above_cursor =
9210 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9211 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9212
9213 // Place the edit popover adjacent to the edit if there is a location
9214 // available that is onscreen and does not obscure the cursor. Otherwise,
9215 // place it adjacent to the cursor.
9216 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9217 .into_iter()
9218 .flatten()
9219 .find(|&start_row| {
9220 let end_row = start_row + line_count as u32;
9221 visible_row_range.contains(&start_row)
9222 && visible_row_range.contains(&end_row)
9223 && cursor_row
9224 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9225 })?;
9226
9227 content_origin
9228 + point(
9229 Pixels::from(-scroll_pixel_position.x),
9230 Pixels::from(
9231 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9232 ),
9233 )
9234 };
9235
9236 origin.x -= BORDER_WIDTH;
9237
9238 window.defer_draw(element, origin, 1);
9239
9240 // Do not return an element, since it will already be drawn due to defer_draw.
9241 None
9242 }
9243
9244 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9245 px(30.)
9246 }
9247
9248 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9249 if self.read_only(cx) {
9250 cx.theme().players().read_only()
9251 } else {
9252 self.style.as_ref().unwrap().local_player
9253 }
9254 }
9255
9256 fn render_edit_prediction_accept_keybind(
9257 &self,
9258 window: &mut Window,
9259 cx: &App,
9260 ) -> Option<AnyElement> {
9261 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9262 let accept_keystroke = accept_binding.keystroke()?;
9263
9264 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9265
9266 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9267 Color::Accent
9268 } else {
9269 Color::Muted
9270 };
9271
9272 h_flex()
9273 .px_0p5()
9274 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9275 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9276 .text_size(TextSize::XSmall.rems(cx))
9277 .child(h_flex().children(ui::render_modifiers(
9278 accept_keystroke.modifiers(),
9279 PlatformStyle::platform(),
9280 Some(modifiers_color),
9281 Some(IconSize::XSmall.rems().into()),
9282 true,
9283 )))
9284 .when(is_platform_style_mac, |parent| {
9285 parent.child(accept_keystroke.key().to_string())
9286 })
9287 .when(!is_platform_style_mac, |parent| {
9288 parent.child(
9289 Key::new(
9290 util::capitalize(accept_keystroke.key()),
9291 Some(Color::Default),
9292 )
9293 .size(Some(IconSize::XSmall.rems().into())),
9294 )
9295 })
9296 .into_any()
9297 .into()
9298 }
9299
9300 fn render_edit_prediction_line_popover(
9301 &self,
9302 label: impl Into<SharedString>,
9303 icon: Option<IconName>,
9304 window: &mut Window,
9305 cx: &App,
9306 ) -> Stateful<Div> {
9307 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9308
9309 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9310 let has_keybind = keybind.is_some();
9311
9312 h_flex()
9313 .id("ep-line-popover")
9314 .py_0p5()
9315 .pl_1()
9316 .pr(padding_right)
9317 .gap_1()
9318 .rounded_md()
9319 .border_1()
9320 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9321 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9322 .shadow_xs()
9323 .when(!has_keybind, |el| {
9324 let status_colors = cx.theme().status();
9325
9326 el.bg(status_colors.error_background)
9327 .border_color(status_colors.error.opacity(0.6))
9328 .pl_2()
9329 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9330 .cursor_default()
9331 .hoverable_tooltip(move |_window, cx| {
9332 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9333 })
9334 })
9335 .children(keybind)
9336 .child(
9337 Label::new(label)
9338 .size(LabelSize::Small)
9339 .when(!has_keybind, |el| {
9340 el.color(cx.theme().status().error.into()).strikethrough()
9341 }),
9342 )
9343 .when(!has_keybind, |el| {
9344 el.child(
9345 h_flex().ml_1().child(
9346 Icon::new(IconName::Info)
9347 .size(IconSize::Small)
9348 .color(cx.theme().status().error.into()),
9349 ),
9350 )
9351 })
9352 .when_some(icon, |element, icon| {
9353 element.child(
9354 div()
9355 .mt(px(1.5))
9356 .child(Icon::new(icon).size(IconSize::Small)),
9357 )
9358 })
9359 }
9360
9361 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9362 let accent_color = cx.theme().colors().text_accent;
9363 let editor_bg_color = cx.theme().colors().editor_background;
9364 editor_bg_color.blend(accent_color.opacity(0.1))
9365 }
9366
9367 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9368 let accent_color = cx.theme().colors().text_accent;
9369 let editor_bg_color = cx.theme().colors().editor_background;
9370 editor_bg_color.blend(accent_color.opacity(0.6))
9371 }
9372 fn get_prediction_provider_icon_name(
9373 provider: &Option<RegisteredEditPredictionProvider>,
9374 ) -> IconName {
9375 match provider {
9376 Some(provider) => match provider.provider.name() {
9377 "copilot" => IconName::Copilot,
9378 "supermaven" => IconName::Supermaven,
9379 _ => IconName::ZedPredict,
9380 },
9381 None => IconName::ZedPredict,
9382 }
9383 }
9384
9385 fn render_edit_prediction_cursor_popover(
9386 &self,
9387 min_width: Pixels,
9388 max_width: Pixels,
9389 cursor_point: Point,
9390 style: &EditorStyle,
9391 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9392 _window: &Window,
9393 cx: &mut Context<Editor>,
9394 ) -> Option<AnyElement> {
9395 let provider = self.edit_prediction_provider.as_ref()?;
9396 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9397
9398 let is_refreshing = provider.provider.is_refreshing(cx);
9399
9400 fn pending_completion_container(icon: IconName) -> Div {
9401 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9402 }
9403
9404 let completion = match &self.active_edit_prediction {
9405 Some(prediction) => {
9406 if !self.has_visible_completions_menu() {
9407 const RADIUS: Pixels = px(6.);
9408 const BORDER_WIDTH: Pixels = px(1.);
9409
9410 return Some(
9411 h_flex()
9412 .elevation_2(cx)
9413 .border(BORDER_WIDTH)
9414 .border_color(cx.theme().colors().border)
9415 .when(accept_keystroke.is_none(), |el| {
9416 el.border_color(cx.theme().status().error)
9417 })
9418 .rounded(RADIUS)
9419 .rounded_tl(px(0.))
9420 .overflow_hidden()
9421 .child(div().px_1p5().child(match &prediction.completion {
9422 EditPrediction::MoveWithin { target, snapshot } => {
9423 use text::ToPoint as _;
9424 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9425 {
9426 Icon::new(IconName::ZedPredictDown)
9427 } else {
9428 Icon::new(IconName::ZedPredictUp)
9429 }
9430 }
9431 EditPrediction::MoveOutside { .. } => {
9432 // TODO [zeta2] custom icon for external jump?
9433 Icon::new(provider_icon)
9434 }
9435 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9436 }))
9437 .child(
9438 h_flex()
9439 .gap_1()
9440 .py_1()
9441 .px_2()
9442 .rounded_r(RADIUS - BORDER_WIDTH)
9443 .border_l_1()
9444 .border_color(cx.theme().colors().border)
9445 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9446 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9447 el.child(
9448 Label::new("Hold")
9449 .size(LabelSize::Small)
9450 .when(accept_keystroke.is_none(), |el| {
9451 el.strikethrough()
9452 })
9453 .line_height_style(LineHeightStyle::UiLabel),
9454 )
9455 })
9456 .id("edit_prediction_cursor_popover_keybind")
9457 .when(accept_keystroke.is_none(), |el| {
9458 let status_colors = cx.theme().status();
9459
9460 el.bg(status_colors.error_background)
9461 .border_color(status_colors.error.opacity(0.6))
9462 .child(Icon::new(IconName::Info).color(Color::Error))
9463 .cursor_default()
9464 .hoverable_tooltip(move |_window, cx| {
9465 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9466 .into()
9467 })
9468 })
9469 .when_some(
9470 accept_keystroke.as_ref(),
9471 |el, accept_keystroke| {
9472 el.child(h_flex().children(ui::render_modifiers(
9473 accept_keystroke.modifiers(),
9474 PlatformStyle::platform(),
9475 Some(Color::Default),
9476 Some(IconSize::XSmall.rems().into()),
9477 false,
9478 )))
9479 },
9480 ),
9481 )
9482 .into_any(),
9483 );
9484 }
9485
9486 self.render_edit_prediction_cursor_popover_preview(
9487 prediction,
9488 cursor_point,
9489 style,
9490 cx,
9491 )?
9492 }
9493
9494 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9495 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9496 stale_completion,
9497 cursor_point,
9498 style,
9499 cx,
9500 )?,
9501
9502 None => pending_completion_container(provider_icon)
9503 .child(Label::new("...").size(LabelSize::Small)),
9504 },
9505
9506 None => pending_completion_container(provider_icon)
9507 .child(Label::new("...").size(LabelSize::Small)),
9508 };
9509
9510 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9511 completion
9512 .with_animation(
9513 "loading-completion",
9514 Animation::new(Duration::from_secs(2))
9515 .repeat()
9516 .with_easing(pulsating_between(0.4, 0.8)),
9517 |label, delta| label.opacity(delta),
9518 )
9519 .into_any_element()
9520 } else {
9521 completion.into_any_element()
9522 };
9523
9524 let has_completion = self.active_edit_prediction.is_some();
9525
9526 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9527 Some(
9528 h_flex()
9529 .min_w(min_width)
9530 .max_w(max_width)
9531 .flex_1()
9532 .elevation_2(cx)
9533 .border_color(cx.theme().colors().border)
9534 .child(
9535 div()
9536 .flex_1()
9537 .py_1()
9538 .px_2()
9539 .overflow_hidden()
9540 .child(completion),
9541 )
9542 .when_some(accept_keystroke, |el, accept_keystroke| {
9543 if !accept_keystroke.modifiers().modified() {
9544 return el;
9545 }
9546
9547 el.child(
9548 h_flex()
9549 .h_full()
9550 .border_l_1()
9551 .rounded_r_lg()
9552 .border_color(cx.theme().colors().border)
9553 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9554 .gap_1()
9555 .py_1()
9556 .px_2()
9557 .child(
9558 h_flex()
9559 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9560 .when(is_platform_style_mac, |parent| parent.gap_1())
9561 .child(h_flex().children(ui::render_modifiers(
9562 accept_keystroke.modifiers(),
9563 PlatformStyle::platform(),
9564 Some(if !has_completion {
9565 Color::Muted
9566 } else {
9567 Color::Default
9568 }),
9569 None,
9570 false,
9571 ))),
9572 )
9573 .child(Label::new("Preview").into_any_element())
9574 .opacity(if has_completion { 1.0 } else { 0.4 }),
9575 )
9576 })
9577 .into_any(),
9578 )
9579 }
9580
9581 fn render_edit_prediction_cursor_popover_preview(
9582 &self,
9583 completion: &EditPredictionState,
9584 cursor_point: Point,
9585 style: &EditorStyle,
9586 cx: &mut Context<Editor>,
9587 ) -> Option<Div> {
9588 use text::ToPoint as _;
9589
9590 fn render_relative_row_jump(
9591 prefix: impl Into<String>,
9592 current_row: u32,
9593 target_row: u32,
9594 ) -> Div {
9595 let (row_diff, arrow) = if target_row < current_row {
9596 (current_row - target_row, IconName::ArrowUp)
9597 } else {
9598 (target_row - current_row, IconName::ArrowDown)
9599 };
9600
9601 h_flex()
9602 .child(
9603 Label::new(format!("{}{}", prefix.into(), row_diff))
9604 .color(Color::Muted)
9605 .size(LabelSize::Small),
9606 )
9607 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9608 }
9609
9610 let supports_jump = self
9611 .edit_prediction_provider
9612 .as_ref()
9613 .map(|provider| provider.provider.supports_jump_to_edit())
9614 .unwrap_or(true);
9615
9616 match &completion.completion {
9617 EditPrediction::MoveWithin {
9618 target, snapshot, ..
9619 } => {
9620 if !supports_jump {
9621 return None;
9622 }
9623
9624 Some(
9625 h_flex()
9626 .px_2()
9627 .gap_2()
9628 .flex_1()
9629 .child(
9630 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9631 Icon::new(IconName::ZedPredictDown)
9632 } else {
9633 Icon::new(IconName::ZedPredictUp)
9634 },
9635 )
9636 .child(Label::new("Jump to Edit")),
9637 )
9638 }
9639 EditPrediction::MoveOutside { snapshot, .. } => {
9640 let file_name = snapshot
9641 .file()
9642 .map(|file| file.file_name(cx))
9643 .unwrap_or("untitled");
9644 Some(
9645 h_flex()
9646 .px_2()
9647 .gap_2()
9648 .flex_1()
9649 .child(Icon::new(IconName::ZedPredict))
9650 .child(Label::new(format!("Jump to {file_name}"))),
9651 )
9652 }
9653 EditPrediction::Edit {
9654 edits,
9655 edit_preview,
9656 snapshot,
9657 display_mode: _,
9658 } => {
9659 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9660
9661 let (highlighted_edits, has_more_lines) =
9662 if let Some(edit_preview) = edit_preview.as_ref() {
9663 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9664 .first_line_preview()
9665 } else {
9666 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9667 };
9668
9669 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9670 .with_default_highlights(&style.text, highlighted_edits.highlights);
9671
9672 let preview = h_flex()
9673 .gap_1()
9674 .min_w_16()
9675 .child(styled_text)
9676 .when(has_more_lines, |parent| parent.child("…"));
9677
9678 let left = if supports_jump && first_edit_row != cursor_point.row {
9679 render_relative_row_jump("", cursor_point.row, first_edit_row)
9680 .into_any_element()
9681 } else {
9682 let icon_name =
9683 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9684 Icon::new(icon_name).into_any_element()
9685 };
9686
9687 Some(
9688 h_flex()
9689 .h_full()
9690 .flex_1()
9691 .gap_2()
9692 .pr_1()
9693 .overflow_x_hidden()
9694 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9695 .child(left)
9696 .child(preview),
9697 )
9698 }
9699 }
9700 }
9701
9702 pub fn render_context_menu(
9703 &self,
9704 style: &EditorStyle,
9705 max_height_in_lines: u32,
9706 window: &mut Window,
9707 cx: &mut Context<Editor>,
9708 ) -> Option<AnyElement> {
9709 let menu = self.context_menu.borrow();
9710 let menu = menu.as_ref()?;
9711 if !menu.visible() {
9712 return None;
9713 };
9714 Some(menu.render(style, max_height_in_lines, window, cx))
9715 }
9716
9717 fn render_context_menu_aside(
9718 &mut self,
9719 max_size: Size<Pixels>,
9720 window: &mut Window,
9721 cx: &mut Context<Editor>,
9722 ) -> Option<AnyElement> {
9723 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9724 if menu.visible() {
9725 menu.render_aside(max_size, window, cx)
9726 } else {
9727 None
9728 }
9729 })
9730 }
9731
9732 fn hide_context_menu(
9733 &mut self,
9734 window: &mut Window,
9735 cx: &mut Context<Self>,
9736 ) -> Option<CodeContextMenu> {
9737 cx.notify();
9738 self.completion_tasks.clear();
9739 let context_menu = self.context_menu.borrow_mut().take();
9740 self.stale_edit_prediction_in_menu.take();
9741 self.update_visible_edit_prediction(window, cx);
9742 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9743 && let Some(completion_provider) = &self.completion_provider
9744 {
9745 completion_provider.selection_changed(None, window, cx);
9746 }
9747 context_menu
9748 }
9749
9750 fn show_snippet_choices(
9751 &mut self,
9752 choices: &Vec<String>,
9753 selection: Range<Anchor>,
9754 cx: &mut Context<Self>,
9755 ) {
9756 let Some((_, buffer, _)) = self
9757 .buffer()
9758 .read(cx)
9759 .excerpt_containing(selection.start, cx)
9760 else {
9761 return;
9762 };
9763 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9764 else {
9765 return;
9766 };
9767 if buffer != end_buffer {
9768 log::error!("expected anchor range to have matching buffer IDs");
9769 return;
9770 }
9771
9772 let id = post_inc(&mut self.next_completion_id);
9773 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9774 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9775 CompletionsMenu::new_snippet_choices(
9776 id,
9777 true,
9778 choices,
9779 selection,
9780 buffer,
9781 snippet_sort_order,
9782 ),
9783 ));
9784 }
9785
9786 pub fn insert_snippet(
9787 &mut self,
9788 insertion_ranges: &[Range<usize>],
9789 snippet: Snippet,
9790 window: &mut Window,
9791 cx: &mut Context<Self>,
9792 ) -> Result<()> {
9793 struct Tabstop<T> {
9794 is_end_tabstop: bool,
9795 ranges: Vec<Range<T>>,
9796 choices: Option<Vec<String>>,
9797 }
9798
9799 let tabstops = self.buffer.update(cx, |buffer, cx| {
9800 let snippet_text: Arc<str> = snippet.text.clone().into();
9801 let edits = insertion_ranges
9802 .iter()
9803 .cloned()
9804 .map(|range| (range, snippet_text.clone()));
9805 let autoindent_mode = AutoindentMode::Block {
9806 original_indent_columns: Vec::new(),
9807 };
9808 buffer.edit(edits, Some(autoindent_mode), cx);
9809
9810 let snapshot = &*buffer.read(cx);
9811 let snippet = &snippet;
9812 snippet
9813 .tabstops
9814 .iter()
9815 .map(|tabstop| {
9816 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9817 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9818 });
9819 let mut tabstop_ranges = tabstop
9820 .ranges
9821 .iter()
9822 .flat_map(|tabstop_range| {
9823 let mut delta = 0_isize;
9824 insertion_ranges.iter().map(move |insertion_range| {
9825 let insertion_start = insertion_range.start as isize + delta;
9826 delta +=
9827 snippet.text.len() as isize - insertion_range.len() as isize;
9828
9829 let start = ((insertion_start + tabstop_range.start) as usize)
9830 .min(snapshot.len());
9831 let end = ((insertion_start + tabstop_range.end) as usize)
9832 .min(snapshot.len());
9833 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9834 })
9835 })
9836 .collect::<Vec<_>>();
9837 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9838
9839 Tabstop {
9840 is_end_tabstop,
9841 ranges: tabstop_ranges,
9842 choices: tabstop.choices.clone(),
9843 }
9844 })
9845 .collect::<Vec<_>>()
9846 });
9847 if let Some(tabstop) = tabstops.first() {
9848 self.change_selections(Default::default(), window, cx, |s| {
9849 // Reverse order so that the first range is the newest created selection.
9850 // Completions will use it and autoscroll will prioritize it.
9851 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9852 });
9853
9854 if let Some(choices) = &tabstop.choices
9855 && let Some(selection) = tabstop.ranges.first()
9856 {
9857 self.show_snippet_choices(choices, selection.clone(), cx)
9858 }
9859
9860 // If we're already at the last tabstop and it's at the end of the snippet,
9861 // we're done, we don't need to keep the state around.
9862 if !tabstop.is_end_tabstop {
9863 let choices = tabstops
9864 .iter()
9865 .map(|tabstop| tabstop.choices.clone())
9866 .collect();
9867
9868 let ranges = tabstops
9869 .into_iter()
9870 .map(|tabstop| tabstop.ranges)
9871 .collect::<Vec<_>>();
9872
9873 self.snippet_stack.push(SnippetState {
9874 active_index: 0,
9875 ranges,
9876 choices,
9877 });
9878 }
9879
9880 // Check whether the just-entered snippet ends with an auto-closable bracket.
9881 if self.autoclose_regions.is_empty() {
9882 let snapshot = self.buffer.read(cx).snapshot(cx);
9883 let mut all_selections = self.selections.all::<Point>(cx);
9884 for selection in &mut all_selections {
9885 let selection_head = selection.head();
9886 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9887 continue;
9888 };
9889
9890 let mut bracket_pair = None;
9891 let max_lookup_length = scope
9892 .brackets()
9893 .map(|(pair, _)| {
9894 pair.start
9895 .as_str()
9896 .chars()
9897 .count()
9898 .max(pair.end.as_str().chars().count())
9899 })
9900 .max();
9901 if let Some(max_lookup_length) = max_lookup_length {
9902 let next_text = snapshot
9903 .chars_at(selection_head)
9904 .take(max_lookup_length)
9905 .collect::<String>();
9906 let prev_text = snapshot
9907 .reversed_chars_at(selection_head)
9908 .take(max_lookup_length)
9909 .collect::<String>();
9910
9911 for (pair, enabled) in scope.brackets() {
9912 if enabled
9913 && pair.close
9914 && prev_text.starts_with(pair.start.as_str())
9915 && next_text.starts_with(pair.end.as_str())
9916 {
9917 bracket_pair = Some(pair.clone());
9918 break;
9919 }
9920 }
9921 }
9922
9923 if let Some(pair) = bracket_pair {
9924 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9925 let autoclose_enabled =
9926 self.use_autoclose && snapshot_settings.use_autoclose;
9927 if autoclose_enabled {
9928 let start = snapshot.anchor_after(selection_head);
9929 let end = snapshot.anchor_after(selection_head);
9930 self.autoclose_regions.push(AutocloseRegion {
9931 selection_id: selection.id,
9932 range: start..end,
9933 pair,
9934 });
9935 }
9936 }
9937 }
9938 }
9939 }
9940 Ok(())
9941 }
9942
9943 pub fn move_to_next_snippet_tabstop(
9944 &mut self,
9945 window: &mut Window,
9946 cx: &mut Context<Self>,
9947 ) -> bool {
9948 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9949 }
9950
9951 pub fn move_to_prev_snippet_tabstop(
9952 &mut self,
9953 window: &mut Window,
9954 cx: &mut Context<Self>,
9955 ) -> bool {
9956 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9957 }
9958
9959 pub fn move_to_snippet_tabstop(
9960 &mut self,
9961 bias: Bias,
9962 window: &mut Window,
9963 cx: &mut Context<Self>,
9964 ) -> bool {
9965 if let Some(mut snippet) = self.snippet_stack.pop() {
9966 match bias {
9967 Bias::Left => {
9968 if snippet.active_index > 0 {
9969 snippet.active_index -= 1;
9970 } else {
9971 self.snippet_stack.push(snippet);
9972 return false;
9973 }
9974 }
9975 Bias::Right => {
9976 if snippet.active_index + 1 < snippet.ranges.len() {
9977 snippet.active_index += 1;
9978 } else {
9979 self.snippet_stack.push(snippet);
9980 return false;
9981 }
9982 }
9983 }
9984 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9985 self.change_selections(Default::default(), window, cx, |s| {
9986 // Reverse order so that the first range is the newest created selection.
9987 // Completions will use it and autoscroll will prioritize it.
9988 s.select_ranges(current_ranges.iter().rev().cloned())
9989 });
9990
9991 if let Some(choices) = &snippet.choices[snippet.active_index]
9992 && let Some(selection) = current_ranges.first()
9993 {
9994 self.show_snippet_choices(choices, selection.clone(), cx);
9995 }
9996
9997 // If snippet state is not at the last tabstop, push it back on the stack
9998 if snippet.active_index + 1 < snippet.ranges.len() {
9999 self.snippet_stack.push(snippet);
10000 }
10001 return true;
10002 }
10003 }
10004
10005 false
10006 }
10007
10008 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10009 self.transact(window, cx, |this, window, cx| {
10010 this.select_all(&SelectAll, window, cx);
10011 this.insert("", window, cx);
10012 });
10013 }
10014
10015 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10016 if self.read_only(cx) {
10017 return;
10018 }
10019 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10020 self.transact(window, cx, |this, window, cx| {
10021 this.select_autoclose_pair(window, cx);
10022 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10023 if !this.linked_edit_ranges.is_empty() {
10024 let selections = this.selections.all::<MultiBufferPoint>(cx);
10025 let snapshot = this.buffer.read(cx).snapshot(cx);
10026
10027 for selection in selections.iter() {
10028 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10029 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10030 if selection_start.buffer_id != selection_end.buffer_id {
10031 continue;
10032 }
10033 if let Some(ranges) =
10034 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10035 {
10036 for (buffer, entries) in ranges {
10037 linked_ranges.entry(buffer).or_default().extend(entries);
10038 }
10039 }
10040 }
10041 }
10042
10043 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
10044 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10045 for selection in &mut selections {
10046 if selection.is_empty() {
10047 let old_head = selection.head();
10048 let mut new_head =
10049 movement::left(&display_map, old_head.to_display_point(&display_map))
10050 .to_point(&display_map);
10051 if let Some((buffer, line_buffer_range)) = display_map
10052 .buffer_snapshot()
10053 .buffer_line_for_row(MultiBufferRow(old_head.row))
10054 {
10055 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10056 let indent_len = match indent_size.kind {
10057 IndentKind::Space => {
10058 buffer.settings_at(line_buffer_range.start, cx).tab_size
10059 }
10060 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10061 };
10062 if old_head.column <= indent_size.len && old_head.column > 0 {
10063 let indent_len = indent_len.get();
10064 new_head = cmp::min(
10065 new_head,
10066 MultiBufferPoint::new(
10067 old_head.row,
10068 ((old_head.column - 1) / indent_len) * indent_len,
10069 ),
10070 );
10071 }
10072 }
10073
10074 selection.set_head(new_head, SelectionGoal::None);
10075 }
10076 }
10077
10078 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10079 this.insert("", window, cx);
10080 let empty_str: Arc<str> = Arc::from("");
10081 for (buffer, edits) in linked_ranges {
10082 let snapshot = buffer.read(cx).snapshot();
10083 use text::ToPoint as TP;
10084
10085 let edits = edits
10086 .into_iter()
10087 .map(|range| {
10088 let end_point = TP::to_point(&range.end, &snapshot);
10089 let mut start_point = TP::to_point(&range.start, &snapshot);
10090
10091 if end_point == start_point {
10092 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10093 .saturating_sub(1);
10094 start_point =
10095 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10096 };
10097
10098 (start_point..end_point, empty_str.clone())
10099 })
10100 .sorted_by_key(|(range, _)| range.start)
10101 .collect::<Vec<_>>();
10102 buffer.update(cx, |this, cx| {
10103 this.edit(edits, None, cx);
10104 })
10105 }
10106 this.refresh_edit_prediction(true, false, window, cx);
10107 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
10108 });
10109 }
10110
10111 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10112 if self.read_only(cx) {
10113 return;
10114 }
10115 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10116 self.transact(window, cx, |this, window, cx| {
10117 this.change_selections(Default::default(), window, cx, |s| {
10118 s.move_with(|map, selection| {
10119 if selection.is_empty() {
10120 let cursor = movement::right(map, selection.head());
10121 selection.end = cursor;
10122 selection.reversed = true;
10123 selection.goal = SelectionGoal::None;
10124 }
10125 })
10126 });
10127 this.insert("", window, cx);
10128 this.refresh_edit_prediction(true, false, window, cx);
10129 });
10130 }
10131
10132 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10133 if self.mode.is_single_line() {
10134 cx.propagate();
10135 return;
10136 }
10137
10138 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10139 if self.move_to_prev_snippet_tabstop(window, cx) {
10140 return;
10141 }
10142 self.outdent(&Outdent, window, cx);
10143 }
10144
10145 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10146 if self.mode.is_single_line() {
10147 cx.propagate();
10148 return;
10149 }
10150
10151 if self.move_to_next_snippet_tabstop(window, cx) {
10152 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10153 return;
10154 }
10155 if self.read_only(cx) {
10156 return;
10157 }
10158 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10159 let mut selections = self.selections.all_adjusted(cx);
10160 let buffer = self.buffer.read(cx);
10161 let snapshot = buffer.snapshot(cx);
10162 let rows_iter = selections.iter().map(|s| s.head().row);
10163 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10164
10165 let has_some_cursor_in_whitespace = selections
10166 .iter()
10167 .filter(|selection| selection.is_empty())
10168 .any(|selection| {
10169 let cursor = selection.head();
10170 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10171 cursor.column < current_indent.len
10172 });
10173
10174 let mut edits = Vec::new();
10175 let mut prev_edited_row = 0;
10176 let mut row_delta = 0;
10177 for selection in &mut selections {
10178 if selection.start.row != prev_edited_row {
10179 row_delta = 0;
10180 }
10181 prev_edited_row = selection.end.row;
10182
10183 // If the selection is non-empty, then increase the indentation of the selected lines.
10184 if !selection.is_empty() {
10185 row_delta =
10186 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10187 continue;
10188 }
10189
10190 let cursor = selection.head();
10191 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10192 if let Some(suggested_indent) =
10193 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10194 {
10195 // Don't do anything if already at suggested indent
10196 // and there is any other cursor which is not
10197 if has_some_cursor_in_whitespace
10198 && cursor.column == current_indent.len
10199 && current_indent.len == suggested_indent.len
10200 {
10201 continue;
10202 }
10203
10204 // Adjust line and move cursor to suggested indent
10205 // if cursor is not at suggested indent
10206 if cursor.column < suggested_indent.len
10207 && cursor.column <= current_indent.len
10208 && current_indent.len <= suggested_indent.len
10209 {
10210 selection.start = Point::new(cursor.row, suggested_indent.len);
10211 selection.end = selection.start;
10212 if row_delta == 0 {
10213 edits.extend(Buffer::edit_for_indent_size_adjustment(
10214 cursor.row,
10215 current_indent,
10216 suggested_indent,
10217 ));
10218 row_delta = suggested_indent.len - current_indent.len;
10219 }
10220 continue;
10221 }
10222
10223 // If current indent is more than suggested indent
10224 // only move cursor to current indent and skip indent
10225 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10226 selection.start = Point::new(cursor.row, current_indent.len);
10227 selection.end = selection.start;
10228 continue;
10229 }
10230 }
10231
10232 // Otherwise, insert a hard or soft tab.
10233 let settings = buffer.language_settings_at(cursor, cx);
10234 let tab_size = if settings.hard_tabs {
10235 IndentSize::tab()
10236 } else {
10237 let tab_size = settings.tab_size.get();
10238 let indent_remainder = snapshot
10239 .text_for_range(Point::new(cursor.row, 0)..cursor)
10240 .flat_map(str::chars)
10241 .fold(row_delta % tab_size, |counter: u32, c| {
10242 if c == '\t' {
10243 0
10244 } else {
10245 (counter + 1) % tab_size
10246 }
10247 });
10248
10249 let chars_to_next_tab_stop = tab_size - indent_remainder;
10250 IndentSize::spaces(chars_to_next_tab_stop)
10251 };
10252 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10253 selection.end = selection.start;
10254 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10255 row_delta += tab_size.len;
10256 }
10257
10258 self.transact(window, cx, |this, window, cx| {
10259 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10260 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10261 this.refresh_edit_prediction(true, false, window, cx);
10262 });
10263 }
10264
10265 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10266 if self.read_only(cx) {
10267 return;
10268 }
10269 if self.mode.is_single_line() {
10270 cx.propagate();
10271 return;
10272 }
10273
10274 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10275 let mut selections = self.selections.all::<Point>(cx);
10276 let mut prev_edited_row = 0;
10277 let mut row_delta = 0;
10278 let mut edits = Vec::new();
10279 let buffer = self.buffer.read(cx);
10280 let snapshot = buffer.snapshot(cx);
10281 for selection in &mut selections {
10282 if selection.start.row != prev_edited_row {
10283 row_delta = 0;
10284 }
10285 prev_edited_row = selection.end.row;
10286
10287 row_delta =
10288 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10289 }
10290
10291 self.transact(window, cx, |this, window, cx| {
10292 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10293 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10294 });
10295 }
10296
10297 fn indent_selection(
10298 buffer: &MultiBuffer,
10299 snapshot: &MultiBufferSnapshot,
10300 selection: &mut Selection<Point>,
10301 edits: &mut Vec<(Range<Point>, String)>,
10302 delta_for_start_row: u32,
10303 cx: &App,
10304 ) -> u32 {
10305 let settings = buffer.language_settings_at(selection.start, cx);
10306 let tab_size = settings.tab_size.get();
10307 let indent_kind = if settings.hard_tabs {
10308 IndentKind::Tab
10309 } else {
10310 IndentKind::Space
10311 };
10312 let mut start_row = selection.start.row;
10313 let mut end_row = selection.end.row + 1;
10314
10315 // If a selection ends at the beginning of a line, don't indent
10316 // that last line.
10317 if selection.end.column == 0 && selection.end.row > selection.start.row {
10318 end_row -= 1;
10319 }
10320
10321 // Avoid re-indenting a row that has already been indented by a
10322 // previous selection, but still update this selection's column
10323 // to reflect that indentation.
10324 if delta_for_start_row > 0 {
10325 start_row += 1;
10326 selection.start.column += delta_for_start_row;
10327 if selection.end.row == selection.start.row {
10328 selection.end.column += delta_for_start_row;
10329 }
10330 }
10331
10332 let mut delta_for_end_row = 0;
10333 let has_multiple_rows = start_row + 1 != end_row;
10334 for row in start_row..end_row {
10335 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10336 let indent_delta = match (current_indent.kind, indent_kind) {
10337 (IndentKind::Space, IndentKind::Space) => {
10338 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10339 IndentSize::spaces(columns_to_next_tab_stop)
10340 }
10341 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10342 (_, IndentKind::Tab) => IndentSize::tab(),
10343 };
10344
10345 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10346 0
10347 } else {
10348 selection.start.column
10349 };
10350 let row_start = Point::new(row, start);
10351 edits.push((
10352 row_start..row_start,
10353 indent_delta.chars().collect::<String>(),
10354 ));
10355
10356 // Update this selection's endpoints to reflect the indentation.
10357 if row == selection.start.row {
10358 selection.start.column += indent_delta.len;
10359 }
10360 if row == selection.end.row {
10361 selection.end.column += indent_delta.len;
10362 delta_for_end_row = indent_delta.len;
10363 }
10364 }
10365
10366 if selection.start.row == selection.end.row {
10367 delta_for_start_row + delta_for_end_row
10368 } else {
10369 delta_for_end_row
10370 }
10371 }
10372
10373 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10374 if self.read_only(cx) {
10375 return;
10376 }
10377 if self.mode.is_single_line() {
10378 cx.propagate();
10379 return;
10380 }
10381
10382 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10383 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10384 let selections = self.selections.all::<Point>(cx);
10385 let mut deletion_ranges = Vec::new();
10386 let mut last_outdent = None;
10387 {
10388 let buffer = self.buffer.read(cx);
10389 let snapshot = buffer.snapshot(cx);
10390 for selection in &selections {
10391 let settings = buffer.language_settings_at(selection.start, cx);
10392 let tab_size = settings.tab_size.get();
10393 let mut rows = selection.spanned_rows(false, &display_map);
10394
10395 // Avoid re-outdenting a row that has already been outdented by a
10396 // previous selection.
10397 if let Some(last_row) = last_outdent
10398 && last_row == rows.start
10399 {
10400 rows.start = rows.start.next_row();
10401 }
10402 let has_multiple_rows = rows.len() > 1;
10403 for row in rows.iter_rows() {
10404 let indent_size = snapshot.indent_size_for_line(row);
10405 if indent_size.len > 0 {
10406 let deletion_len = match indent_size.kind {
10407 IndentKind::Space => {
10408 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10409 if columns_to_prev_tab_stop == 0 {
10410 tab_size
10411 } else {
10412 columns_to_prev_tab_stop
10413 }
10414 }
10415 IndentKind::Tab => 1,
10416 };
10417 let start = if has_multiple_rows
10418 || deletion_len > selection.start.column
10419 || indent_size.len < selection.start.column
10420 {
10421 0
10422 } else {
10423 selection.start.column - deletion_len
10424 };
10425 deletion_ranges.push(
10426 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10427 );
10428 last_outdent = Some(row);
10429 }
10430 }
10431 }
10432 }
10433
10434 self.transact(window, cx, |this, window, cx| {
10435 this.buffer.update(cx, |buffer, cx| {
10436 let empty_str: Arc<str> = Arc::default();
10437 buffer.edit(
10438 deletion_ranges
10439 .into_iter()
10440 .map(|range| (range, empty_str.clone())),
10441 None,
10442 cx,
10443 );
10444 });
10445 let selections = this.selections.all::<usize>(cx);
10446 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10447 });
10448 }
10449
10450 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10451 if self.read_only(cx) {
10452 return;
10453 }
10454 if self.mode.is_single_line() {
10455 cx.propagate();
10456 return;
10457 }
10458
10459 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10460 let selections = self
10461 .selections
10462 .all::<usize>(cx)
10463 .into_iter()
10464 .map(|s| s.range());
10465
10466 self.transact(window, cx, |this, window, cx| {
10467 this.buffer.update(cx, |buffer, cx| {
10468 buffer.autoindent_ranges(selections, cx);
10469 });
10470 let selections = this.selections.all::<usize>(cx);
10471 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10472 });
10473 }
10474
10475 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10476 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10477 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10478 let selections = self.selections.all::<Point>(cx);
10479
10480 let mut new_cursors = Vec::new();
10481 let mut edit_ranges = Vec::new();
10482 let mut selections = selections.iter().peekable();
10483 while let Some(selection) = selections.next() {
10484 let mut rows = selection.spanned_rows(false, &display_map);
10485
10486 // Accumulate contiguous regions of rows that we want to delete.
10487 while let Some(next_selection) = selections.peek() {
10488 let next_rows = next_selection.spanned_rows(false, &display_map);
10489 if next_rows.start <= rows.end {
10490 rows.end = next_rows.end;
10491 selections.next().unwrap();
10492 } else {
10493 break;
10494 }
10495 }
10496
10497 let buffer = display_map.buffer_snapshot();
10498 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10499 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10500 // If there's a line after the range, delete the \n from the end of the row range
10501 (
10502 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10503 rows.end,
10504 )
10505 } else {
10506 // If there isn't a line after the range, delete the \n from the line before the
10507 // start of the row range
10508 edit_start = edit_start.saturating_sub(1);
10509 (buffer.len(), rows.start.previous_row())
10510 };
10511
10512 let text_layout_details = self.text_layout_details(window);
10513 let x = display_map.x_for_display_point(
10514 selection.head().to_display_point(&display_map),
10515 &text_layout_details,
10516 );
10517 let row = Point::new(target_row.0, 0)
10518 .to_display_point(&display_map)
10519 .row();
10520 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10521
10522 new_cursors.push((
10523 selection.id,
10524 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10525 SelectionGoal::None,
10526 ));
10527 edit_ranges.push(edit_start..edit_end);
10528 }
10529
10530 self.transact(window, cx, |this, window, cx| {
10531 let buffer = this.buffer.update(cx, |buffer, cx| {
10532 let empty_str: Arc<str> = Arc::default();
10533 buffer.edit(
10534 edit_ranges
10535 .into_iter()
10536 .map(|range| (range, empty_str.clone())),
10537 None,
10538 cx,
10539 );
10540 buffer.snapshot(cx)
10541 });
10542 let new_selections = new_cursors
10543 .into_iter()
10544 .map(|(id, cursor, goal)| {
10545 let cursor = cursor.to_point(&buffer);
10546 Selection {
10547 id,
10548 start: cursor,
10549 end: cursor,
10550 reversed: false,
10551 goal,
10552 }
10553 })
10554 .collect();
10555
10556 this.change_selections(Default::default(), window, cx, |s| {
10557 s.select(new_selections);
10558 });
10559 });
10560 }
10561
10562 pub fn join_lines_impl(
10563 &mut self,
10564 insert_whitespace: bool,
10565 window: &mut Window,
10566 cx: &mut Context<Self>,
10567 ) {
10568 if self.read_only(cx) {
10569 return;
10570 }
10571 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10572 for selection in self.selections.all::<Point>(cx) {
10573 let start = MultiBufferRow(selection.start.row);
10574 // Treat single line selections as if they include the next line. Otherwise this action
10575 // would do nothing for single line selections individual cursors.
10576 let end = if selection.start.row == selection.end.row {
10577 MultiBufferRow(selection.start.row + 1)
10578 } else {
10579 MultiBufferRow(selection.end.row)
10580 };
10581
10582 if let Some(last_row_range) = row_ranges.last_mut()
10583 && start <= last_row_range.end
10584 {
10585 last_row_range.end = end;
10586 continue;
10587 }
10588 row_ranges.push(start..end);
10589 }
10590
10591 let snapshot = self.buffer.read(cx).snapshot(cx);
10592 let mut cursor_positions = Vec::new();
10593 for row_range in &row_ranges {
10594 let anchor = snapshot.anchor_before(Point::new(
10595 row_range.end.previous_row().0,
10596 snapshot.line_len(row_range.end.previous_row()),
10597 ));
10598 cursor_positions.push(anchor..anchor);
10599 }
10600
10601 self.transact(window, cx, |this, window, cx| {
10602 for row_range in row_ranges.into_iter().rev() {
10603 for row in row_range.iter_rows().rev() {
10604 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10605 let next_line_row = row.next_row();
10606 let indent = snapshot.indent_size_for_line(next_line_row);
10607 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10608
10609 let replace =
10610 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10611 " "
10612 } else {
10613 ""
10614 };
10615
10616 this.buffer.update(cx, |buffer, cx| {
10617 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10618 });
10619 }
10620 }
10621
10622 this.change_selections(Default::default(), window, cx, |s| {
10623 s.select_anchor_ranges(cursor_positions)
10624 });
10625 });
10626 }
10627
10628 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10629 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10630 self.join_lines_impl(true, window, cx);
10631 }
10632
10633 pub fn sort_lines_case_sensitive(
10634 &mut self,
10635 _: &SortLinesCaseSensitive,
10636 window: &mut Window,
10637 cx: &mut Context<Self>,
10638 ) {
10639 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10640 }
10641
10642 pub fn sort_lines_by_length(
10643 &mut self,
10644 _: &SortLinesByLength,
10645 window: &mut Window,
10646 cx: &mut Context<Self>,
10647 ) {
10648 self.manipulate_immutable_lines(window, cx, |lines| {
10649 lines.sort_by_key(|&line| line.chars().count())
10650 })
10651 }
10652
10653 pub fn sort_lines_case_insensitive(
10654 &mut self,
10655 _: &SortLinesCaseInsensitive,
10656 window: &mut Window,
10657 cx: &mut Context<Self>,
10658 ) {
10659 self.manipulate_immutable_lines(window, cx, |lines| {
10660 lines.sort_by_key(|line| line.to_lowercase())
10661 })
10662 }
10663
10664 pub fn unique_lines_case_insensitive(
10665 &mut self,
10666 _: &UniqueLinesCaseInsensitive,
10667 window: &mut Window,
10668 cx: &mut Context<Self>,
10669 ) {
10670 self.manipulate_immutable_lines(window, cx, |lines| {
10671 let mut seen = HashSet::default();
10672 lines.retain(|line| seen.insert(line.to_lowercase()));
10673 })
10674 }
10675
10676 pub fn unique_lines_case_sensitive(
10677 &mut self,
10678 _: &UniqueLinesCaseSensitive,
10679 window: &mut Window,
10680 cx: &mut Context<Self>,
10681 ) {
10682 self.manipulate_immutable_lines(window, cx, |lines| {
10683 let mut seen = HashSet::default();
10684 lines.retain(|line| seen.insert(*line));
10685 })
10686 }
10687
10688 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10689 let snapshot = self.buffer.read(cx).snapshot(cx);
10690 for selection in self.selections.disjoint_anchors_arc().iter() {
10691 if snapshot
10692 .language_at(selection.start)
10693 .and_then(|lang| lang.config().wrap_characters.as_ref())
10694 .is_some()
10695 {
10696 return true;
10697 }
10698 }
10699 false
10700 }
10701
10702 fn wrap_selections_in_tag(
10703 &mut self,
10704 _: &WrapSelectionsInTag,
10705 window: &mut Window,
10706 cx: &mut Context<Self>,
10707 ) {
10708 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10709
10710 let snapshot = self.buffer.read(cx).snapshot(cx);
10711
10712 let mut edits = Vec::new();
10713 let mut boundaries = Vec::new();
10714
10715 for selection in self.selections.all::<Point>(cx).iter() {
10716 let Some(wrap_config) = snapshot
10717 .language_at(selection.start)
10718 .and_then(|lang| lang.config().wrap_characters.clone())
10719 else {
10720 continue;
10721 };
10722
10723 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10724 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10725
10726 let start_before = snapshot.anchor_before(selection.start);
10727 let end_after = snapshot.anchor_after(selection.end);
10728
10729 edits.push((start_before..start_before, open_tag));
10730 edits.push((end_after..end_after, close_tag));
10731
10732 boundaries.push((
10733 start_before,
10734 end_after,
10735 wrap_config.start_prefix.len(),
10736 wrap_config.end_suffix.len(),
10737 ));
10738 }
10739
10740 if edits.is_empty() {
10741 return;
10742 }
10743
10744 self.transact(window, cx, |this, window, cx| {
10745 let buffer = this.buffer.update(cx, |buffer, cx| {
10746 buffer.edit(edits, None, cx);
10747 buffer.snapshot(cx)
10748 });
10749
10750 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10751 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10752 boundaries.into_iter()
10753 {
10754 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10755 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10756 new_selections.push(open_offset..open_offset);
10757 new_selections.push(close_offset..close_offset);
10758 }
10759
10760 this.change_selections(Default::default(), window, cx, |s| {
10761 s.select_ranges(new_selections);
10762 });
10763
10764 this.request_autoscroll(Autoscroll::fit(), cx);
10765 });
10766 }
10767
10768 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10769 let Some(project) = self.project.clone() else {
10770 return;
10771 };
10772 self.reload(project, window, cx)
10773 .detach_and_notify_err(window, cx);
10774 }
10775
10776 pub fn restore_file(
10777 &mut self,
10778 _: &::git::RestoreFile,
10779 window: &mut Window,
10780 cx: &mut Context<Self>,
10781 ) {
10782 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10783 let mut buffer_ids = HashSet::default();
10784 let snapshot = self.buffer().read(cx).snapshot(cx);
10785 for selection in self.selections.all::<usize>(cx) {
10786 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10787 }
10788
10789 let buffer = self.buffer().read(cx);
10790 let ranges = buffer_ids
10791 .into_iter()
10792 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10793 .collect::<Vec<_>>();
10794
10795 self.restore_hunks_in_ranges(ranges, window, cx);
10796 }
10797
10798 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10799 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10800 let selections = self
10801 .selections
10802 .all(cx)
10803 .into_iter()
10804 .map(|s| s.range())
10805 .collect();
10806 self.restore_hunks_in_ranges(selections, window, cx);
10807 }
10808
10809 pub fn restore_hunks_in_ranges(
10810 &mut self,
10811 ranges: Vec<Range<Point>>,
10812 window: &mut Window,
10813 cx: &mut Context<Editor>,
10814 ) {
10815 let mut revert_changes = HashMap::default();
10816 let chunk_by = self
10817 .snapshot(window, cx)
10818 .hunks_for_ranges(ranges)
10819 .into_iter()
10820 .chunk_by(|hunk| hunk.buffer_id);
10821 for (buffer_id, hunks) in &chunk_by {
10822 let hunks = hunks.collect::<Vec<_>>();
10823 for hunk in &hunks {
10824 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10825 }
10826 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10827 }
10828 drop(chunk_by);
10829 if !revert_changes.is_empty() {
10830 self.transact(window, cx, |editor, window, cx| {
10831 editor.restore(revert_changes, window, cx);
10832 });
10833 }
10834 }
10835
10836 pub fn open_active_item_in_terminal(
10837 &mut self,
10838 _: &OpenInTerminal,
10839 window: &mut Window,
10840 cx: &mut Context<Self>,
10841 ) {
10842 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10843 let project_path = buffer.read(cx).project_path(cx)?;
10844 let project = self.project()?.read(cx);
10845 let entry = project.entry_for_path(&project_path, cx)?;
10846 let parent = match &entry.canonical_path {
10847 Some(canonical_path) => canonical_path.to_path_buf(),
10848 None => project.absolute_path(&project_path, cx)?,
10849 }
10850 .parent()?
10851 .to_path_buf();
10852 Some(parent)
10853 }) {
10854 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10855 }
10856 }
10857
10858 fn set_breakpoint_context_menu(
10859 &mut self,
10860 display_row: DisplayRow,
10861 position: Option<Anchor>,
10862 clicked_point: gpui::Point<Pixels>,
10863 window: &mut Window,
10864 cx: &mut Context<Self>,
10865 ) {
10866 let source = self
10867 .buffer
10868 .read(cx)
10869 .snapshot(cx)
10870 .anchor_before(Point::new(display_row.0, 0u32));
10871
10872 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10873
10874 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10875 self,
10876 source,
10877 clicked_point,
10878 context_menu,
10879 window,
10880 cx,
10881 );
10882 }
10883
10884 fn add_edit_breakpoint_block(
10885 &mut self,
10886 anchor: Anchor,
10887 breakpoint: &Breakpoint,
10888 edit_action: BreakpointPromptEditAction,
10889 window: &mut Window,
10890 cx: &mut Context<Self>,
10891 ) {
10892 let weak_editor = cx.weak_entity();
10893 let bp_prompt = cx.new(|cx| {
10894 BreakpointPromptEditor::new(
10895 weak_editor,
10896 anchor,
10897 breakpoint.clone(),
10898 edit_action,
10899 window,
10900 cx,
10901 )
10902 });
10903
10904 let height = bp_prompt.update(cx, |this, cx| {
10905 this.prompt
10906 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10907 });
10908 let cloned_prompt = bp_prompt.clone();
10909 let blocks = vec![BlockProperties {
10910 style: BlockStyle::Sticky,
10911 placement: BlockPlacement::Above(anchor),
10912 height: Some(height),
10913 render: Arc::new(move |cx| {
10914 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10915 cloned_prompt.clone().into_any_element()
10916 }),
10917 priority: 0,
10918 }];
10919
10920 let focus_handle = bp_prompt.focus_handle(cx);
10921 window.focus(&focus_handle);
10922
10923 let block_ids = self.insert_blocks(blocks, None, cx);
10924 bp_prompt.update(cx, |prompt, _| {
10925 prompt.add_block_ids(block_ids);
10926 });
10927 }
10928
10929 pub(crate) fn breakpoint_at_row(
10930 &self,
10931 row: u32,
10932 window: &mut Window,
10933 cx: &mut Context<Self>,
10934 ) -> Option<(Anchor, Breakpoint)> {
10935 let snapshot = self.snapshot(window, cx);
10936 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
10937
10938 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10939 }
10940
10941 pub(crate) fn breakpoint_at_anchor(
10942 &self,
10943 breakpoint_position: Anchor,
10944 snapshot: &EditorSnapshot,
10945 cx: &mut Context<Self>,
10946 ) -> Option<(Anchor, Breakpoint)> {
10947 let buffer = self
10948 .buffer
10949 .read(cx)
10950 .buffer_for_anchor(breakpoint_position, cx)?;
10951
10952 let enclosing_excerpt = breakpoint_position.excerpt_id;
10953 let buffer_snapshot = buffer.read(cx).snapshot();
10954
10955 let row = buffer_snapshot
10956 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10957 .row;
10958
10959 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
10960 let anchor_end = snapshot
10961 .buffer_snapshot()
10962 .anchor_after(Point::new(row, line_len));
10963
10964 self.breakpoint_store
10965 .as_ref()?
10966 .read_with(cx, |breakpoint_store, cx| {
10967 breakpoint_store
10968 .breakpoints(
10969 &buffer,
10970 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10971 &buffer_snapshot,
10972 cx,
10973 )
10974 .next()
10975 .and_then(|(bp, _)| {
10976 let breakpoint_row = buffer_snapshot
10977 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10978 .row;
10979
10980 if breakpoint_row == row {
10981 snapshot
10982 .buffer_snapshot()
10983 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10984 .map(|position| (position, bp.bp.clone()))
10985 } else {
10986 None
10987 }
10988 })
10989 })
10990 }
10991
10992 pub fn edit_log_breakpoint(
10993 &mut self,
10994 _: &EditLogBreakpoint,
10995 window: &mut Window,
10996 cx: &mut Context<Self>,
10997 ) {
10998 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10999 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11000 message: None,
11001 state: BreakpointState::Enabled,
11002 condition: None,
11003 hit_condition: None,
11004 });
11005
11006 self.add_edit_breakpoint_block(
11007 anchor,
11008 &breakpoint,
11009 BreakpointPromptEditAction::Log,
11010 window,
11011 cx,
11012 );
11013 }
11014 }
11015
11016 fn breakpoints_at_cursors(
11017 &self,
11018 window: &mut Window,
11019 cx: &mut Context<Self>,
11020 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11021 let snapshot = self.snapshot(window, cx);
11022 let cursors = self
11023 .selections
11024 .disjoint_anchors_arc()
11025 .iter()
11026 .map(|selection| {
11027 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11028
11029 let breakpoint_position = self
11030 .breakpoint_at_row(cursor_position.row, window, cx)
11031 .map(|bp| bp.0)
11032 .unwrap_or_else(|| {
11033 snapshot
11034 .display_snapshot
11035 .buffer_snapshot()
11036 .anchor_after(Point::new(cursor_position.row, 0))
11037 });
11038
11039 let breakpoint = self
11040 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11041 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11042
11043 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11044 })
11045 // 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.
11046 .collect::<HashMap<Anchor, _>>();
11047
11048 cursors.into_iter().collect()
11049 }
11050
11051 pub fn enable_breakpoint(
11052 &mut self,
11053 _: &crate::actions::EnableBreakpoint,
11054 window: &mut Window,
11055 cx: &mut Context<Self>,
11056 ) {
11057 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11058 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11059 continue;
11060 };
11061 self.edit_breakpoint_at_anchor(
11062 anchor,
11063 breakpoint,
11064 BreakpointEditAction::InvertState,
11065 cx,
11066 );
11067 }
11068 }
11069
11070 pub fn disable_breakpoint(
11071 &mut self,
11072 _: &crate::actions::DisableBreakpoint,
11073 window: &mut Window,
11074 cx: &mut Context<Self>,
11075 ) {
11076 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11077 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11078 continue;
11079 };
11080 self.edit_breakpoint_at_anchor(
11081 anchor,
11082 breakpoint,
11083 BreakpointEditAction::InvertState,
11084 cx,
11085 );
11086 }
11087 }
11088
11089 pub fn toggle_breakpoint(
11090 &mut self,
11091 _: &crate::actions::ToggleBreakpoint,
11092 window: &mut Window,
11093 cx: &mut Context<Self>,
11094 ) {
11095 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11096 if let Some(breakpoint) = breakpoint {
11097 self.edit_breakpoint_at_anchor(
11098 anchor,
11099 breakpoint,
11100 BreakpointEditAction::Toggle,
11101 cx,
11102 );
11103 } else {
11104 self.edit_breakpoint_at_anchor(
11105 anchor,
11106 Breakpoint::new_standard(),
11107 BreakpointEditAction::Toggle,
11108 cx,
11109 );
11110 }
11111 }
11112 }
11113
11114 pub fn edit_breakpoint_at_anchor(
11115 &mut self,
11116 breakpoint_position: Anchor,
11117 breakpoint: Breakpoint,
11118 edit_action: BreakpointEditAction,
11119 cx: &mut Context<Self>,
11120 ) {
11121 let Some(breakpoint_store) = &self.breakpoint_store else {
11122 return;
11123 };
11124
11125 let Some(buffer) = self
11126 .buffer
11127 .read(cx)
11128 .buffer_for_anchor(breakpoint_position, cx)
11129 else {
11130 return;
11131 };
11132
11133 breakpoint_store.update(cx, |breakpoint_store, cx| {
11134 breakpoint_store.toggle_breakpoint(
11135 buffer,
11136 BreakpointWithPosition {
11137 position: breakpoint_position.text_anchor,
11138 bp: breakpoint,
11139 },
11140 edit_action,
11141 cx,
11142 );
11143 });
11144
11145 cx.notify();
11146 }
11147
11148 #[cfg(any(test, feature = "test-support"))]
11149 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11150 self.breakpoint_store.clone()
11151 }
11152
11153 pub fn prepare_restore_change(
11154 &self,
11155 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11156 hunk: &MultiBufferDiffHunk,
11157 cx: &mut App,
11158 ) -> Option<()> {
11159 if hunk.is_created_file() {
11160 return None;
11161 }
11162 let buffer = self.buffer.read(cx);
11163 let diff = buffer.diff_for(hunk.buffer_id)?;
11164 let buffer = buffer.buffer(hunk.buffer_id)?;
11165 let buffer = buffer.read(cx);
11166 let original_text = diff
11167 .read(cx)
11168 .base_text()
11169 .as_rope()
11170 .slice(hunk.diff_base_byte_range.clone());
11171 let buffer_snapshot = buffer.snapshot();
11172 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11173 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11174 probe
11175 .0
11176 .start
11177 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11178 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11179 }) {
11180 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11181 Some(())
11182 } else {
11183 None
11184 }
11185 }
11186
11187 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11188 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11189 }
11190
11191 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11192 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11193 }
11194
11195 fn manipulate_lines<M>(
11196 &mut self,
11197 window: &mut Window,
11198 cx: &mut Context<Self>,
11199 mut manipulate: M,
11200 ) where
11201 M: FnMut(&str) -> LineManipulationResult,
11202 {
11203 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11204
11205 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11206 let buffer = self.buffer.read(cx).snapshot(cx);
11207
11208 let mut edits = Vec::new();
11209
11210 let selections = self.selections.all::<Point>(cx);
11211 let mut selections = selections.iter().peekable();
11212 let mut contiguous_row_selections = Vec::new();
11213 let mut new_selections = Vec::new();
11214 let mut added_lines = 0;
11215 let mut removed_lines = 0;
11216
11217 while let Some(selection) = selections.next() {
11218 let (start_row, end_row) = consume_contiguous_rows(
11219 &mut contiguous_row_selections,
11220 selection,
11221 &display_map,
11222 &mut selections,
11223 );
11224
11225 let start_point = Point::new(start_row.0, 0);
11226 let end_point = Point::new(
11227 end_row.previous_row().0,
11228 buffer.line_len(end_row.previous_row()),
11229 );
11230 let text = buffer
11231 .text_for_range(start_point..end_point)
11232 .collect::<String>();
11233
11234 let LineManipulationResult {
11235 new_text,
11236 line_count_before,
11237 line_count_after,
11238 } = manipulate(&text);
11239
11240 edits.push((start_point..end_point, new_text));
11241
11242 // Selections must change based on added and removed line count
11243 let start_row =
11244 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11245 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11246 new_selections.push(Selection {
11247 id: selection.id,
11248 start: start_row,
11249 end: end_row,
11250 goal: SelectionGoal::None,
11251 reversed: selection.reversed,
11252 });
11253
11254 if line_count_after > line_count_before {
11255 added_lines += line_count_after - line_count_before;
11256 } else if line_count_before > line_count_after {
11257 removed_lines += line_count_before - line_count_after;
11258 }
11259 }
11260
11261 self.transact(window, cx, |this, window, cx| {
11262 let buffer = this.buffer.update(cx, |buffer, cx| {
11263 buffer.edit(edits, None, cx);
11264 buffer.snapshot(cx)
11265 });
11266
11267 // Recalculate offsets on newly edited buffer
11268 let new_selections = new_selections
11269 .iter()
11270 .map(|s| {
11271 let start_point = Point::new(s.start.0, 0);
11272 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11273 Selection {
11274 id: s.id,
11275 start: buffer.point_to_offset(start_point),
11276 end: buffer.point_to_offset(end_point),
11277 goal: s.goal,
11278 reversed: s.reversed,
11279 }
11280 })
11281 .collect();
11282
11283 this.change_selections(Default::default(), window, cx, |s| {
11284 s.select(new_selections);
11285 });
11286
11287 this.request_autoscroll(Autoscroll::fit(), cx);
11288 });
11289 }
11290
11291 fn manipulate_immutable_lines<Fn>(
11292 &mut self,
11293 window: &mut Window,
11294 cx: &mut Context<Self>,
11295 mut callback: Fn,
11296 ) where
11297 Fn: FnMut(&mut Vec<&str>),
11298 {
11299 self.manipulate_lines(window, cx, |text| {
11300 let mut lines: Vec<&str> = text.split('\n').collect();
11301 let line_count_before = lines.len();
11302
11303 callback(&mut lines);
11304
11305 LineManipulationResult {
11306 new_text: lines.join("\n"),
11307 line_count_before,
11308 line_count_after: lines.len(),
11309 }
11310 });
11311 }
11312
11313 fn manipulate_mutable_lines<Fn>(
11314 &mut self,
11315 window: &mut Window,
11316 cx: &mut Context<Self>,
11317 mut callback: Fn,
11318 ) where
11319 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11320 {
11321 self.manipulate_lines(window, cx, |text| {
11322 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11323 let line_count_before = lines.len();
11324
11325 callback(&mut lines);
11326
11327 LineManipulationResult {
11328 new_text: lines.join("\n"),
11329 line_count_before,
11330 line_count_after: lines.len(),
11331 }
11332 });
11333 }
11334
11335 pub fn convert_indentation_to_spaces(
11336 &mut self,
11337 _: &ConvertIndentationToSpaces,
11338 window: &mut Window,
11339 cx: &mut Context<Self>,
11340 ) {
11341 let settings = self.buffer.read(cx).language_settings(cx);
11342 let tab_size = settings.tab_size.get() as usize;
11343
11344 self.manipulate_mutable_lines(window, cx, |lines| {
11345 // Allocates a reasonably sized scratch buffer once for the whole loop
11346 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11347 // Avoids recomputing spaces that could be inserted many times
11348 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11349 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11350 .collect();
11351
11352 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11353 let mut chars = line.as_ref().chars();
11354 let mut col = 0;
11355 let mut changed = false;
11356
11357 for ch in chars.by_ref() {
11358 match ch {
11359 ' ' => {
11360 reindented_line.push(' ');
11361 col += 1;
11362 }
11363 '\t' => {
11364 // \t are converted to spaces depending on the current column
11365 let spaces_len = tab_size - (col % tab_size);
11366 reindented_line.extend(&space_cache[spaces_len - 1]);
11367 col += spaces_len;
11368 changed = true;
11369 }
11370 _ => {
11371 // If we dont append before break, the character is consumed
11372 reindented_line.push(ch);
11373 break;
11374 }
11375 }
11376 }
11377
11378 if !changed {
11379 reindented_line.clear();
11380 continue;
11381 }
11382 // Append the rest of the line and replace old reference with new one
11383 reindented_line.extend(chars);
11384 *line = Cow::Owned(reindented_line.clone());
11385 reindented_line.clear();
11386 }
11387 });
11388 }
11389
11390 pub fn convert_indentation_to_tabs(
11391 &mut self,
11392 _: &ConvertIndentationToTabs,
11393 window: &mut Window,
11394 cx: &mut Context<Self>,
11395 ) {
11396 let settings = self.buffer.read(cx).language_settings(cx);
11397 let tab_size = settings.tab_size.get() as usize;
11398
11399 self.manipulate_mutable_lines(window, cx, |lines| {
11400 // Allocates a reasonably sized buffer once for the whole loop
11401 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11402 // Avoids recomputing spaces that could be inserted many times
11403 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11404 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11405 .collect();
11406
11407 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11408 let mut chars = line.chars();
11409 let mut spaces_count = 0;
11410 let mut first_non_indent_char = None;
11411 let mut changed = false;
11412
11413 for ch in chars.by_ref() {
11414 match ch {
11415 ' ' => {
11416 // Keep track of spaces. Append \t when we reach tab_size
11417 spaces_count += 1;
11418 changed = true;
11419 if spaces_count == tab_size {
11420 reindented_line.push('\t');
11421 spaces_count = 0;
11422 }
11423 }
11424 '\t' => {
11425 reindented_line.push('\t');
11426 spaces_count = 0;
11427 }
11428 _ => {
11429 // Dont append it yet, we might have remaining spaces
11430 first_non_indent_char = Some(ch);
11431 break;
11432 }
11433 }
11434 }
11435
11436 if !changed {
11437 reindented_line.clear();
11438 continue;
11439 }
11440 // Remaining spaces that didn't make a full tab stop
11441 if spaces_count > 0 {
11442 reindented_line.extend(&space_cache[spaces_count - 1]);
11443 }
11444 // If we consume an extra character that was not indentation, add it back
11445 if let Some(extra_char) = first_non_indent_char {
11446 reindented_line.push(extra_char);
11447 }
11448 // Append the rest of the line and replace old reference with new one
11449 reindented_line.extend(chars);
11450 *line = Cow::Owned(reindented_line.clone());
11451 reindented_line.clear();
11452 }
11453 });
11454 }
11455
11456 pub fn convert_to_upper_case(
11457 &mut self,
11458 _: &ConvertToUpperCase,
11459 window: &mut Window,
11460 cx: &mut Context<Self>,
11461 ) {
11462 self.manipulate_text(window, cx, |text| text.to_uppercase())
11463 }
11464
11465 pub fn convert_to_lower_case(
11466 &mut self,
11467 _: &ConvertToLowerCase,
11468 window: &mut Window,
11469 cx: &mut Context<Self>,
11470 ) {
11471 self.manipulate_text(window, cx, |text| text.to_lowercase())
11472 }
11473
11474 pub fn convert_to_title_case(
11475 &mut self,
11476 _: &ConvertToTitleCase,
11477 window: &mut Window,
11478 cx: &mut Context<Self>,
11479 ) {
11480 self.manipulate_text(window, cx, |text| {
11481 text.split('\n')
11482 .map(|line| line.to_case(Case::Title))
11483 .join("\n")
11484 })
11485 }
11486
11487 pub fn convert_to_snake_case(
11488 &mut self,
11489 _: &ConvertToSnakeCase,
11490 window: &mut Window,
11491 cx: &mut Context<Self>,
11492 ) {
11493 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11494 }
11495
11496 pub fn convert_to_kebab_case(
11497 &mut self,
11498 _: &ConvertToKebabCase,
11499 window: &mut Window,
11500 cx: &mut Context<Self>,
11501 ) {
11502 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11503 }
11504
11505 pub fn convert_to_upper_camel_case(
11506 &mut self,
11507 _: &ConvertToUpperCamelCase,
11508 window: &mut Window,
11509 cx: &mut Context<Self>,
11510 ) {
11511 self.manipulate_text(window, cx, |text| {
11512 text.split('\n')
11513 .map(|line| line.to_case(Case::UpperCamel))
11514 .join("\n")
11515 })
11516 }
11517
11518 pub fn convert_to_lower_camel_case(
11519 &mut self,
11520 _: &ConvertToLowerCamelCase,
11521 window: &mut Window,
11522 cx: &mut Context<Self>,
11523 ) {
11524 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11525 }
11526
11527 pub fn convert_to_opposite_case(
11528 &mut self,
11529 _: &ConvertToOppositeCase,
11530 window: &mut Window,
11531 cx: &mut Context<Self>,
11532 ) {
11533 self.manipulate_text(window, cx, |text| {
11534 text.chars()
11535 .fold(String::with_capacity(text.len()), |mut t, c| {
11536 if c.is_uppercase() {
11537 t.extend(c.to_lowercase());
11538 } else {
11539 t.extend(c.to_uppercase());
11540 }
11541 t
11542 })
11543 })
11544 }
11545
11546 pub fn convert_to_sentence_case(
11547 &mut self,
11548 _: &ConvertToSentenceCase,
11549 window: &mut Window,
11550 cx: &mut Context<Self>,
11551 ) {
11552 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11553 }
11554
11555 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11556 self.manipulate_text(window, cx, |text| {
11557 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11558 if has_upper_case_characters {
11559 text.to_lowercase()
11560 } else {
11561 text.to_uppercase()
11562 }
11563 })
11564 }
11565
11566 pub fn convert_to_rot13(
11567 &mut self,
11568 _: &ConvertToRot13,
11569 window: &mut Window,
11570 cx: &mut Context<Self>,
11571 ) {
11572 self.manipulate_text(window, cx, |text| {
11573 text.chars()
11574 .map(|c| match c {
11575 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11576 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11577 _ => c,
11578 })
11579 .collect()
11580 })
11581 }
11582
11583 pub fn convert_to_rot47(
11584 &mut self,
11585 _: &ConvertToRot47,
11586 window: &mut Window,
11587 cx: &mut Context<Self>,
11588 ) {
11589 self.manipulate_text(window, cx, |text| {
11590 text.chars()
11591 .map(|c| {
11592 let code_point = c as u32;
11593 if code_point >= 33 && code_point <= 126 {
11594 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11595 }
11596 c
11597 })
11598 .collect()
11599 })
11600 }
11601
11602 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11603 where
11604 Fn: FnMut(&str) -> String,
11605 {
11606 let buffer = self.buffer.read(cx).snapshot(cx);
11607
11608 let mut new_selections = Vec::new();
11609 let mut edits = Vec::new();
11610 let mut selection_adjustment = 0i32;
11611
11612 for selection in self.selections.all_adjusted(cx) {
11613 let selection_is_empty = selection.is_empty();
11614
11615 let (start, end) = if selection_is_empty {
11616 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11617 (word_range.start, word_range.end)
11618 } else {
11619 (
11620 buffer.point_to_offset(selection.start),
11621 buffer.point_to_offset(selection.end),
11622 )
11623 };
11624
11625 let text = buffer.text_for_range(start..end).collect::<String>();
11626 let old_length = text.len() as i32;
11627 let text = callback(&text);
11628
11629 new_selections.push(Selection {
11630 start: (start as i32 - selection_adjustment) as usize,
11631 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11632 goal: SelectionGoal::None,
11633 id: selection.id,
11634 reversed: selection.reversed,
11635 });
11636
11637 selection_adjustment += old_length - text.len() as i32;
11638
11639 edits.push((start..end, text));
11640 }
11641
11642 self.transact(window, cx, |this, window, cx| {
11643 this.buffer.update(cx, |buffer, cx| {
11644 buffer.edit(edits, None, cx);
11645 });
11646
11647 this.change_selections(Default::default(), window, cx, |s| {
11648 s.select(new_selections);
11649 });
11650
11651 this.request_autoscroll(Autoscroll::fit(), cx);
11652 });
11653 }
11654
11655 pub fn move_selection_on_drop(
11656 &mut self,
11657 selection: &Selection<Anchor>,
11658 target: DisplayPoint,
11659 is_cut: bool,
11660 window: &mut Window,
11661 cx: &mut Context<Self>,
11662 ) {
11663 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11664 let buffer = display_map.buffer_snapshot();
11665 let mut edits = Vec::new();
11666 let insert_point = display_map
11667 .clip_point(target, Bias::Left)
11668 .to_point(&display_map);
11669 let text = buffer
11670 .text_for_range(selection.start..selection.end)
11671 .collect::<String>();
11672 if is_cut {
11673 edits.push(((selection.start..selection.end), String::new()));
11674 }
11675 let insert_anchor = buffer.anchor_before(insert_point);
11676 edits.push(((insert_anchor..insert_anchor), text));
11677 let last_edit_start = insert_anchor.bias_left(buffer);
11678 let last_edit_end = insert_anchor.bias_right(buffer);
11679 self.transact(window, cx, |this, window, cx| {
11680 this.buffer.update(cx, |buffer, cx| {
11681 buffer.edit(edits, None, cx);
11682 });
11683 this.change_selections(Default::default(), window, cx, |s| {
11684 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11685 });
11686 });
11687 }
11688
11689 pub fn clear_selection_drag_state(&mut self) {
11690 self.selection_drag_state = SelectionDragState::None;
11691 }
11692
11693 pub fn duplicate(
11694 &mut self,
11695 upwards: bool,
11696 whole_lines: bool,
11697 window: &mut Window,
11698 cx: &mut Context<Self>,
11699 ) {
11700 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11701
11702 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11703 let buffer = display_map.buffer_snapshot();
11704 let selections = self.selections.all::<Point>(cx);
11705
11706 let mut edits = Vec::new();
11707 let mut selections_iter = selections.iter().peekable();
11708 while let Some(selection) = selections_iter.next() {
11709 let mut rows = selection.spanned_rows(false, &display_map);
11710 // duplicate line-wise
11711 if whole_lines || selection.start == selection.end {
11712 // Avoid duplicating the same lines twice.
11713 while let Some(next_selection) = selections_iter.peek() {
11714 let next_rows = next_selection.spanned_rows(false, &display_map);
11715 if next_rows.start < rows.end {
11716 rows.end = next_rows.end;
11717 selections_iter.next().unwrap();
11718 } else {
11719 break;
11720 }
11721 }
11722
11723 // Copy the text from the selected row region and splice it either at the start
11724 // or end of the region.
11725 let start = Point::new(rows.start.0, 0);
11726 let end = Point::new(
11727 rows.end.previous_row().0,
11728 buffer.line_len(rows.end.previous_row()),
11729 );
11730
11731 let mut text = buffer.text_for_range(start..end).collect::<String>();
11732
11733 let insert_location = if upwards {
11734 // When duplicating upward, we need to insert before the current line.
11735 // If we're on the last line and it doesn't end with a newline,
11736 // we need to add a newline before the duplicated content.
11737 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11738 && buffer.max_point().column > 0
11739 && !text.ends_with('\n');
11740
11741 if needs_leading_newline {
11742 text.insert(0, '\n');
11743 end
11744 } else {
11745 text.push('\n');
11746 Point::new(rows.end.0, 0)
11747 }
11748 } else {
11749 text.push('\n');
11750 start
11751 };
11752 edits.push((insert_location..insert_location, text));
11753 } else {
11754 // duplicate character-wise
11755 let start = selection.start;
11756 let end = selection.end;
11757 let text = buffer.text_for_range(start..end).collect::<String>();
11758 edits.push((selection.end..selection.end, text));
11759 }
11760 }
11761
11762 self.transact(window, cx, |this, _, cx| {
11763 this.buffer.update(cx, |buffer, cx| {
11764 buffer.edit(edits, None, cx);
11765 });
11766
11767 this.request_autoscroll(Autoscroll::fit(), cx);
11768 });
11769 }
11770
11771 pub fn duplicate_line_up(
11772 &mut self,
11773 _: &DuplicateLineUp,
11774 window: &mut Window,
11775 cx: &mut Context<Self>,
11776 ) {
11777 self.duplicate(true, true, window, cx);
11778 }
11779
11780 pub fn duplicate_line_down(
11781 &mut self,
11782 _: &DuplicateLineDown,
11783 window: &mut Window,
11784 cx: &mut Context<Self>,
11785 ) {
11786 self.duplicate(false, true, window, cx);
11787 }
11788
11789 pub fn duplicate_selection(
11790 &mut self,
11791 _: &DuplicateSelection,
11792 window: &mut Window,
11793 cx: &mut Context<Self>,
11794 ) {
11795 self.duplicate(false, false, window, cx);
11796 }
11797
11798 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11799 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11800 if self.mode.is_single_line() {
11801 cx.propagate();
11802 return;
11803 }
11804
11805 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11806 let buffer = self.buffer.read(cx).snapshot(cx);
11807
11808 let mut edits = Vec::new();
11809 let mut unfold_ranges = Vec::new();
11810 let mut refold_creases = Vec::new();
11811
11812 let selections = self.selections.all::<Point>(cx);
11813 let mut selections = selections.iter().peekable();
11814 let mut contiguous_row_selections = Vec::new();
11815 let mut new_selections = Vec::new();
11816
11817 while let Some(selection) = selections.next() {
11818 // Find all the selections that span a contiguous row range
11819 let (start_row, end_row) = consume_contiguous_rows(
11820 &mut contiguous_row_selections,
11821 selection,
11822 &display_map,
11823 &mut selections,
11824 );
11825
11826 // Move the text spanned by the row range to be before the line preceding the row range
11827 if start_row.0 > 0 {
11828 let range_to_move = Point::new(
11829 start_row.previous_row().0,
11830 buffer.line_len(start_row.previous_row()),
11831 )
11832 ..Point::new(
11833 end_row.previous_row().0,
11834 buffer.line_len(end_row.previous_row()),
11835 );
11836 let insertion_point = display_map
11837 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11838 .0;
11839
11840 // Don't move lines across excerpts
11841 if buffer
11842 .excerpt_containing(insertion_point..range_to_move.end)
11843 .is_some()
11844 {
11845 let text = buffer
11846 .text_for_range(range_to_move.clone())
11847 .flat_map(|s| s.chars())
11848 .skip(1)
11849 .chain(['\n'])
11850 .collect::<String>();
11851
11852 edits.push((
11853 buffer.anchor_after(range_to_move.start)
11854 ..buffer.anchor_before(range_to_move.end),
11855 String::new(),
11856 ));
11857 let insertion_anchor = buffer.anchor_after(insertion_point);
11858 edits.push((insertion_anchor..insertion_anchor, text));
11859
11860 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11861
11862 // Move selections up
11863 new_selections.extend(contiguous_row_selections.drain(..).map(
11864 |mut selection| {
11865 selection.start.row -= row_delta;
11866 selection.end.row -= row_delta;
11867 selection
11868 },
11869 ));
11870
11871 // Move folds up
11872 unfold_ranges.push(range_to_move.clone());
11873 for fold in display_map.folds_in_range(
11874 buffer.anchor_before(range_to_move.start)
11875 ..buffer.anchor_after(range_to_move.end),
11876 ) {
11877 let mut start = fold.range.start.to_point(&buffer);
11878 let mut end = fold.range.end.to_point(&buffer);
11879 start.row -= row_delta;
11880 end.row -= row_delta;
11881 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11882 }
11883 }
11884 }
11885
11886 // If we didn't move line(s), preserve the existing selections
11887 new_selections.append(&mut contiguous_row_selections);
11888 }
11889
11890 self.transact(window, cx, |this, window, cx| {
11891 this.unfold_ranges(&unfold_ranges, true, true, cx);
11892 this.buffer.update(cx, |buffer, cx| {
11893 for (range, text) in edits {
11894 buffer.edit([(range, text)], None, cx);
11895 }
11896 });
11897 this.fold_creases(refold_creases, true, window, cx);
11898 this.change_selections(Default::default(), window, cx, |s| {
11899 s.select(new_selections);
11900 })
11901 });
11902 }
11903
11904 pub fn move_line_down(
11905 &mut self,
11906 _: &MoveLineDown,
11907 window: &mut Window,
11908 cx: &mut Context<Self>,
11909 ) {
11910 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11911 if self.mode.is_single_line() {
11912 cx.propagate();
11913 return;
11914 }
11915
11916 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11917 let buffer = self.buffer.read(cx).snapshot(cx);
11918
11919 let mut edits = Vec::new();
11920 let mut unfold_ranges = Vec::new();
11921 let mut refold_creases = Vec::new();
11922
11923 let selections = self.selections.all::<Point>(cx);
11924 let mut selections = selections.iter().peekable();
11925 let mut contiguous_row_selections = Vec::new();
11926 let mut new_selections = Vec::new();
11927
11928 while let Some(selection) = selections.next() {
11929 // Find all the selections that span a contiguous row range
11930 let (start_row, end_row) = consume_contiguous_rows(
11931 &mut contiguous_row_selections,
11932 selection,
11933 &display_map,
11934 &mut selections,
11935 );
11936
11937 // Move the text spanned by the row range to be after the last line of the row range
11938 if end_row.0 <= buffer.max_point().row {
11939 let range_to_move =
11940 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11941 let insertion_point = display_map
11942 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11943 .0;
11944
11945 // Don't move lines across excerpt boundaries
11946 if buffer
11947 .excerpt_containing(range_to_move.start..insertion_point)
11948 .is_some()
11949 {
11950 let mut text = String::from("\n");
11951 text.extend(buffer.text_for_range(range_to_move.clone()));
11952 text.pop(); // Drop trailing newline
11953 edits.push((
11954 buffer.anchor_after(range_to_move.start)
11955 ..buffer.anchor_before(range_to_move.end),
11956 String::new(),
11957 ));
11958 let insertion_anchor = buffer.anchor_after(insertion_point);
11959 edits.push((insertion_anchor..insertion_anchor, text));
11960
11961 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11962
11963 // Move selections down
11964 new_selections.extend(contiguous_row_selections.drain(..).map(
11965 |mut selection| {
11966 selection.start.row += row_delta;
11967 selection.end.row += row_delta;
11968 selection
11969 },
11970 ));
11971
11972 // Move folds down
11973 unfold_ranges.push(range_to_move.clone());
11974 for fold in display_map.folds_in_range(
11975 buffer.anchor_before(range_to_move.start)
11976 ..buffer.anchor_after(range_to_move.end),
11977 ) {
11978 let mut start = fold.range.start.to_point(&buffer);
11979 let mut end = fold.range.end.to_point(&buffer);
11980 start.row += row_delta;
11981 end.row += row_delta;
11982 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11983 }
11984 }
11985 }
11986
11987 // If we didn't move line(s), preserve the existing selections
11988 new_selections.append(&mut contiguous_row_selections);
11989 }
11990
11991 self.transact(window, cx, |this, window, cx| {
11992 this.unfold_ranges(&unfold_ranges, true, true, cx);
11993 this.buffer.update(cx, |buffer, cx| {
11994 for (range, text) in edits {
11995 buffer.edit([(range, text)], None, cx);
11996 }
11997 });
11998 this.fold_creases(refold_creases, true, window, cx);
11999 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12000 });
12001 }
12002
12003 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12004 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12005 let text_layout_details = &self.text_layout_details(window);
12006 self.transact(window, cx, |this, window, cx| {
12007 let edits = this.change_selections(Default::default(), window, cx, |s| {
12008 let mut edits: Vec<(Range<usize>, String)> = Default::default();
12009 s.move_with(|display_map, selection| {
12010 if !selection.is_empty() {
12011 return;
12012 }
12013
12014 let mut head = selection.head();
12015 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12016 if head.column() == display_map.line_len(head.row()) {
12017 transpose_offset = display_map
12018 .buffer_snapshot()
12019 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12020 }
12021
12022 if transpose_offset == 0 {
12023 return;
12024 }
12025
12026 *head.column_mut() += 1;
12027 head = display_map.clip_point(head, Bias::Right);
12028 let goal = SelectionGoal::HorizontalPosition(
12029 display_map
12030 .x_for_display_point(head, text_layout_details)
12031 .into(),
12032 );
12033 selection.collapse_to(head, goal);
12034
12035 let transpose_start = display_map
12036 .buffer_snapshot()
12037 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12038 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12039 let transpose_end = display_map
12040 .buffer_snapshot()
12041 .clip_offset(transpose_offset + 1, Bias::Right);
12042 if let Some(ch) = display_map
12043 .buffer_snapshot()
12044 .chars_at(transpose_start)
12045 .next()
12046 {
12047 edits.push((transpose_start..transpose_offset, String::new()));
12048 edits.push((transpose_end..transpose_end, ch.to_string()));
12049 }
12050 }
12051 });
12052 edits
12053 });
12054 this.buffer
12055 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12056 let selections = this.selections.all::<usize>(cx);
12057 this.change_selections(Default::default(), window, cx, |s| {
12058 s.select(selections);
12059 });
12060 });
12061 }
12062
12063 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12064 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12065 if self.mode.is_single_line() {
12066 cx.propagate();
12067 return;
12068 }
12069
12070 self.rewrap_impl(RewrapOptions::default(), cx)
12071 }
12072
12073 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12074 let buffer = self.buffer.read(cx).snapshot(cx);
12075 let selections = self.selections.all::<Point>(cx);
12076
12077 #[derive(Clone, Debug, PartialEq)]
12078 enum CommentFormat {
12079 /// single line comment, with prefix for line
12080 Line(String),
12081 /// single line within a block comment, with prefix for line
12082 BlockLine(String),
12083 /// a single line of a block comment that includes the initial delimiter
12084 BlockCommentWithStart(BlockCommentConfig),
12085 /// a single line of a block comment that includes the ending delimiter
12086 BlockCommentWithEnd(BlockCommentConfig),
12087 }
12088
12089 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12090 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12091 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12092 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12093 .peekable();
12094
12095 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12096 row
12097 } else {
12098 return Vec::new();
12099 };
12100
12101 let language_settings = buffer.language_settings_at(selection.head(), cx);
12102 let language_scope = buffer.language_scope_at(selection.head());
12103
12104 let indent_and_prefix_for_row =
12105 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12106 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12107 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12108 &language_scope
12109 {
12110 let indent_end = Point::new(row, indent.len);
12111 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12112 let line_text_after_indent = buffer
12113 .text_for_range(indent_end..line_end)
12114 .collect::<String>();
12115
12116 let is_within_comment_override = buffer
12117 .language_scope_at(indent_end)
12118 .is_some_and(|scope| scope.override_name() == Some("comment"));
12119 let comment_delimiters = if is_within_comment_override {
12120 // we are within a comment syntax node, but we don't
12121 // yet know what kind of comment: block, doc or line
12122 match (
12123 language_scope.documentation_comment(),
12124 language_scope.block_comment(),
12125 ) {
12126 (Some(config), _) | (_, Some(config))
12127 if buffer.contains_str_at(indent_end, &config.start) =>
12128 {
12129 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12130 }
12131 (Some(config), _) | (_, Some(config))
12132 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12133 {
12134 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12135 }
12136 (Some(config), _) | (_, Some(config))
12137 if buffer.contains_str_at(indent_end, &config.prefix) =>
12138 {
12139 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12140 }
12141 (_, _) => language_scope
12142 .line_comment_prefixes()
12143 .iter()
12144 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12145 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12146 }
12147 } else {
12148 // we not in an overridden comment node, but we may
12149 // be within a non-overridden line comment node
12150 language_scope
12151 .line_comment_prefixes()
12152 .iter()
12153 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12154 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12155 };
12156
12157 let rewrap_prefix = language_scope
12158 .rewrap_prefixes()
12159 .iter()
12160 .find_map(|prefix_regex| {
12161 prefix_regex.find(&line_text_after_indent).map(|mat| {
12162 if mat.start() == 0 {
12163 Some(mat.as_str().to_string())
12164 } else {
12165 None
12166 }
12167 })
12168 })
12169 .flatten();
12170 (comment_delimiters, rewrap_prefix)
12171 } else {
12172 (None, None)
12173 };
12174 (indent, comment_prefix, rewrap_prefix)
12175 };
12176
12177 let mut ranges = Vec::new();
12178 let from_empty_selection = selection.is_empty();
12179
12180 let mut current_range_start = first_row;
12181 let mut prev_row = first_row;
12182 let (
12183 mut current_range_indent,
12184 mut current_range_comment_delimiters,
12185 mut current_range_rewrap_prefix,
12186 ) = indent_and_prefix_for_row(first_row);
12187
12188 for row in non_blank_rows_iter.skip(1) {
12189 let has_paragraph_break = row > prev_row + 1;
12190
12191 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12192 indent_and_prefix_for_row(row);
12193
12194 let has_indent_change = row_indent != current_range_indent;
12195 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12196
12197 let has_boundary_change = has_comment_change
12198 || row_rewrap_prefix.is_some()
12199 || (has_indent_change && current_range_comment_delimiters.is_some());
12200
12201 if has_paragraph_break || has_boundary_change {
12202 ranges.push((
12203 language_settings.clone(),
12204 Point::new(current_range_start, 0)
12205 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12206 current_range_indent,
12207 current_range_comment_delimiters.clone(),
12208 current_range_rewrap_prefix.clone(),
12209 from_empty_selection,
12210 ));
12211 current_range_start = row;
12212 current_range_indent = row_indent;
12213 current_range_comment_delimiters = row_comment_delimiters;
12214 current_range_rewrap_prefix = row_rewrap_prefix;
12215 }
12216 prev_row = row;
12217 }
12218
12219 ranges.push((
12220 language_settings.clone(),
12221 Point::new(current_range_start, 0)
12222 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12223 current_range_indent,
12224 current_range_comment_delimiters,
12225 current_range_rewrap_prefix,
12226 from_empty_selection,
12227 ));
12228
12229 ranges
12230 });
12231
12232 let mut edits = Vec::new();
12233 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12234
12235 for (
12236 language_settings,
12237 wrap_range,
12238 mut indent_size,
12239 comment_prefix,
12240 rewrap_prefix,
12241 from_empty_selection,
12242 ) in wrap_ranges
12243 {
12244 let mut start_row = wrap_range.start.row;
12245 let mut end_row = wrap_range.end.row;
12246
12247 // Skip selections that overlap with a range that has already been rewrapped.
12248 let selection_range = start_row..end_row;
12249 if rewrapped_row_ranges
12250 .iter()
12251 .any(|range| range.overlaps(&selection_range))
12252 {
12253 continue;
12254 }
12255
12256 let tab_size = language_settings.tab_size;
12257
12258 let (line_prefix, inside_comment) = match &comment_prefix {
12259 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12260 (Some(prefix.as_str()), true)
12261 }
12262 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12263 (Some(prefix.as_ref()), true)
12264 }
12265 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12266 start: _,
12267 end: _,
12268 prefix,
12269 tab_size,
12270 })) => {
12271 indent_size.len += tab_size;
12272 (Some(prefix.as_ref()), true)
12273 }
12274 None => (None, false),
12275 };
12276 let indent_prefix = indent_size.chars().collect::<String>();
12277 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12278
12279 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12280 RewrapBehavior::InComments => inside_comment,
12281 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12282 RewrapBehavior::Anywhere => true,
12283 };
12284
12285 let should_rewrap = options.override_language_settings
12286 || allow_rewrap_based_on_language
12287 || self.hard_wrap.is_some();
12288 if !should_rewrap {
12289 continue;
12290 }
12291
12292 if from_empty_selection {
12293 'expand_upwards: while start_row > 0 {
12294 let prev_row = start_row - 1;
12295 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12296 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12297 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12298 {
12299 start_row = prev_row;
12300 } else {
12301 break 'expand_upwards;
12302 }
12303 }
12304
12305 'expand_downwards: while end_row < buffer.max_point().row {
12306 let next_row = end_row + 1;
12307 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12308 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12309 && !buffer.is_line_blank(MultiBufferRow(next_row))
12310 {
12311 end_row = next_row;
12312 } else {
12313 break 'expand_downwards;
12314 }
12315 }
12316 }
12317
12318 let start = Point::new(start_row, 0);
12319 let start_offset = ToOffset::to_offset(&start, &buffer);
12320 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12321 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12322 let mut first_line_delimiter = None;
12323 let mut last_line_delimiter = None;
12324 let Some(lines_without_prefixes) = selection_text
12325 .lines()
12326 .enumerate()
12327 .map(|(ix, line)| {
12328 let line_trimmed = line.trim_start();
12329 if rewrap_prefix.is_some() && ix > 0 {
12330 Ok(line_trimmed)
12331 } else if let Some(
12332 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12333 start,
12334 prefix,
12335 end,
12336 tab_size,
12337 })
12338 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12339 start,
12340 prefix,
12341 end,
12342 tab_size,
12343 }),
12344 ) = &comment_prefix
12345 {
12346 let line_trimmed = line_trimmed
12347 .strip_prefix(start.as_ref())
12348 .map(|s| {
12349 let mut indent_size = indent_size;
12350 indent_size.len -= tab_size;
12351 let indent_prefix: String = indent_size.chars().collect();
12352 first_line_delimiter = Some((indent_prefix, start));
12353 s.trim_start()
12354 })
12355 .unwrap_or(line_trimmed);
12356 let line_trimmed = line_trimmed
12357 .strip_suffix(end.as_ref())
12358 .map(|s| {
12359 last_line_delimiter = Some(end);
12360 s.trim_end()
12361 })
12362 .unwrap_or(line_trimmed);
12363 let line_trimmed = line_trimmed
12364 .strip_prefix(prefix.as_ref())
12365 .unwrap_or(line_trimmed);
12366 Ok(line_trimmed)
12367 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12368 line_trimmed.strip_prefix(prefix).with_context(|| {
12369 format!("line did not start with prefix {prefix:?}: {line:?}")
12370 })
12371 } else {
12372 line_trimmed
12373 .strip_prefix(&line_prefix.trim_start())
12374 .with_context(|| {
12375 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12376 })
12377 }
12378 })
12379 .collect::<Result<Vec<_>, _>>()
12380 .log_err()
12381 else {
12382 continue;
12383 };
12384
12385 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12386 buffer
12387 .language_settings_at(Point::new(start_row, 0), cx)
12388 .preferred_line_length as usize
12389 });
12390
12391 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12392 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12393 } else {
12394 line_prefix.clone()
12395 };
12396
12397 let wrapped_text = {
12398 let mut wrapped_text = wrap_with_prefix(
12399 line_prefix,
12400 subsequent_lines_prefix,
12401 lines_without_prefixes.join("\n"),
12402 wrap_column,
12403 tab_size,
12404 options.preserve_existing_whitespace,
12405 );
12406
12407 if let Some((indent, delimiter)) = first_line_delimiter {
12408 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12409 }
12410 if let Some(last_line) = last_line_delimiter {
12411 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12412 }
12413
12414 wrapped_text
12415 };
12416
12417 // TODO: should always use char-based diff while still supporting cursor behavior that
12418 // matches vim.
12419 let mut diff_options = DiffOptions::default();
12420 if options.override_language_settings {
12421 diff_options.max_word_diff_len = 0;
12422 diff_options.max_word_diff_line_count = 0;
12423 } else {
12424 diff_options.max_word_diff_len = usize::MAX;
12425 diff_options.max_word_diff_line_count = usize::MAX;
12426 }
12427
12428 for (old_range, new_text) in
12429 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12430 {
12431 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12432 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12433 edits.push((edit_start..edit_end, new_text));
12434 }
12435
12436 rewrapped_row_ranges.push(start_row..=end_row);
12437 }
12438
12439 self.buffer
12440 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12441 }
12442
12443 pub fn cut_common(
12444 &mut self,
12445 cut_no_selection_line: bool,
12446 window: &mut Window,
12447 cx: &mut Context<Self>,
12448 ) -> ClipboardItem {
12449 let mut text = String::new();
12450 let buffer = self.buffer.read(cx).snapshot(cx);
12451 let mut selections = self.selections.all::<Point>(cx);
12452 let mut clipboard_selections = Vec::with_capacity(selections.len());
12453 {
12454 let max_point = buffer.max_point();
12455 let mut is_first = true;
12456 for selection in &mut selections {
12457 let is_entire_line =
12458 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12459 if is_entire_line {
12460 selection.start = Point::new(selection.start.row, 0);
12461 if !selection.is_empty() && selection.end.column == 0 {
12462 selection.end = cmp::min(max_point, selection.end);
12463 } else {
12464 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12465 }
12466 selection.goal = SelectionGoal::None;
12467 }
12468 if is_first {
12469 is_first = false;
12470 } else {
12471 text += "\n";
12472 }
12473 let mut len = 0;
12474 for chunk in buffer.text_for_range(selection.start..selection.end) {
12475 text.push_str(chunk);
12476 len += chunk.len();
12477 }
12478 clipboard_selections.push(ClipboardSelection {
12479 len,
12480 is_entire_line,
12481 first_line_indent: buffer
12482 .indent_size_for_line(MultiBufferRow(selection.start.row))
12483 .len,
12484 });
12485 }
12486 }
12487
12488 self.transact(window, cx, |this, window, cx| {
12489 this.change_selections(Default::default(), window, cx, |s| {
12490 s.select(selections);
12491 });
12492 this.insert("", window, cx);
12493 });
12494 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12495 }
12496
12497 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12498 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12499 let item = self.cut_common(true, window, cx);
12500 cx.write_to_clipboard(item);
12501 }
12502
12503 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12504 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12505 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12506 s.move_with(|snapshot, sel| {
12507 if sel.is_empty() {
12508 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12509 }
12510 if sel.is_empty() {
12511 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12512 }
12513 });
12514 });
12515 let item = self.cut_common(false, window, cx);
12516 cx.set_global(KillRing(item))
12517 }
12518
12519 pub fn kill_ring_yank(
12520 &mut self,
12521 _: &KillRingYank,
12522 window: &mut Window,
12523 cx: &mut Context<Self>,
12524 ) {
12525 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12526 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12527 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12528 (kill_ring.text().to_string(), kill_ring.metadata_json())
12529 } else {
12530 return;
12531 }
12532 } else {
12533 return;
12534 };
12535 self.do_paste(&text, metadata, false, window, cx);
12536 }
12537
12538 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12539 self.do_copy(true, cx);
12540 }
12541
12542 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12543 self.do_copy(false, cx);
12544 }
12545
12546 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12547 let selections = self.selections.all::<Point>(cx);
12548 let buffer = self.buffer.read(cx).read(cx);
12549 let mut text = String::new();
12550
12551 let mut clipboard_selections = Vec::with_capacity(selections.len());
12552 {
12553 let max_point = buffer.max_point();
12554 let mut is_first = true;
12555 for selection in &selections {
12556 let mut start = selection.start;
12557 let mut end = selection.end;
12558 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12559 let mut add_trailing_newline = false;
12560 if is_entire_line {
12561 start = Point::new(start.row, 0);
12562 let next_line_start = Point::new(end.row + 1, 0);
12563 if next_line_start <= max_point {
12564 end = next_line_start;
12565 } else {
12566 // We're on the last line without a trailing newline.
12567 // Copy to the end of the line and add a newline afterwards.
12568 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12569 add_trailing_newline = true;
12570 }
12571 }
12572
12573 let mut trimmed_selections = Vec::new();
12574 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12575 let row = MultiBufferRow(start.row);
12576 let first_indent = buffer.indent_size_for_line(row);
12577 if first_indent.len == 0 || start.column > first_indent.len {
12578 trimmed_selections.push(start..end);
12579 } else {
12580 trimmed_selections.push(
12581 Point::new(row.0, first_indent.len)
12582 ..Point::new(row.0, buffer.line_len(row)),
12583 );
12584 for row in start.row + 1..=end.row {
12585 let mut line_len = buffer.line_len(MultiBufferRow(row));
12586 if row == end.row {
12587 line_len = end.column;
12588 }
12589 if line_len == 0 {
12590 trimmed_selections
12591 .push(Point::new(row, 0)..Point::new(row, line_len));
12592 continue;
12593 }
12594 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12595 if row_indent_size.len >= first_indent.len {
12596 trimmed_selections.push(
12597 Point::new(row, first_indent.len)..Point::new(row, line_len),
12598 );
12599 } else {
12600 trimmed_selections.clear();
12601 trimmed_selections.push(start..end);
12602 break;
12603 }
12604 }
12605 }
12606 } else {
12607 trimmed_selections.push(start..end);
12608 }
12609
12610 for trimmed_range in trimmed_selections {
12611 if is_first {
12612 is_first = false;
12613 } else {
12614 text += "\n";
12615 }
12616 let mut len = 0;
12617 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12618 text.push_str(chunk);
12619 len += chunk.len();
12620 }
12621 if add_trailing_newline {
12622 text.push('\n');
12623 len += 1;
12624 }
12625 clipboard_selections.push(ClipboardSelection {
12626 len,
12627 is_entire_line,
12628 first_line_indent: buffer
12629 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12630 .len,
12631 });
12632 }
12633 }
12634 }
12635
12636 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12637 text,
12638 clipboard_selections,
12639 ));
12640 }
12641
12642 pub fn do_paste(
12643 &mut self,
12644 text: &String,
12645 clipboard_selections: Option<Vec<ClipboardSelection>>,
12646 handle_entire_lines: bool,
12647 window: &mut Window,
12648 cx: &mut Context<Self>,
12649 ) {
12650 if self.read_only(cx) {
12651 return;
12652 }
12653
12654 let clipboard_text = Cow::Borrowed(text.as_str());
12655
12656 self.transact(window, cx, |this, window, cx| {
12657 let had_active_edit_prediction = this.has_active_edit_prediction();
12658 let old_selections = this.selections.all::<usize>(cx);
12659 let cursor_offset = this.selections.last::<usize>(cx).head();
12660
12661 if let Some(mut clipboard_selections) = clipboard_selections {
12662 let all_selections_were_entire_line =
12663 clipboard_selections.iter().all(|s| s.is_entire_line);
12664 let first_selection_indent_column =
12665 clipboard_selections.first().map(|s| s.first_line_indent);
12666 if clipboard_selections.len() != old_selections.len() {
12667 clipboard_selections.drain(..);
12668 }
12669 let mut auto_indent_on_paste = true;
12670
12671 this.buffer.update(cx, |buffer, cx| {
12672 let snapshot = buffer.read(cx);
12673 auto_indent_on_paste = snapshot
12674 .language_settings_at(cursor_offset, cx)
12675 .auto_indent_on_paste;
12676
12677 let mut start_offset = 0;
12678 let mut edits = Vec::new();
12679 let mut original_indent_columns = Vec::new();
12680 for (ix, selection) in old_selections.iter().enumerate() {
12681 let to_insert;
12682 let entire_line;
12683 let original_indent_column;
12684 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12685 let end_offset = start_offset + clipboard_selection.len;
12686 to_insert = &clipboard_text[start_offset..end_offset];
12687 entire_line = clipboard_selection.is_entire_line;
12688 start_offset = end_offset + 1;
12689 original_indent_column = Some(clipboard_selection.first_line_indent);
12690 } else {
12691 to_insert = &*clipboard_text;
12692 entire_line = all_selections_were_entire_line;
12693 original_indent_column = first_selection_indent_column
12694 }
12695
12696 let (range, to_insert) =
12697 if selection.is_empty() && handle_entire_lines && entire_line {
12698 // If the corresponding selection was empty when this slice of the
12699 // clipboard text was written, then the entire line containing the
12700 // selection was copied. If this selection is also currently empty,
12701 // then paste the line before the current line of the buffer.
12702 let column = selection.start.to_point(&snapshot).column as usize;
12703 let line_start = selection.start - column;
12704 (line_start..line_start, Cow::Borrowed(to_insert))
12705 } else {
12706 let language = snapshot.language_at(selection.head());
12707 let range = selection.range();
12708 if let Some(language) = language
12709 && language.name() == "Markdown".into()
12710 {
12711 edit_for_markdown_paste(
12712 &snapshot,
12713 range,
12714 to_insert,
12715 url::Url::parse(to_insert).ok(),
12716 )
12717 } else {
12718 (range, Cow::Borrowed(to_insert))
12719 }
12720 };
12721
12722 edits.push((range, to_insert));
12723 original_indent_columns.push(original_indent_column);
12724 }
12725 drop(snapshot);
12726
12727 buffer.edit(
12728 edits,
12729 if auto_indent_on_paste {
12730 Some(AutoindentMode::Block {
12731 original_indent_columns,
12732 })
12733 } else {
12734 None
12735 },
12736 cx,
12737 );
12738 });
12739
12740 let selections = this.selections.all::<usize>(cx);
12741 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12742 } else {
12743 let url = url::Url::parse(&clipboard_text).ok();
12744
12745 let auto_indent_mode = if !clipboard_text.is_empty() {
12746 Some(AutoindentMode::Block {
12747 original_indent_columns: Vec::new(),
12748 })
12749 } else {
12750 None
12751 };
12752
12753 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12754 let snapshot = buffer.snapshot(cx);
12755
12756 let anchors = old_selections
12757 .iter()
12758 .map(|s| {
12759 let anchor = snapshot.anchor_after(s.head());
12760 s.map(|_| anchor)
12761 })
12762 .collect::<Vec<_>>();
12763
12764 let mut edits = Vec::new();
12765
12766 for selection in old_selections.iter() {
12767 let language = snapshot.language_at(selection.head());
12768 let range = selection.range();
12769
12770 let (edit_range, edit_text) = if let Some(language) = language
12771 && language.name() == "Markdown".into()
12772 {
12773 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12774 } else {
12775 (range, clipboard_text.clone())
12776 };
12777
12778 edits.push((edit_range, edit_text));
12779 }
12780
12781 drop(snapshot);
12782 buffer.edit(edits, auto_indent_mode, cx);
12783
12784 anchors
12785 });
12786
12787 this.change_selections(Default::default(), window, cx, |s| {
12788 s.select_anchors(selection_anchors);
12789 });
12790 }
12791
12792 let trigger_in_words =
12793 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12794
12795 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12796 });
12797 }
12798
12799 pub fn diff_clipboard_with_selection(
12800 &mut self,
12801 _: &DiffClipboardWithSelection,
12802 window: &mut Window,
12803 cx: &mut Context<Self>,
12804 ) {
12805 let selections = self.selections.all::<usize>(cx);
12806
12807 if selections.is_empty() {
12808 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12809 return;
12810 };
12811
12812 let clipboard_text = match cx.read_from_clipboard() {
12813 Some(item) => match item.entries().first() {
12814 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12815 _ => None,
12816 },
12817 None => None,
12818 };
12819
12820 let Some(clipboard_text) = clipboard_text else {
12821 log::warn!("Clipboard doesn't contain text.");
12822 return;
12823 };
12824
12825 window.dispatch_action(
12826 Box::new(DiffClipboardWithSelectionData {
12827 clipboard_text,
12828 editor: cx.entity(),
12829 }),
12830 cx,
12831 );
12832 }
12833
12834 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12835 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12836 if let Some(item) = cx.read_from_clipboard() {
12837 let entries = item.entries();
12838
12839 match entries.first() {
12840 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12841 // of all the pasted entries.
12842 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12843 .do_paste(
12844 clipboard_string.text(),
12845 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12846 true,
12847 window,
12848 cx,
12849 ),
12850 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12851 }
12852 }
12853 }
12854
12855 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12856 if self.read_only(cx) {
12857 return;
12858 }
12859
12860 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12861
12862 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12863 if let Some((selections, _)) =
12864 self.selection_history.transaction(transaction_id).cloned()
12865 {
12866 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12867 s.select_anchors(selections.to_vec());
12868 });
12869 } else {
12870 log::error!(
12871 "No entry in selection_history found for undo. \
12872 This may correspond to a bug where undo does not update the selection. \
12873 If this is occurring, please add details to \
12874 https://github.com/zed-industries/zed/issues/22692"
12875 );
12876 }
12877 self.request_autoscroll(Autoscroll::fit(), cx);
12878 self.unmark_text(window, cx);
12879 self.refresh_edit_prediction(true, false, window, cx);
12880 cx.emit(EditorEvent::Edited { transaction_id });
12881 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12882 }
12883 }
12884
12885 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12886 if self.read_only(cx) {
12887 return;
12888 }
12889
12890 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12891
12892 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12893 if let Some((_, Some(selections))) =
12894 self.selection_history.transaction(transaction_id).cloned()
12895 {
12896 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12897 s.select_anchors(selections.to_vec());
12898 });
12899 } else {
12900 log::error!(
12901 "No entry in selection_history found for redo. \
12902 This may correspond to a bug where undo does not update the selection. \
12903 If this is occurring, please add details to \
12904 https://github.com/zed-industries/zed/issues/22692"
12905 );
12906 }
12907 self.request_autoscroll(Autoscroll::fit(), cx);
12908 self.unmark_text(window, cx);
12909 self.refresh_edit_prediction(true, false, window, cx);
12910 cx.emit(EditorEvent::Edited { transaction_id });
12911 }
12912 }
12913
12914 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12915 self.buffer
12916 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12917 }
12918
12919 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12920 self.buffer
12921 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12922 }
12923
12924 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12925 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12926 self.change_selections(Default::default(), window, cx, |s| {
12927 s.move_with(|map, selection| {
12928 let cursor = if selection.is_empty() {
12929 movement::left(map, selection.start)
12930 } else {
12931 selection.start
12932 };
12933 selection.collapse_to(cursor, SelectionGoal::None);
12934 });
12935 })
12936 }
12937
12938 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12939 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12940 self.change_selections(Default::default(), window, cx, |s| {
12941 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12942 })
12943 }
12944
12945 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12946 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12947 self.change_selections(Default::default(), window, cx, |s| {
12948 s.move_with(|map, selection| {
12949 let cursor = if selection.is_empty() {
12950 movement::right(map, selection.end)
12951 } else {
12952 selection.end
12953 };
12954 selection.collapse_to(cursor, SelectionGoal::None)
12955 });
12956 })
12957 }
12958
12959 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12960 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12961 self.change_selections(Default::default(), window, cx, |s| {
12962 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12963 });
12964 }
12965
12966 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12967 if self.take_rename(true, window, cx).is_some() {
12968 return;
12969 }
12970
12971 if self.mode.is_single_line() {
12972 cx.propagate();
12973 return;
12974 }
12975
12976 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12977
12978 let text_layout_details = &self.text_layout_details(window);
12979 let selection_count = self.selections.count();
12980 let first_selection = self.selections.first_anchor();
12981
12982 self.change_selections(Default::default(), window, cx, |s| {
12983 s.move_with(|map, selection| {
12984 if !selection.is_empty() {
12985 selection.goal = SelectionGoal::None;
12986 }
12987 let (cursor, goal) = movement::up(
12988 map,
12989 selection.start,
12990 selection.goal,
12991 false,
12992 text_layout_details,
12993 );
12994 selection.collapse_to(cursor, goal);
12995 });
12996 });
12997
12998 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12999 {
13000 cx.propagate();
13001 }
13002 }
13003
13004 pub fn move_up_by_lines(
13005 &mut self,
13006 action: &MoveUpByLines,
13007 window: &mut Window,
13008 cx: &mut Context<Self>,
13009 ) {
13010 if self.take_rename(true, window, cx).is_some() {
13011 return;
13012 }
13013
13014 if self.mode.is_single_line() {
13015 cx.propagate();
13016 return;
13017 }
13018
13019 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13020
13021 let text_layout_details = &self.text_layout_details(window);
13022
13023 self.change_selections(Default::default(), window, cx, |s| {
13024 s.move_with(|map, selection| {
13025 if !selection.is_empty() {
13026 selection.goal = SelectionGoal::None;
13027 }
13028 let (cursor, goal) = movement::up_by_rows(
13029 map,
13030 selection.start,
13031 action.lines,
13032 selection.goal,
13033 false,
13034 text_layout_details,
13035 );
13036 selection.collapse_to(cursor, goal);
13037 });
13038 })
13039 }
13040
13041 pub fn move_down_by_lines(
13042 &mut self,
13043 action: &MoveDownByLines,
13044 window: &mut Window,
13045 cx: &mut Context<Self>,
13046 ) {
13047 if self.take_rename(true, window, cx).is_some() {
13048 return;
13049 }
13050
13051 if self.mode.is_single_line() {
13052 cx.propagate();
13053 return;
13054 }
13055
13056 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13057
13058 let text_layout_details = &self.text_layout_details(window);
13059
13060 self.change_selections(Default::default(), window, cx, |s| {
13061 s.move_with(|map, selection| {
13062 if !selection.is_empty() {
13063 selection.goal = SelectionGoal::None;
13064 }
13065 let (cursor, goal) = movement::down_by_rows(
13066 map,
13067 selection.start,
13068 action.lines,
13069 selection.goal,
13070 false,
13071 text_layout_details,
13072 );
13073 selection.collapse_to(cursor, goal);
13074 });
13075 })
13076 }
13077
13078 pub fn select_down_by_lines(
13079 &mut self,
13080 action: &SelectDownByLines,
13081 window: &mut Window,
13082 cx: &mut Context<Self>,
13083 ) {
13084 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13085 let text_layout_details = &self.text_layout_details(window);
13086 self.change_selections(Default::default(), window, cx, |s| {
13087 s.move_heads_with(|map, head, goal| {
13088 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13089 })
13090 })
13091 }
13092
13093 pub fn select_up_by_lines(
13094 &mut self,
13095 action: &SelectUpByLines,
13096 window: &mut Window,
13097 cx: &mut Context<Self>,
13098 ) {
13099 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13100 let text_layout_details = &self.text_layout_details(window);
13101 self.change_selections(Default::default(), window, cx, |s| {
13102 s.move_heads_with(|map, head, goal| {
13103 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13104 })
13105 })
13106 }
13107
13108 pub fn select_page_up(
13109 &mut self,
13110 _: &SelectPageUp,
13111 window: &mut Window,
13112 cx: &mut Context<Self>,
13113 ) {
13114 let Some(row_count) = self.visible_row_count() else {
13115 return;
13116 };
13117
13118 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13119
13120 let text_layout_details = &self.text_layout_details(window);
13121
13122 self.change_selections(Default::default(), window, cx, |s| {
13123 s.move_heads_with(|map, head, goal| {
13124 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13125 })
13126 })
13127 }
13128
13129 pub fn move_page_up(
13130 &mut self,
13131 action: &MovePageUp,
13132 window: &mut Window,
13133 cx: &mut Context<Self>,
13134 ) {
13135 if self.take_rename(true, window, cx).is_some() {
13136 return;
13137 }
13138
13139 if self
13140 .context_menu
13141 .borrow_mut()
13142 .as_mut()
13143 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13144 .unwrap_or(false)
13145 {
13146 return;
13147 }
13148
13149 if matches!(self.mode, EditorMode::SingleLine) {
13150 cx.propagate();
13151 return;
13152 }
13153
13154 let Some(row_count) = self.visible_row_count() else {
13155 return;
13156 };
13157
13158 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13159
13160 let effects = if action.center_cursor {
13161 SelectionEffects::scroll(Autoscroll::center())
13162 } else {
13163 SelectionEffects::default()
13164 };
13165
13166 let text_layout_details = &self.text_layout_details(window);
13167
13168 self.change_selections(effects, window, cx, |s| {
13169 s.move_with(|map, selection| {
13170 if !selection.is_empty() {
13171 selection.goal = SelectionGoal::None;
13172 }
13173 let (cursor, goal) = movement::up_by_rows(
13174 map,
13175 selection.end,
13176 row_count,
13177 selection.goal,
13178 false,
13179 text_layout_details,
13180 );
13181 selection.collapse_to(cursor, goal);
13182 });
13183 });
13184 }
13185
13186 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13187 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13188 let text_layout_details = &self.text_layout_details(window);
13189 self.change_selections(Default::default(), window, cx, |s| {
13190 s.move_heads_with(|map, head, goal| {
13191 movement::up(map, head, goal, false, text_layout_details)
13192 })
13193 })
13194 }
13195
13196 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13197 self.take_rename(true, window, cx);
13198
13199 if self.mode.is_single_line() {
13200 cx.propagate();
13201 return;
13202 }
13203
13204 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13205
13206 let text_layout_details = &self.text_layout_details(window);
13207 let selection_count = self.selections.count();
13208 let first_selection = self.selections.first_anchor();
13209
13210 self.change_selections(Default::default(), window, cx, |s| {
13211 s.move_with(|map, selection| {
13212 if !selection.is_empty() {
13213 selection.goal = SelectionGoal::None;
13214 }
13215 let (cursor, goal) = movement::down(
13216 map,
13217 selection.end,
13218 selection.goal,
13219 false,
13220 text_layout_details,
13221 );
13222 selection.collapse_to(cursor, goal);
13223 });
13224 });
13225
13226 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13227 {
13228 cx.propagate();
13229 }
13230 }
13231
13232 pub fn select_page_down(
13233 &mut self,
13234 _: &SelectPageDown,
13235 window: &mut Window,
13236 cx: &mut Context<Self>,
13237 ) {
13238 let Some(row_count) = self.visible_row_count() else {
13239 return;
13240 };
13241
13242 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13243
13244 let text_layout_details = &self.text_layout_details(window);
13245
13246 self.change_selections(Default::default(), window, cx, |s| {
13247 s.move_heads_with(|map, head, goal| {
13248 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13249 })
13250 })
13251 }
13252
13253 pub fn move_page_down(
13254 &mut self,
13255 action: &MovePageDown,
13256 window: &mut Window,
13257 cx: &mut Context<Self>,
13258 ) {
13259 if self.take_rename(true, window, cx).is_some() {
13260 return;
13261 }
13262
13263 if self
13264 .context_menu
13265 .borrow_mut()
13266 .as_mut()
13267 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13268 .unwrap_or(false)
13269 {
13270 return;
13271 }
13272
13273 if matches!(self.mode, EditorMode::SingleLine) {
13274 cx.propagate();
13275 return;
13276 }
13277
13278 let Some(row_count) = self.visible_row_count() else {
13279 return;
13280 };
13281
13282 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13283
13284 let effects = if action.center_cursor {
13285 SelectionEffects::scroll(Autoscroll::center())
13286 } else {
13287 SelectionEffects::default()
13288 };
13289
13290 let text_layout_details = &self.text_layout_details(window);
13291 self.change_selections(effects, window, cx, |s| {
13292 s.move_with(|map, selection| {
13293 if !selection.is_empty() {
13294 selection.goal = SelectionGoal::None;
13295 }
13296 let (cursor, goal) = movement::down_by_rows(
13297 map,
13298 selection.end,
13299 row_count,
13300 selection.goal,
13301 false,
13302 text_layout_details,
13303 );
13304 selection.collapse_to(cursor, goal);
13305 });
13306 });
13307 }
13308
13309 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13310 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13311 let text_layout_details = &self.text_layout_details(window);
13312 self.change_selections(Default::default(), window, cx, |s| {
13313 s.move_heads_with(|map, head, goal| {
13314 movement::down(map, head, goal, false, text_layout_details)
13315 })
13316 });
13317 }
13318
13319 pub fn context_menu_first(
13320 &mut self,
13321 _: &ContextMenuFirst,
13322 window: &mut Window,
13323 cx: &mut Context<Self>,
13324 ) {
13325 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13326 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13327 }
13328 }
13329
13330 pub fn context_menu_prev(
13331 &mut self,
13332 _: &ContextMenuPrevious,
13333 window: &mut Window,
13334 cx: &mut Context<Self>,
13335 ) {
13336 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13337 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13338 }
13339 }
13340
13341 pub fn context_menu_next(
13342 &mut self,
13343 _: &ContextMenuNext,
13344 window: &mut Window,
13345 cx: &mut Context<Self>,
13346 ) {
13347 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13348 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13349 }
13350 }
13351
13352 pub fn context_menu_last(
13353 &mut self,
13354 _: &ContextMenuLast,
13355 window: &mut Window,
13356 cx: &mut Context<Self>,
13357 ) {
13358 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13359 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13360 }
13361 }
13362
13363 pub fn signature_help_prev(
13364 &mut self,
13365 _: &SignatureHelpPrevious,
13366 _: &mut Window,
13367 cx: &mut Context<Self>,
13368 ) {
13369 if let Some(popover) = self.signature_help_state.popover_mut() {
13370 if popover.current_signature == 0 {
13371 popover.current_signature = popover.signatures.len() - 1;
13372 } else {
13373 popover.current_signature -= 1;
13374 }
13375 cx.notify();
13376 }
13377 }
13378
13379 pub fn signature_help_next(
13380 &mut self,
13381 _: &SignatureHelpNext,
13382 _: &mut Window,
13383 cx: &mut Context<Self>,
13384 ) {
13385 if let Some(popover) = self.signature_help_state.popover_mut() {
13386 if popover.current_signature + 1 == popover.signatures.len() {
13387 popover.current_signature = 0;
13388 } else {
13389 popover.current_signature += 1;
13390 }
13391 cx.notify();
13392 }
13393 }
13394
13395 pub fn move_to_previous_word_start(
13396 &mut self,
13397 _: &MoveToPreviousWordStart,
13398 window: &mut Window,
13399 cx: &mut Context<Self>,
13400 ) {
13401 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13402 self.change_selections(Default::default(), window, cx, |s| {
13403 s.move_cursors_with(|map, head, _| {
13404 (
13405 movement::previous_word_start(map, head),
13406 SelectionGoal::None,
13407 )
13408 });
13409 })
13410 }
13411
13412 pub fn move_to_previous_subword_start(
13413 &mut self,
13414 _: &MoveToPreviousSubwordStart,
13415 window: &mut Window,
13416 cx: &mut Context<Self>,
13417 ) {
13418 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13419 self.change_selections(Default::default(), window, cx, |s| {
13420 s.move_cursors_with(|map, head, _| {
13421 (
13422 movement::previous_subword_start(map, head),
13423 SelectionGoal::None,
13424 )
13425 });
13426 })
13427 }
13428
13429 pub fn select_to_previous_word_start(
13430 &mut self,
13431 _: &SelectToPreviousWordStart,
13432 window: &mut Window,
13433 cx: &mut Context<Self>,
13434 ) {
13435 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13436 self.change_selections(Default::default(), window, cx, |s| {
13437 s.move_heads_with(|map, head, _| {
13438 (
13439 movement::previous_word_start(map, head),
13440 SelectionGoal::None,
13441 )
13442 });
13443 })
13444 }
13445
13446 pub fn select_to_previous_subword_start(
13447 &mut self,
13448 _: &SelectToPreviousSubwordStart,
13449 window: &mut Window,
13450 cx: &mut Context<Self>,
13451 ) {
13452 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13453 self.change_selections(Default::default(), window, cx, |s| {
13454 s.move_heads_with(|map, head, _| {
13455 (
13456 movement::previous_subword_start(map, head),
13457 SelectionGoal::None,
13458 )
13459 });
13460 })
13461 }
13462
13463 pub fn delete_to_previous_word_start(
13464 &mut self,
13465 action: &DeleteToPreviousWordStart,
13466 window: &mut Window,
13467 cx: &mut Context<Self>,
13468 ) {
13469 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13470 self.transact(window, cx, |this, window, cx| {
13471 this.select_autoclose_pair(window, cx);
13472 this.change_selections(Default::default(), window, cx, |s| {
13473 s.move_with(|map, selection| {
13474 if selection.is_empty() {
13475 let mut cursor = if action.ignore_newlines {
13476 movement::previous_word_start(map, selection.head())
13477 } else {
13478 movement::previous_word_start_or_newline(map, selection.head())
13479 };
13480 cursor = movement::adjust_greedy_deletion(
13481 map,
13482 selection.head(),
13483 cursor,
13484 action.ignore_brackets,
13485 );
13486 selection.set_head(cursor, SelectionGoal::None);
13487 }
13488 });
13489 });
13490 this.insert("", window, cx);
13491 });
13492 }
13493
13494 pub fn delete_to_previous_subword_start(
13495 &mut self,
13496 _: &DeleteToPreviousSubwordStart,
13497 window: &mut Window,
13498 cx: &mut Context<Self>,
13499 ) {
13500 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13501 self.transact(window, cx, |this, window, cx| {
13502 this.select_autoclose_pair(window, cx);
13503 this.change_selections(Default::default(), window, cx, |s| {
13504 s.move_with(|map, selection| {
13505 if selection.is_empty() {
13506 let mut cursor = movement::previous_subword_start(map, selection.head());
13507 cursor =
13508 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13509 selection.set_head(cursor, SelectionGoal::None);
13510 }
13511 });
13512 });
13513 this.insert("", window, cx);
13514 });
13515 }
13516
13517 pub fn move_to_next_word_end(
13518 &mut self,
13519 _: &MoveToNextWordEnd,
13520 window: &mut Window,
13521 cx: &mut Context<Self>,
13522 ) {
13523 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13524 self.change_selections(Default::default(), window, cx, |s| {
13525 s.move_cursors_with(|map, head, _| {
13526 (movement::next_word_end(map, head), SelectionGoal::None)
13527 });
13528 })
13529 }
13530
13531 pub fn move_to_next_subword_end(
13532 &mut self,
13533 _: &MoveToNextSubwordEnd,
13534 window: &mut Window,
13535 cx: &mut Context<Self>,
13536 ) {
13537 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13538 self.change_selections(Default::default(), window, cx, |s| {
13539 s.move_cursors_with(|map, head, _| {
13540 (movement::next_subword_end(map, head), SelectionGoal::None)
13541 });
13542 })
13543 }
13544
13545 pub fn select_to_next_word_end(
13546 &mut self,
13547 _: &SelectToNextWordEnd,
13548 window: &mut Window,
13549 cx: &mut Context<Self>,
13550 ) {
13551 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13552 self.change_selections(Default::default(), window, cx, |s| {
13553 s.move_heads_with(|map, head, _| {
13554 (movement::next_word_end(map, head), SelectionGoal::None)
13555 });
13556 })
13557 }
13558
13559 pub fn select_to_next_subword_end(
13560 &mut self,
13561 _: &SelectToNextSubwordEnd,
13562 window: &mut Window,
13563 cx: &mut Context<Self>,
13564 ) {
13565 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13566 self.change_selections(Default::default(), window, cx, |s| {
13567 s.move_heads_with(|map, head, _| {
13568 (movement::next_subword_end(map, head), SelectionGoal::None)
13569 });
13570 })
13571 }
13572
13573 pub fn delete_to_next_word_end(
13574 &mut self,
13575 action: &DeleteToNextWordEnd,
13576 window: &mut Window,
13577 cx: &mut Context<Self>,
13578 ) {
13579 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13580 self.transact(window, cx, |this, window, cx| {
13581 this.change_selections(Default::default(), window, cx, |s| {
13582 s.move_with(|map, selection| {
13583 if selection.is_empty() {
13584 let mut cursor = if action.ignore_newlines {
13585 movement::next_word_end(map, selection.head())
13586 } else {
13587 movement::next_word_end_or_newline(map, selection.head())
13588 };
13589 cursor = movement::adjust_greedy_deletion(
13590 map,
13591 selection.head(),
13592 cursor,
13593 action.ignore_brackets,
13594 );
13595 selection.set_head(cursor, SelectionGoal::None);
13596 }
13597 });
13598 });
13599 this.insert("", window, cx);
13600 });
13601 }
13602
13603 pub fn delete_to_next_subword_end(
13604 &mut self,
13605 _: &DeleteToNextSubwordEnd,
13606 window: &mut Window,
13607 cx: &mut Context<Self>,
13608 ) {
13609 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13610 self.transact(window, cx, |this, window, cx| {
13611 this.change_selections(Default::default(), window, cx, |s| {
13612 s.move_with(|map, selection| {
13613 if selection.is_empty() {
13614 let mut cursor = movement::next_subword_end(map, selection.head());
13615 cursor =
13616 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13617 selection.set_head(cursor, SelectionGoal::None);
13618 }
13619 });
13620 });
13621 this.insert("", window, cx);
13622 });
13623 }
13624
13625 pub fn move_to_beginning_of_line(
13626 &mut self,
13627 action: &MoveToBeginningOfLine,
13628 window: &mut Window,
13629 cx: &mut Context<Self>,
13630 ) {
13631 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13632 self.change_selections(Default::default(), window, cx, |s| {
13633 s.move_cursors_with(|map, head, _| {
13634 (
13635 movement::indented_line_beginning(
13636 map,
13637 head,
13638 action.stop_at_soft_wraps,
13639 action.stop_at_indent,
13640 ),
13641 SelectionGoal::None,
13642 )
13643 });
13644 })
13645 }
13646
13647 pub fn select_to_beginning_of_line(
13648 &mut self,
13649 action: &SelectToBeginningOfLine,
13650 window: &mut Window,
13651 cx: &mut Context<Self>,
13652 ) {
13653 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13654 self.change_selections(Default::default(), window, cx, |s| {
13655 s.move_heads_with(|map, head, _| {
13656 (
13657 movement::indented_line_beginning(
13658 map,
13659 head,
13660 action.stop_at_soft_wraps,
13661 action.stop_at_indent,
13662 ),
13663 SelectionGoal::None,
13664 )
13665 });
13666 });
13667 }
13668
13669 pub fn delete_to_beginning_of_line(
13670 &mut self,
13671 action: &DeleteToBeginningOfLine,
13672 window: &mut Window,
13673 cx: &mut Context<Self>,
13674 ) {
13675 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13676 self.transact(window, cx, |this, window, cx| {
13677 this.change_selections(Default::default(), window, cx, |s| {
13678 s.move_with(|_, selection| {
13679 selection.reversed = true;
13680 });
13681 });
13682
13683 this.select_to_beginning_of_line(
13684 &SelectToBeginningOfLine {
13685 stop_at_soft_wraps: false,
13686 stop_at_indent: action.stop_at_indent,
13687 },
13688 window,
13689 cx,
13690 );
13691 this.backspace(&Backspace, window, cx);
13692 });
13693 }
13694
13695 pub fn move_to_end_of_line(
13696 &mut self,
13697 action: &MoveToEndOfLine,
13698 window: &mut Window,
13699 cx: &mut Context<Self>,
13700 ) {
13701 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13702 self.change_selections(Default::default(), window, cx, |s| {
13703 s.move_cursors_with(|map, head, _| {
13704 (
13705 movement::line_end(map, head, action.stop_at_soft_wraps),
13706 SelectionGoal::None,
13707 )
13708 });
13709 })
13710 }
13711
13712 pub fn select_to_end_of_line(
13713 &mut self,
13714 action: &SelectToEndOfLine,
13715 window: &mut Window,
13716 cx: &mut Context<Self>,
13717 ) {
13718 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13719 self.change_selections(Default::default(), window, cx, |s| {
13720 s.move_heads_with(|map, head, _| {
13721 (
13722 movement::line_end(map, head, action.stop_at_soft_wraps),
13723 SelectionGoal::None,
13724 )
13725 });
13726 })
13727 }
13728
13729 pub fn delete_to_end_of_line(
13730 &mut self,
13731 _: &DeleteToEndOfLine,
13732 window: &mut Window,
13733 cx: &mut Context<Self>,
13734 ) {
13735 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13736 self.transact(window, cx, |this, window, cx| {
13737 this.select_to_end_of_line(
13738 &SelectToEndOfLine {
13739 stop_at_soft_wraps: false,
13740 },
13741 window,
13742 cx,
13743 );
13744 this.delete(&Delete, window, cx);
13745 });
13746 }
13747
13748 pub fn cut_to_end_of_line(
13749 &mut self,
13750 action: &CutToEndOfLine,
13751 window: &mut Window,
13752 cx: &mut Context<Self>,
13753 ) {
13754 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13755 self.transact(window, cx, |this, window, cx| {
13756 this.select_to_end_of_line(
13757 &SelectToEndOfLine {
13758 stop_at_soft_wraps: false,
13759 },
13760 window,
13761 cx,
13762 );
13763 if !action.stop_at_newlines {
13764 this.change_selections(Default::default(), window, cx, |s| {
13765 s.move_with(|_, sel| {
13766 if sel.is_empty() {
13767 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13768 }
13769 });
13770 });
13771 }
13772 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13773 let item = this.cut_common(false, window, cx);
13774 cx.write_to_clipboard(item);
13775 });
13776 }
13777
13778 pub fn move_to_start_of_paragraph(
13779 &mut self,
13780 _: &MoveToStartOfParagraph,
13781 window: &mut Window,
13782 cx: &mut Context<Self>,
13783 ) {
13784 if matches!(self.mode, EditorMode::SingleLine) {
13785 cx.propagate();
13786 return;
13787 }
13788 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13789 self.change_selections(Default::default(), window, cx, |s| {
13790 s.move_with(|map, selection| {
13791 selection.collapse_to(
13792 movement::start_of_paragraph(map, selection.head(), 1),
13793 SelectionGoal::None,
13794 )
13795 });
13796 })
13797 }
13798
13799 pub fn move_to_end_of_paragraph(
13800 &mut self,
13801 _: &MoveToEndOfParagraph,
13802 window: &mut Window,
13803 cx: &mut Context<Self>,
13804 ) {
13805 if matches!(self.mode, EditorMode::SingleLine) {
13806 cx.propagate();
13807 return;
13808 }
13809 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13810 self.change_selections(Default::default(), window, cx, |s| {
13811 s.move_with(|map, selection| {
13812 selection.collapse_to(
13813 movement::end_of_paragraph(map, selection.head(), 1),
13814 SelectionGoal::None,
13815 )
13816 });
13817 })
13818 }
13819
13820 pub fn select_to_start_of_paragraph(
13821 &mut self,
13822 _: &SelectToStartOfParagraph,
13823 window: &mut Window,
13824 cx: &mut Context<Self>,
13825 ) {
13826 if matches!(self.mode, EditorMode::SingleLine) {
13827 cx.propagate();
13828 return;
13829 }
13830 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13831 self.change_selections(Default::default(), window, cx, |s| {
13832 s.move_heads_with(|map, head, _| {
13833 (
13834 movement::start_of_paragraph(map, head, 1),
13835 SelectionGoal::None,
13836 )
13837 });
13838 })
13839 }
13840
13841 pub fn select_to_end_of_paragraph(
13842 &mut self,
13843 _: &SelectToEndOfParagraph,
13844 window: &mut Window,
13845 cx: &mut Context<Self>,
13846 ) {
13847 if matches!(self.mode, EditorMode::SingleLine) {
13848 cx.propagate();
13849 return;
13850 }
13851 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13852 self.change_selections(Default::default(), window, cx, |s| {
13853 s.move_heads_with(|map, head, _| {
13854 (
13855 movement::end_of_paragraph(map, head, 1),
13856 SelectionGoal::None,
13857 )
13858 });
13859 })
13860 }
13861
13862 pub fn move_to_start_of_excerpt(
13863 &mut self,
13864 _: &MoveToStartOfExcerpt,
13865 window: &mut Window,
13866 cx: &mut Context<Self>,
13867 ) {
13868 if matches!(self.mode, EditorMode::SingleLine) {
13869 cx.propagate();
13870 return;
13871 }
13872 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13873 self.change_selections(Default::default(), window, cx, |s| {
13874 s.move_with(|map, selection| {
13875 selection.collapse_to(
13876 movement::start_of_excerpt(
13877 map,
13878 selection.head(),
13879 workspace::searchable::Direction::Prev,
13880 ),
13881 SelectionGoal::None,
13882 )
13883 });
13884 })
13885 }
13886
13887 pub fn move_to_start_of_next_excerpt(
13888 &mut self,
13889 _: &MoveToStartOfNextExcerpt,
13890 window: &mut Window,
13891 cx: &mut Context<Self>,
13892 ) {
13893 if matches!(self.mode, EditorMode::SingleLine) {
13894 cx.propagate();
13895 return;
13896 }
13897
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::Next,
13905 ),
13906 SelectionGoal::None,
13907 )
13908 });
13909 })
13910 }
13911
13912 pub fn move_to_end_of_excerpt(
13913 &mut self,
13914 _: &MoveToEndOfExcerpt,
13915 window: &mut Window,
13916 cx: &mut Context<Self>,
13917 ) {
13918 if matches!(self.mode, EditorMode::SingleLine) {
13919 cx.propagate();
13920 return;
13921 }
13922 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13923 self.change_selections(Default::default(), window, cx, |s| {
13924 s.move_with(|map, selection| {
13925 selection.collapse_to(
13926 movement::end_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_previous_excerpt(
13938 &mut self,
13939 _: &MoveToEndOfPreviousExcerpt,
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::Prev,
13955 ),
13956 SelectionGoal::None,
13957 )
13958 });
13959 })
13960 }
13961
13962 pub fn select_to_start_of_excerpt(
13963 &mut self,
13964 _: &SelectToStartOfExcerpt,
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_heads_with(|map, head, _| {
13975 (
13976 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13977 SelectionGoal::None,
13978 )
13979 });
13980 })
13981 }
13982
13983 pub fn select_to_start_of_next_excerpt(
13984 &mut self,
13985 _: &SelectToStartOfNextExcerpt,
13986 window: &mut Window,
13987 cx: &mut Context<Self>,
13988 ) {
13989 if matches!(self.mode, EditorMode::SingleLine) {
13990 cx.propagate();
13991 return;
13992 }
13993 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13994 self.change_selections(Default::default(), window, cx, |s| {
13995 s.move_heads_with(|map, head, _| {
13996 (
13997 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13998 SelectionGoal::None,
13999 )
14000 });
14001 })
14002 }
14003
14004 pub fn select_to_end_of_excerpt(
14005 &mut self,
14006 _: &SelectToEndOfExcerpt,
14007 window: &mut Window,
14008 cx: &mut Context<Self>,
14009 ) {
14010 if matches!(self.mode, EditorMode::SingleLine) {
14011 cx.propagate();
14012 return;
14013 }
14014 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14015 self.change_selections(Default::default(), window, cx, |s| {
14016 s.move_heads_with(|map, head, _| {
14017 (
14018 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14019 SelectionGoal::None,
14020 )
14021 });
14022 })
14023 }
14024
14025 pub fn select_to_end_of_previous_excerpt(
14026 &mut self,
14027 _: &SelectToEndOfPreviousExcerpt,
14028 window: &mut Window,
14029 cx: &mut Context<Self>,
14030 ) {
14031 if matches!(self.mode, EditorMode::SingleLine) {
14032 cx.propagate();
14033 return;
14034 }
14035 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14036 self.change_selections(Default::default(), window, cx, |s| {
14037 s.move_heads_with(|map, head, _| {
14038 (
14039 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14040 SelectionGoal::None,
14041 )
14042 });
14043 })
14044 }
14045
14046 pub fn move_to_beginning(
14047 &mut self,
14048 _: &MoveToBeginning,
14049 window: &mut Window,
14050 cx: &mut Context<Self>,
14051 ) {
14052 if matches!(self.mode, EditorMode::SingleLine) {
14053 cx.propagate();
14054 return;
14055 }
14056 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14057 self.change_selections(Default::default(), window, cx, |s| {
14058 s.select_ranges(vec![0..0]);
14059 });
14060 }
14061
14062 pub fn select_to_beginning(
14063 &mut self,
14064 _: &SelectToBeginning,
14065 window: &mut Window,
14066 cx: &mut Context<Self>,
14067 ) {
14068 let mut selection = self.selections.last::<Point>(cx);
14069 selection.set_head(Point::zero(), SelectionGoal::None);
14070 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14071 self.change_selections(Default::default(), window, cx, |s| {
14072 s.select(vec![selection]);
14073 });
14074 }
14075
14076 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14077 if matches!(self.mode, EditorMode::SingleLine) {
14078 cx.propagate();
14079 return;
14080 }
14081 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14082 let cursor = self.buffer.read(cx).read(cx).len();
14083 self.change_selections(Default::default(), window, cx, |s| {
14084 s.select_ranges(vec![cursor..cursor])
14085 });
14086 }
14087
14088 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14089 self.nav_history = nav_history;
14090 }
14091
14092 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14093 self.nav_history.as_ref()
14094 }
14095
14096 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14097 self.push_to_nav_history(
14098 self.selections.newest_anchor().head(),
14099 None,
14100 false,
14101 true,
14102 cx,
14103 );
14104 }
14105
14106 fn push_to_nav_history(
14107 &mut self,
14108 cursor_anchor: Anchor,
14109 new_position: Option<Point>,
14110 is_deactivate: bool,
14111 always: bool,
14112 cx: &mut Context<Self>,
14113 ) {
14114 if let Some(nav_history) = self.nav_history.as_mut() {
14115 let buffer = self.buffer.read(cx).read(cx);
14116 let cursor_position = cursor_anchor.to_point(&buffer);
14117 let scroll_state = self.scroll_manager.anchor();
14118 let scroll_top_row = scroll_state.top_row(&buffer);
14119 drop(buffer);
14120
14121 if let Some(new_position) = new_position {
14122 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14123 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14124 return;
14125 }
14126 }
14127
14128 nav_history.push(
14129 Some(NavigationData {
14130 cursor_anchor,
14131 cursor_position,
14132 scroll_anchor: scroll_state,
14133 scroll_top_row,
14134 }),
14135 cx,
14136 );
14137 cx.emit(EditorEvent::PushedToNavHistory {
14138 anchor: cursor_anchor,
14139 is_deactivate,
14140 })
14141 }
14142 }
14143
14144 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14145 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14146 let buffer = self.buffer.read(cx).snapshot(cx);
14147 let mut selection = self.selections.first::<usize>(cx);
14148 selection.set_head(buffer.len(), SelectionGoal::None);
14149 self.change_selections(Default::default(), window, cx, |s| {
14150 s.select(vec![selection]);
14151 });
14152 }
14153
14154 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14155 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14156 let end = self.buffer.read(cx).read(cx).len();
14157 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14158 s.select_ranges(vec![0..end]);
14159 });
14160 }
14161
14162 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14163 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14164 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14165 let mut selections = self.selections.all::<Point>(cx);
14166 let max_point = display_map.buffer_snapshot().max_point();
14167 for selection in &mut selections {
14168 let rows = selection.spanned_rows(true, &display_map);
14169 selection.start = Point::new(rows.start.0, 0);
14170 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14171 selection.reversed = false;
14172 }
14173 self.change_selections(Default::default(), window, cx, |s| {
14174 s.select(selections);
14175 });
14176 }
14177
14178 pub fn split_selection_into_lines(
14179 &mut self,
14180 action: &SplitSelectionIntoLines,
14181 window: &mut Window,
14182 cx: &mut Context<Self>,
14183 ) {
14184 let selections = self
14185 .selections
14186 .all::<Point>(cx)
14187 .into_iter()
14188 .map(|selection| selection.start..selection.end)
14189 .collect::<Vec<_>>();
14190 self.unfold_ranges(&selections, true, true, cx);
14191
14192 let mut new_selection_ranges = Vec::new();
14193 {
14194 let buffer = self.buffer.read(cx).read(cx);
14195 for selection in selections {
14196 for row in selection.start.row..selection.end.row {
14197 let line_start = Point::new(row, 0);
14198 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14199
14200 if action.keep_selections {
14201 // Keep the selection range for each line
14202 let selection_start = if row == selection.start.row {
14203 selection.start
14204 } else {
14205 line_start
14206 };
14207 new_selection_ranges.push(selection_start..line_end);
14208 } else {
14209 // Collapse to cursor at end of line
14210 new_selection_ranges.push(line_end..line_end);
14211 }
14212 }
14213
14214 let is_multiline_selection = selection.start.row != selection.end.row;
14215 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14216 // so this action feels more ergonomic when paired with other selection operations
14217 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14218 if !should_skip_last {
14219 if action.keep_selections {
14220 if is_multiline_selection {
14221 let line_start = Point::new(selection.end.row, 0);
14222 new_selection_ranges.push(line_start..selection.end);
14223 } else {
14224 new_selection_ranges.push(selection.start..selection.end);
14225 }
14226 } else {
14227 new_selection_ranges.push(selection.end..selection.end);
14228 }
14229 }
14230 }
14231 }
14232 self.change_selections(Default::default(), window, cx, |s| {
14233 s.select_ranges(new_selection_ranges);
14234 });
14235 }
14236
14237 pub fn add_selection_above(
14238 &mut self,
14239 _: &AddSelectionAbove,
14240 window: &mut Window,
14241 cx: &mut Context<Self>,
14242 ) {
14243 self.add_selection(true, window, cx);
14244 }
14245
14246 pub fn add_selection_below(
14247 &mut self,
14248 _: &AddSelectionBelow,
14249 window: &mut Window,
14250 cx: &mut Context<Self>,
14251 ) {
14252 self.add_selection(false, window, cx);
14253 }
14254
14255 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
14256 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14257
14258 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14259 let all_selections = self.selections.all::<Point>(cx);
14260 let text_layout_details = self.text_layout_details(window);
14261
14262 let (mut columnar_selections, new_selections_to_columnarize) = {
14263 if let Some(state) = self.add_selections_state.as_ref() {
14264 let columnar_selection_ids: HashSet<_> = state
14265 .groups
14266 .iter()
14267 .flat_map(|group| group.stack.iter())
14268 .copied()
14269 .collect();
14270
14271 all_selections
14272 .into_iter()
14273 .partition(|s| columnar_selection_ids.contains(&s.id))
14274 } else {
14275 (Vec::new(), all_selections)
14276 }
14277 };
14278
14279 let mut state = self
14280 .add_selections_state
14281 .take()
14282 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14283
14284 for selection in new_selections_to_columnarize {
14285 let range = selection.display_range(&display_map).sorted();
14286 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14287 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14288 let positions = start_x.min(end_x)..start_x.max(end_x);
14289 let mut stack = Vec::new();
14290 for row in range.start.row().0..=range.end.row().0 {
14291 if let Some(selection) = self.selections.build_columnar_selection(
14292 &display_map,
14293 DisplayRow(row),
14294 &positions,
14295 selection.reversed,
14296 &text_layout_details,
14297 ) {
14298 stack.push(selection.id);
14299 columnar_selections.push(selection);
14300 }
14301 }
14302 if !stack.is_empty() {
14303 if above {
14304 stack.reverse();
14305 }
14306 state.groups.push(AddSelectionsGroup { above, stack });
14307 }
14308 }
14309
14310 let mut final_selections = Vec::new();
14311 let end_row = if above {
14312 DisplayRow(0)
14313 } else {
14314 display_map.max_point().row()
14315 };
14316
14317 let mut last_added_item_per_group = HashMap::default();
14318 for group in state.groups.iter_mut() {
14319 if let Some(last_id) = group.stack.last() {
14320 last_added_item_per_group.insert(*last_id, group);
14321 }
14322 }
14323
14324 for selection in columnar_selections {
14325 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14326 if above == group.above {
14327 let range = selection.display_range(&display_map).sorted();
14328 debug_assert_eq!(range.start.row(), range.end.row());
14329 let mut row = range.start.row();
14330 let positions =
14331 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14332 Pixels::from(start)..Pixels::from(end)
14333 } else {
14334 let start_x =
14335 display_map.x_for_display_point(range.start, &text_layout_details);
14336 let end_x =
14337 display_map.x_for_display_point(range.end, &text_layout_details);
14338 start_x.min(end_x)..start_x.max(end_x)
14339 };
14340
14341 let mut maybe_new_selection = None;
14342 while row != end_row {
14343 if above {
14344 row.0 -= 1;
14345 } else {
14346 row.0 += 1;
14347 }
14348 if let Some(new_selection) = self.selections.build_columnar_selection(
14349 &display_map,
14350 row,
14351 &positions,
14352 selection.reversed,
14353 &text_layout_details,
14354 ) {
14355 maybe_new_selection = Some(new_selection);
14356 break;
14357 }
14358 }
14359
14360 if let Some(new_selection) = maybe_new_selection {
14361 group.stack.push(new_selection.id);
14362 if above {
14363 final_selections.push(new_selection);
14364 final_selections.push(selection);
14365 } else {
14366 final_selections.push(selection);
14367 final_selections.push(new_selection);
14368 }
14369 } else {
14370 final_selections.push(selection);
14371 }
14372 } else {
14373 group.stack.pop();
14374 }
14375 } else {
14376 final_selections.push(selection);
14377 }
14378 }
14379
14380 self.change_selections(Default::default(), window, cx, |s| {
14381 s.select(final_selections);
14382 });
14383
14384 let final_selection_ids: HashSet<_> = self
14385 .selections
14386 .all::<Point>(cx)
14387 .iter()
14388 .map(|s| s.id)
14389 .collect();
14390 state.groups.retain_mut(|group| {
14391 // selections might get merged above so we remove invalid items from stacks
14392 group.stack.retain(|id| final_selection_ids.contains(id));
14393
14394 // single selection in stack can be treated as initial state
14395 group.stack.len() > 1
14396 });
14397
14398 if !state.groups.is_empty() {
14399 self.add_selections_state = Some(state);
14400 }
14401 }
14402
14403 fn select_match_ranges(
14404 &mut self,
14405 range: Range<usize>,
14406 reversed: bool,
14407 replace_newest: bool,
14408 auto_scroll: Option<Autoscroll>,
14409 window: &mut Window,
14410 cx: &mut Context<Editor>,
14411 ) {
14412 self.unfold_ranges(
14413 std::slice::from_ref(&range),
14414 false,
14415 auto_scroll.is_some(),
14416 cx,
14417 );
14418 let effects = if let Some(scroll) = auto_scroll {
14419 SelectionEffects::scroll(scroll)
14420 } else {
14421 SelectionEffects::no_scroll()
14422 };
14423 self.change_selections(effects, window, cx, |s| {
14424 if replace_newest {
14425 s.delete(s.newest_anchor().id);
14426 }
14427 if reversed {
14428 s.insert_range(range.end..range.start);
14429 } else {
14430 s.insert_range(range);
14431 }
14432 });
14433 }
14434
14435 pub fn select_next_match_internal(
14436 &mut self,
14437 display_map: &DisplaySnapshot,
14438 replace_newest: bool,
14439 autoscroll: Option<Autoscroll>,
14440 window: &mut Window,
14441 cx: &mut Context<Self>,
14442 ) -> Result<()> {
14443 let buffer = display_map.buffer_snapshot();
14444 let mut selections = self.selections.all::<usize>(cx);
14445 if let Some(mut select_next_state) = self.select_next_state.take() {
14446 let query = &select_next_state.query;
14447 if !select_next_state.done {
14448 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14449 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14450 let mut next_selected_range = None;
14451
14452 // Collect and sort selection ranges for efficient overlap checking
14453 let mut selection_ranges: Vec<_> = selections.iter().map(|s| s.range()).collect();
14454 selection_ranges.sort_by_key(|r| r.start);
14455
14456 let bytes_after_last_selection =
14457 buffer.bytes_in_range(last_selection.end..buffer.len());
14458 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14459 let query_matches = query
14460 .stream_find_iter(bytes_after_last_selection)
14461 .map(|result| (last_selection.end, result))
14462 .chain(
14463 query
14464 .stream_find_iter(bytes_before_first_selection)
14465 .map(|result| (0, result)),
14466 );
14467
14468 for (start_offset, query_match) in query_matches {
14469 let query_match = query_match.unwrap(); // can only fail due to I/O
14470 let offset_range =
14471 start_offset + query_match.start()..start_offset + query_match.end();
14472
14473 if !select_next_state.wordwise
14474 || (!buffer.is_inside_word(offset_range.start, None)
14475 && !buffer.is_inside_word(offset_range.end, None))
14476 {
14477 // Use binary search to check for overlap (O(log n))
14478 let overlaps = selection_ranges
14479 .binary_search_by(|range| {
14480 if range.end <= offset_range.start {
14481 std::cmp::Ordering::Less
14482 } else if range.start >= offset_range.end {
14483 std::cmp::Ordering::Greater
14484 } else {
14485 std::cmp::Ordering::Equal
14486 }
14487 })
14488 .is_ok();
14489
14490 if !overlaps {
14491 next_selected_range = Some(offset_range);
14492 break;
14493 }
14494 }
14495 }
14496
14497 if let Some(next_selected_range) = next_selected_range {
14498 self.select_match_ranges(
14499 next_selected_range,
14500 last_selection.reversed,
14501 replace_newest,
14502 autoscroll,
14503 window,
14504 cx,
14505 );
14506 } else {
14507 select_next_state.done = true;
14508 }
14509 }
14510
14511 self.select_next_state = Some(select_next_state);
14512 } else {
14513 let mut only_carets = true;
14514 let mut same_text_selected = true;
14515 let mut selected_text = None;
14516
14517 let mut selections_iter = selections.iter().peekable();
14518 while let Some(selection) = selections_iter.next() {
14519 if selection.start != selection.end {
14520 only_carets = false;
14521 }
14522
14523 if same_text_selected {
14524 if selected_text.is_none() {
14525 selected_text =
14526 Some(buffer.text_for_range(selection.range()).collect::<String>());
14527 }
14528
14529 if let Some(next_selection) = selections_iter.peek() {
14530 if next_selection.range().len() == selection.range().len() {
14531 let next_selected_text = buffer
14532 .text_for_range(next_selection.range())
14533 .collect::<String>();
14534 if Some(next_selected_text) != selected_text {
14535 same_text_selected = false;
14536 selected_text = None;
14537 }
14538 } else {
14539 same_text_selected = false;
14540 selected_text = None;
14541 }
14542 }
14543 }
14544 }
14545
14546 if only_carets {
14547 for selection in &mut selections {
14548 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14549 selection.start = word_range.start;
14550 selection.end = word_range.end;
14551 selection.goal = SelectionGoal::None;
14552 selection.reversed = false;
14553 self.select_match_ranges(
14554 selection.start..selection.end,
14555 selection.reversed,
14556 replace_newest,
14557 autoscroll,
14558 window,
14559 cx,
14560 );
14561 }
14562
14563 if selections.len() == 1 {
14564 let selection = selections
14565 .last()
14566 .expect("ensured that there's only one selection");
14567 let query = buffer
14568 .text_for_range(selection.start..selection.end)
14569 .collect::<String>();
14570 let is_empty = query.is_empty();
14571 let select_state = SelectNextState {
14572 query: AhoCorasick::new(&[query])?,
14573 wordwise: true,
14574 done: is_empty,
14575 };
14576 self.select_next_state = Some(select_state);
14577 } else {
14578 self.select_next_state = None;
14579 }
14580 } else if let Some(selected_text) = selected_text {
14581 self.select_next_state = Some(SelectNextState {
14582 query: AhoCorasick::new(&[selected_text])?,
14583 wordwise: false,
14584 done: false,
14585 });
14586 self.select_next_match_internal(
14587 display_map,
14588 replace_newest,
14589 autoscroll,
14590 window,
14591 cx,
14592 )?;
14593 }
14594 }
14595 Ok(())
14596 }
14597
14598 pub fn select_all_matches(
14599 &mut self,
14600 _action: &SelectAllMatches,
14601 window: &mut Window,
14602 cx: &mut Context<Self>,
14603 ) -> Result<()> {
14604 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14605
14606 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14607
14608 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14609 let Some(select_next_state) = self.select_next_state.as_mut() else {
14610 return Ok(());
14611 };
14612 if select_next_state.done {
14613 return Ok(());
14614 }
14615
14616 let mut new_selections = Vec::new();
14617
14618 let reversed = self.selections.oldest::<usize>(cx).reversed;
14619 let buffer = display_map.buffer_snapshot();
14620 let query_matches = select_next_state
14621 .query
14622 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14623
14624 for query_match in query_matches.into_iter() {
14625 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14626 let offset_range = if reversed {
14627 query_match.end()..query_match.start()
14628 } else {
14629 query_match.start()..query_match.end()
14630 };
14631
14632 if !select_next_state.wordwise
14633 || (!buffer.is_inside_word(offset_range.start, None)
14634 && !buffer.is_inside_word(offset_range.end, None))
14635 {
14636 new_selections.push(offset_range.start..offset_range.end);
14637 }
14638 }
14639
14640 select_next_state.done = true;
14641
14642 if new_selections.is_empty() {
14643 log::error!("bug: new_selections is empty in select_all_matches");
14644 return Ok(());
14645 }
14646
14647 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14648 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14649 selections.select_ranges(new_selections)
14650 });
14651
14652 Ok(())
14653 }
14654
14655 pub fn select_next(
14656 &mut self,
14657 action: &SelectNext,
14658 window: &mut Window,
14659 cx: &mut Context<Self>,
14660 ) -> Result<()> {
14661 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14662 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14663 self.select_next_match_internal(
14664 &display_map,
14665 action.replace_newest,
14666 Some(Autoscroll::newest()),
14667 window,
14668 cx,
14669 )?;
14670 Ok(())
14671 }
14672
14673 pub fn select_previous(
14674 &mut self,
14675 action: &SelectPrevious,
14676 window: &mut Window,
14677 cx: &mut Context<Self>,
14678 ) -> Result<()> {
14679 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14680 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14681 let buffer = display_map.buffer_snapshot();
14682 let mut selections = self.selections.all::<usize>(cx);
14683 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14684 let query = &select_prev_state.query;
14685 if !select_prev_state.done {
14686 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14687 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14688 let mut next_selected_range = None;
14689 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14690 let bytes_before_last_selection =
14691 buffer.reversed_bytes_in_range(0..last_selection.start);
14692 let bytes_after_first_selection =
14693 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14694 let query_matches = query
14695 .stream_find_iter(bytes_before_last_selection)
14696 .map(|result| (last_selection.start, result))
14697 .chain(
14698 query
14699 .stream_find_iter(bytes_after_first_selection)
14700 .map(|result| (buffer.len(), result)),
14701 );
14702 for (end_offset, query_match) in query_matches {
14703 let query_match = query_match.unwrap(); // can only fail due to I/O
14704 let offset_range =
14705 end_offset - query_match.end()..end_offset - query_match.start();
14706
14707 if !select_prev_state.wordwise
14708 || (!buffer.is_inside_word(offset_range.start, None)
14709 && !buffer.is_inside_word(offset_range.end, None))
14710 {
14711 next_selected_range = Some(offset_range);
14712 break;
14713 }
14714 }
14715
14716 if let Some(next_selected_range) = next_selected_range {
14717 self.select_match_ranges(
14718 next_selected_range,
14719 last_selection.reversed,
14720 action.replace_newest,
14721 Some(Autoscroll::newest()),
14722 window,
14723 cx,
14724 );
14725 } else {
14726 select_prev_state.done = true;
14727 }
14728 }
14729
14730 self.select_prev_state = Some(select_prev_state);
14731 } else {
14732 let mut only_carets = true;
14733 let mut same_text_selected = true;
14734 let mut selected_text = None;
14735
14736 let mut selections_iter = selections.iter().peekable();
14737 while let Some(selection) = selections_iter.next() {
14738 if selection.start != selection.end {
14739 only_carets = false;
14740 }
14741
14742 if same_text_selected {
14743 if selected_text.is_none() {
14744 selected_text =
14745 Some(buffer.text_for_range(selection.range()).collect::<String>());
14746 }
14747
14748 if let Some(next_selection) = selections_iter.peek() {
14749 if next_selection.range().len() == selection.range().len() {
14750 let next_selected_text = buffer
14751 .text_for_range(next_selection.range())
14752 .collect::<String>();
14753 if Some(next_selected_text) != selected_text {
14754 same_text_selected = false;
14755 selected_text = None;
14756 }
14757 } else {
14758 same_text_selected = false;
14759 selected_text = None;
14760 }
14761 }
14762 }
14763 }
14764
14765 if only_carets {
14766 for selection in &mut selections {
14767 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14768 selection.start = word_range.start;
14769 selection.end = word_range.end;
14770 selection.goal = SelectionGoal::None;
14771 selection.reversed = false;
14772 self.select_match_ranges(
14773 selection.start..selection.end,
14774 selection.reversed,
14775 action.replace_newest,
14776 Some(Autoscroll::newest()),
14777 window,
14778 cx,
14779 );
14780 }
14781 if selections.len() == 1 {
14782 let selection = selections
14783 .last()
14784 .expect("ensured that there's only one selection");
14785 let query = buffer
14786 .text_for_range(selection.start..selection.end)
14787 .collect::<String>();
14788 let is_empty = query.is_empty();
14789 let select_state = SelectNextState {
14790 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14791 wordwise: true,
14792 done: is_empty,
14793 };
14794 self.select_prev_state = Some(select_state);
14795 } else {
14796 self.select_prev_state = None;
14797 }
14798 } else if let Some(selected_text) = selected_text {
14799 self.select_prev_state = Some(SelectNextState {
14800 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14801 wordwise: false,
14802 done: false,
14803 });
14804 self.select_previous(action, window, cx)?;
14805 }
14806 }
14807 Ok(())
14808 }
14809
14810 pub fn find_next_match(
14811 &mut self,
14812 _: &FindNextMatch,
14813 window: &mut Window,
14814 cx: &mut Context<Self>,
14815 ) -> Result<()> {
14816 let selections = self.selections.disjoint_anchors_arc();
14817 match selections.first() {
14818 Some(first) if selections.len() >= 2 => {
14819 self.change_selections(Default::default(), window, cx, |s| {
14820 s.select_ranges([first.range()]);
14821 });
14822 }
14823 _ => self.select_next(
14824 &SelectNext {
14825 replace_newest: true,
14826 },
14827 window,
14828 cx,
14829 )?,
14830 }
14831 Ok(())
14832 }
14833
14834 pub fn find_previous_match(
14835 &mut self,
14836 _: &FindPreviousMatch,
14837 window: &mut Window,
14838 cx: &mut Context<Self>,
14839 ) -> Result<()> {
14840 let selections = self.selections.disjoint_anchors_arc();
14841 match selections.last() {
14842 Some(last) if selections.len() >= 2 => {
14843 self.change_selections(Default::default(), window, cx, |s| {
14844 s.select_ranges([last.range()]);
14845 });
14846 }
14847 _ => self.select_previous(
14848 &SelectPrevious {
14849 replace_newest: true,
14850 },
14851 window,
14852 cx,
14853 )?,
14854 }
14855 Ok(())
14856 }
14857
14858 pub fn toggle_comments(
14859 &mut self,
14860 action: &ToggleComments,
14861 window: &mut Window,
14862 cx: &mut Context<Self>,
14863 ) {
14864 if self.read_only(cx) {
14865 return;
14866 }
14867 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14868 let text_layout_details = &self.text_layout_details(window);
14869 self.transact(window, cx, |this, window, cx| {
14870 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14871 let mut edits = Vec::new();
14872 let mut selection_edit_ranges = Vec::new();
14873 let mut last_toggled_row = None;
14874 let snapshot = this.buffer.read(cx).read(cx);
14875 let empty_str: Arc<str> = Arc::default();
14876 let mut suffixes_inserted = Vec::new();
14877 let ignore_indent = action.ignore_indent;
14878
14879 fn comment_prefix_range(
14880 snapshot: &MultiBufferSnapshot,
14881 row: MultiBufferRow,
14882 comment_prefix: &str,
14883 comment_prefix_whitespace: &str,
14884 ignore_indent: bool,
14885 ) -> Range<Point> {
14886 let indent_size = if ignore_indent {
14887 0
14888 } else {
14889 snapshot.indent_size_for_line(row).len
14890 };
14891
14892 let start = Point::new(row.0, indent_size);
14893
14894 let mut line_bytes = snapshot
14895 .bytes_in_range(start..snapshot.max_point())
14896 .flatten()
14897 .copied();
14898
14899 // If this line currently begins with the line comment prefix, then record
14900 // the range containing the prefix.
14901 if line_bytes
14902 .by_ref()
14903 .take(comment_prefix.len())
14904 .eq(comment_prefix.bytes())
14905 {
14906 // Include any whitespace that matches the comment prefix.
14907 let matching_whitespace_len = line_bytes
14908 .zip(comment_prefix_whitespace.bytes())
14909 .take_while(|(a, b)| a == b)
14910 .count() as u32;
14911 let end = Point::new(
14912 start.row,
14913 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14914 );
14915 start..end
14916 } else {
14917 start..start
14918 }
14919 }
14920
14921 fn comment_suffix_range(
14922 snapshot: &MultiBufferSnapshot,
14923 row: MultiBufferRow,
14924 comment_suffix: &str,
14925 comment_suffix_has_leading_space: bool,
14926 ) -> Range<Point> {
14927 let end = Point::new(row.0, snapshot.line_len(row));
14928 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14929
14930 let mut line_end_bytes = snapshot
14931 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14932 .flatten()
14933 .copied();
14934
14935 let leading_space_len = if suffix_start_column > 0
14936 && line_end_bytes.next() == Some(b' ')
14937 && comment_suffix_has_leading_space
14938 {
14939 1
14940 } else {
14941 0
14942 };
14943
14944 // If this line currently begins with the line comment prefix, then record
14945 // the range containing the prefix.
14946 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14947 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14948 start..end
14949 } else {
14950 end..end
14951 }
14952 }
14953
14954 // TODO: Handle selections that cross excerpts
14955 for selection in &mut selections {
14956 let start_column = snapshot
14957 .indent_size_for_line(MultiBufferRow(selection.start.row))
14958 .len;
14959 let language = if let Some(language) =
14960 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14961 {
14962 language
14963 } else {
14964 continue;
14965 };
14966
14967 selection_edit_ranges.clear();
14968
14969 // If multiple selections contain a given row, avoid processing that
14970 // row more than once.
14971 let mut start_row = MultiBufferRow(selection.start.row);
14972 if last_toggled_row == Some(start_row) {
14973 start_row = start_row.next_row();
14974 }
14975 let end_row =
14976 if selection.end.row > selection.start.row && selection.end.column == 0 {
14977 MultiBufferRow(selection.end.row - 1)
14978 } else {
14979 MultiBufferRow(selection.end.row)
14980 };
14981 last_toggled_row = Some(end_row);
14982
14983 if start_row > end_row {
14984 continue;
14985 }
14986
14987 // If the language has line comments, toggle those.
14988 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14989
14990 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14991 if ignore_indent {
14992 full_comment_prefixes = full_comment_prefixes
14993 .into_iter()
14994 .map(|s| Arc::from(s.trim_end()))
14995 .collect();
14996 }
14997
14998 if !full_comment_prefixes.is_empty() {
14999 let first_prefix = full_comment_prefixes
15000 .first()
15001 .expect("prefixes is non-empty");
15002 let prefix_trimmed_lengths = full_comment_prefixes
15003 .iter()
15004 .map(|p| p.trim_end_matches(' ').len())
15005 .collect::<SmallVec<[usize; 4]>>();
15006
15007 let mut all_selection_lines_are_comments = true;
15008
15009 for row in start_row.0..=end_row.0 {
15010 let row = MultiBufferRow(row);
15011 if start_row < end_row && snapshot.is_line_blank(row) {
15012 continue;
15013 }
15014
15015 let prefix_range = full_comment_prefixes
15016 .iter()
15017 .zip(prefix_trimmed_lengths.iter().copied())
15018 .map(|(prefix, trimmed_prefix_len)| {
15019 comment_prefix_range(
15020 snapshot.deref(),
15021 row,
15022 &prefix[..trimmed_prefix_len],
15023 &prefix[trimmed_prefix_len..],
15024 ignore_indent,
15025 )
15026 })
15027 .max_by_key(|range| range.end.column - range.start.column)
15028 .expect("prefixes is non-empty");
15029
15030 if prefix_range.is_empty() {
15031 all_selection_lines_are_comments = false;
15032 }
15033
15034 selection_edit_ranges.push(prefix_range);
15035 }
15036
15037 if all_selection_lines_are_comments {
15038 edits.extend(
15039 selection_edit_ranges
15040 .iter()
15041 .cloned()
15042 .map(|range| (range, empty_str.clone())),
15043 );
15044 } else {
15045 let min_column = selection_edit_ranges
15046 .iter()
15047 .map(|range| range.start.column)
15048 .min()
15049 .unwrap_or(0);
15050 edits.extend(selection_edit_ranges.iter().map(|range| {
15051 let position = Point::new(range.start.row, min_column);
15052 (position..position, first_prefix.clone())
15053 }));
15054 }
15055 } else if let Some(BlockCommentConfig {
15056 start: full_comment_prefix,
15057 end: comment_suffix,
15058 ..
15059 }) = language.block_comment()
15060 {
15061 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15062 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15063 let prefix_range = comment_prefix_range(
15064 snapshot.deref(),
15065 start_row,
15066 comment_prefix,
15067 comment_prefix_whitespace,
15068 ignore_indent,
15069 );
15070 let suffix_range = comment_suffix_range(
15071 snapshot.deref(),
15072 end_row,
15073 comment_suffix.trim_start_matches(' '),
15074 comment_suffix.starts_with(' '),
15075 );
15076
15077 if prefix_range.is_empty() || suffix_range.is_empty() {
15078 edits.push((
15079 prefix_range.start..prefix_range.start,
15080 full_comment_prefix.clone(),
15081 ));
15082 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15083 suffixes_inserted.push((end_row, comment_suffix.len()));
15084 } else {
15085 edits.push((prefix_range, empty_str.clone()));
15086 edits.push((suffix_range, empty_str.clone()));
15087 }
15088 } else {
15089 continue;
15090 }
15091 }
15092
15093 drop(snapshot);
15094 this.buffer.update(cx, |buffer, cx| {
15095 buffer.edit(edits, None, cx);
15096 });
15097
15098 // Adjust selections so that they end before any comment suffixes that
15099 // were inserted.
15100 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15101 let mut selections = this.selections.all::<Point>(cx);
15102 let snapshot = this.buffer.read(cx).read(cx);
15103 for selection in &mut selections {
15104 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15105 match row.cmp(&MultiBufferRow(selection.end.row)) {
15106 Ordering::Less => {
15107 suffixes_inserted.next();
15108 continue;
15109 }
15110 Ordering::Greater => break,
15111 Ordering::Equal => {
15112 if selection.end.column == snapshot.line_len(row) {
15113 if selection.is_empty() {
15114 selection.start.column -= suffix_len as u32;
15115 }
15116 selection.end.column -= suffix_len as u32;
15117 }
15118 break;
15119 }
15120 }
15121 }
15122 }
15123
15124 drop(snapshot);
15125 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15126
15127 let selections = this.selections.all::<Point>(cx);
15128 let selections_on_single_row = selections.windows(2).all(|selections| {
15129 selections[0].start.row == selections[1].start.row
15130 && selections[0].end.row == selections[1].end.row
15131 && selections[0].start.row == selections[0].end.row
15132 });
15133 let selections_selecting = selections
15134 .iter()
15135 .any(|selection| selection.start != selection.end);
15136 let advance_downwards = action.advance_downwards
15137 && selections_on_single_row
15138 && !selections_selecting
15139 && !matches!(this.mode, EditorMode::SingleLine);
15140
15141 if advance_downwards {
15142 let snapshot = this.buffer.read(cx).snapshot(cx);
15143
15144 this.change_selections(Default::default(), window, cx, |s| {
15145 s.move_cursors_with(|display_snapshot, display_point, _| {
15146 let mut point = display_point.to_point(display_snapshot);
15147 point.row += 1;
15148 point = snapshot.clip_point(point, Bias::Left);
15149 let display_point = point.to_display_point(display_snapshot);
15150 let goal = SelectionGoal::HorizontalPosition(
15151 display_snapshot
15152 .x_for_display_point(display_point, text_layout_details)
15153 .into(),
15154 );
15155 (display_point, goal)
15156 })
15157 });
15158 }
15159 });
15160 }
15161
15162 pub fn select_enclosing_symbol(
15163 &mut self,
15164 _: &SelectEnclosingSymbol,
15165 window: &mut Window,
15166 cx: &mut Context<Self>,
15167 ) {
15168 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15169
15170 let buffer = self.buffer.read(cx).snapshot(cx);
15171 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
15172
15173 fn update_selection(
15174 selection: &Selection<usize>,
15175 buffer_snap: &MultiBufferSnapshot,
15176 ) -> Option<Selection<usize>> {
15177 let cursor = selection.head();
15178 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15179 for symbol in symbols.iter().rev() {
15180 let start = symbol.range.start.to_offset(buffer_snap);
15181 let end = symbol.range.end.to_offset(buffer_snap);
15182 let new_range = start..end;
15183 if start < selection.start || end > selection.end {
15184 return Some(Selection {
15185 id: selection.id,
15186 start: new_range.start,
15187 end: new_range.end,
15188 goal: SelectionGoal::None,
15189 reversed: selection.reversed,
15190 });
15191 }
15192 }
15193 None
15194 }
15195
15196 let mut selected_larger_symbol = false;
15197 let new_selections = old_selections
15198 .iter()
15199 .map(|selection| match update_selection(selection, &buffer) {
15200 Some(new_selection) => {
15201 if new_selection.range() != selection.range() {
15202 selected_larger_symbol = true;
15203 }
15204 new_selection
15205 }
15206 None => selection.clone(),
15207 })
15208 .collect::<Vec<_>>();
15209
15210 if selected_larger_symbol {
15211 self.change_selections(Default::default(), window, cx, |s| {
15212 s.select(new_selections);
15213 });
15214 }
15215 }
15216
15217 pub fn select_larger_syntax_node(
15218 &mut self,
15219 _: &SelectLargerSyntaxNode,
15220 window: &mut Window,
15221 cx: &mut Context<Self>,
15222 ) {
15223 let Some(visible_row_count) = self.visible_row_count() else {
15224 return;
15225 };
15226 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15227 if old_selections.is_empty() {
15228 return;
15229 }
15230
15231 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15232
15233 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15234 let buffer = self.buffer.read(cx).snapshot(cx);
15235
15236 let mut selected_larger_node = false;
15237 let mut new_selections = old_selections
15238 .iter()
15239 .map(|selection| {
15240 let old_range = selection.start..selection.end;
15241
15242 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15243 // manually select word at selection
15244 if ["string_content", "inline"].contains(&node.kind()) {
15245 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15246 // ignore if word is already selected
15247 if !word_range.is_empty() && old_range != word_range {
15248 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15249 // only select word if start and end point belongs to same word
15250 if word_range == last_word_range {
15251 selected_larger_node = true;
15252 return Selection {
15253 id: selection.id,
15254 start: word_range.start,
15255 end: word_range.end,
15256 goal: SelectionGoal::None,
15257 reversed: selection.reversed,
15258 };
15259 }
15260 }
15261 }
15262 }
15263
15264 let mut new_range = old_range.clone();
15265 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15266 new_range = range;
15267 if !node.is_named() {
15268 continue;
15269 }
15270 if !display_map.intersects_fold(new_range.start)
15271 && !display_map.intersects_fold(new_range.end)
15272 {
15273 break;
15274 }
15275 }
15276
15277 selected_larger_node |= new_range != old_range;
15278 Selection {
15279 id: selection.id,
15280 start: new_range.start,
15281 end: new_range.end,
15282 goal: SelectionGoal::None,
15283 reversed: selection.reversed,
15284 }
15285 })
15286 .collect::<Vec<_>>();
15287
15288 if !selected_larger_node {
15289 return; // don't put this call in the history
15290 }
15291
15292 // scroll based on transformation done to the last selection created by the user
15293 let (last_old, last_new) = old_selections
15294 .last()
15295 .zip(new_selections.last().cloned())
15296 .expect("old_selections isn't empty");
15297
15298 // revert selection
15299 let is_selection_reversed = {
15300 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15301 new_selections.last_mut().expect("checked above").reversed =
15302 should_newest_selection_be_reversed;
15303 should_newest_selection_be_reversed
15304 };
15305
15306 if selected_larger_node {
15307 self.select_syntax_node_history.disable_clearing = true;
15308 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15309 s.select(new_selections.clone());
15310 });
15311 self.select_syntax_node_history.disable_clearing = false;
15312 }
15313
15314 let start_row = last_new.start.to_display_point(&display_map).row().0;
15315 let end_row = last_new.end.to_display_point(&display_map).row().0;
15316 let selection_height = end_row - start_row + 1;
15317 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15318
15319 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15320 let scroll_behavior = if fits_on_the_screen {
15321 self.request_autoscroll(Autoscroll::fit(), cx);
15322 SelectSyntaxNodeScrollBehavior::FitSelection
15323 } else if is_selection_reversed {
15324 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15325 SelectSyntaxNodeScrollBehavior::CursorTop
15326 } else {
15327 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15328 SelectSyntaxNodeScrollBehavior::CursorBottom
15329 };
15330
15331 self.select_syntax_node_history.push((
15332 old_selections,
15333 scroll_behavior,
15334 is_selection_reversed,
15335 ));
15336 }
15337
15338 pub fn select_smaller_syntax_node(
15339 &mut self,
15340 _: &SelectSmallerSyntaxNode,
15341 window: &mut Window,
15342 cx: &mut Context<Self>,
15343 ) {
15344 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15345
15346 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15347 self.select_syntax_node_history.pop()
15348 {
15349 if let Some(selection) = selections.last_mut() {
15350 selection.reversed = is_selection_reversed;
15351 }
15352
15353 self.select_syntax_node_history.disable_clearing = true;
15354 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15355 s.select(selections.to_vec());
15356 });
15357 self.select_syntax_node_history.disable_clearing = false;
15358
15359 match scroll_behavior {
15360 SelectSyntaxNodeScrollBehavior::CursorTop => {
15361 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15362 }
15363 SelectSyntaxNodeScrollBehavior::FitSelection => {
15364 self.request_autoscroll(Autoscroll::fit(), cx);
15365 }
15366 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15367 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15368 }
15369 }
15370 }
15371 }
15372
15373 pub fn unwrap_syntax_node(
15374 &mut self,
15375 _: &UnwrapSyntaxNode,
15376 window: &mut Window,
15377 cx: &mut Context<Self>,
15378 ) {
15379 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15380
15381 let buffer = self.buffer.read(cx).snapshot(cx);
15382 let selections = self
15383 .selections
15384 .all::<usize>(cx)
15385 .into_iter()
15386 // subtracting the offset requires sorting
15387 .sorted_by_key(|i| i.start);
15388
15389 let full_edits = selections
15390 .into_iter()
15391 .filter_map(|selection| {
15392 let child = if selection.is_empty()
15393 && let Some((_, ancestor_range)) =
15394 buffer.syntax_ancestor(selection.start..selection.end)
15395 {
15396 ancestor_range
15397 } else {
15398 selection.range()
15399 };
15400
15401 let mut parent = child.clone();
15402 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15403 parent = ancestor_range;
15404 if parent.start < child.start || parent.end > child.end {
15405 break;
15406 }
15407 }
15408
15409 if parent == child {
15410 return None;
15411 }
15412 let text = buffer.text_for_range(child).collect::<String>();
15413 Some((selection.id, parent, text))
15414 })
15415 .collect::<Vec<_>>();
15416 if full_edits.is_empty() {
15417 return;
15418 }
15419
15420 self.transact(window, cx, |this, window, cx| {
15421 this.buffer.update(cx, |buffer, cx| {
15422 buffer.edit(
15423 full_edits
15424 .iter()
15425 .map(|(_, p, t)| (p.clone(), t.clone()))
15426 .collect::<Vec<_>>(),
15427 None,
15428 cx,
15429 );
15430 });
15431 this.change_selections(Default::default(), window, cx, |s| {
15432 let mut offset = 0;
15433 let mut selections = vec![];
15434 for (id, parent, text) in full_edits {
15435 let start = parent.start - offset;
15436 offset += parent.len() - text.len();
15437 selections.push(Selection {
15438 id,
15439 start,
15440 end: start + text.len(),
15441 reversed: false,
15442 goal: Default::default(),
15443 });
15444 }
15445 s.select(selections);
15446 });
15447 });
15448 }
15449
15450 pub fn select_next_syntax_node(
15451 &mut self,
15452 _: &SelectNextSyntaxNode,
15453 window: &mut Window,
15454 cx: &mut Context<Self>,
15455 ) {
15456 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15457 if old_selections.is_empty() {
15458 return;
15459 }
15460
15461 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15462
15463 let buffer = self.buffer.read(cx).snapshot(cx);
15464 let mut selected_sibling = false;
15465
15466 let new_selections = old_selections
15467 .iter()
15468 .map(|selection| {
15469 let old_range = selection.start..selection.end;
15470
15471 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15472 let new_range = node.byte_range();
15473 selected_sibling = true;
15474 Selection {
15475 id: selection.id,
15476 start: new_range.start,
15477 end: new_range.end,
15478 goal: SelectionGoal::None,
15479 reversed: selection.reversed,
15480 }
15481 } else {
15482 selection.clone()
15483 }
15484 })
15485 .collect::<Vec<_>>();
15486
15487 if selected_sibling {
15488 self.change_selections(
15489 SelectionEffects::scroll(Autoscroll::fit()),
15490 window,
15491 cx,
15492 |s| {
15493 s.select(new_selections);
15494 },
15495 );
15496 }
15497 }
15498
15499 pub fn select_prev_syntax_node(
15500 &mut self,
15501 _: &SelectPreviousSyntaxNode,
15502 window: &mut Window,
15503 cx: &mut Context<Self>,
15504 ) {
15505 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).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_prev_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 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15549 if !EditorSettings::get_global(cx).gutter.runnables {
15550 self.clear_tasks();
15551 return Task::ready(());
15552 }
15553 let project = self.project().map(Entity::downgrade);
15554 let task_sources = self.lsp_task_sources(cx);
15555 let multi_buffer = self.buffer.downgrade();
15556 cx.spawn_in(window, async move |editor, cx| {
15557 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15558 let Some(project) = project.and_then(|p| p.upgrade()) else {
15559 return;
15560 };
15561 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15562 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15563 }) else {
15564 return;
15565 };
15566
15567 let hide_runnables = project
15568 .update(cx, |project, _| project.is_via_collab())
15569 .unwrap_or(true);
15570 if hide_runnables {
15571 return;
15572 }
15573 let new_rows =
15574 cx.background_spawn({
15575 let snapshot = display_snapshot.clone();
15576 async move {
15577 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15578 }
15579 })
15580 .await;
15581 let Ok(lsp_tasks) =
15582 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15583 else {
15584 return;
15585 };
15586 let lsp_tasks = lsp_tasks.await;
15587
15588 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15589 lsp_tasks
15590 .into_iter()
15591 .flat_map(|(kind, tasks)| {
15592 tasks.into_iter().filter_map(move |(location, task)| {
15593 Some((kind.clone(), location?, task))
15594 })
15595 })
15596 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15597 let buffer = location.target.buffer;
15598 let buffer_snapshot = buffer.read(cx).snapshot();
15599 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15600 |(excerpt_id, snapshot, _)| {
15601 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15602 display_snapshot
15603 .buffer_snapshot()
15604 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15605 } else {
15606 None
15607 }
15608 },
15609 );
15610 if let Some(offset) = offset {
15611 let task_buffer_range =
15612 location.target.range.to_point(&buffer_snapshot);
15613 let context_buffer_range =
15614 task_buffer_range.to_offset(&buffer_snapshot);
15615 let context_range = BufferOffset(context_buffer_range.start)
15616 ..BufferOffset(context_buffer_range.end);
15617
15618 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15619 .or_insert_with(|| RunnableTasks {
15620 templates: Vec::new(),
15621 offset,
15622 column: task_buffer_range.start.column,
15623 extra_variables: HashMap::default(),
15624 context_range,
15625 })
15626 .templates
15627 .push((kind, task.original_task().clone()));
15628 }
15629
15630 acc
15631 })
15632 }) else {
15633 return;
15634 };
15635
15636 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15637 buffer.language_settings(cx).tasks.prefer_lsp
15638 }) else {
15639 return;
15640 };
15641
15642 let rows = Self::runnable_rows(
15643 project,
15644 display_snapshot,
15645 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15646 new_rows,
15647 cx.clone(),
15648 )
15649 .await;
15650 editor
15651 .update(cx, |editor, _| {
15652 editor.clear_tasks();
15653 for (key, mut value) in rows {
15654 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15655 value.templates.extend(lsp_tasks.templates);
15656 }
15657
15658 editor.insert_tasks(key, value);
15659 }
15660 for (key, value) in lsp_tasks_by_rows {
15661 editor.insert_tasks(key, value);
15662 }
15663 })
15664 .ok();
15665 })
15666 }
15667 fn fetch_runnable_ranges(
15668 snapshot: &DisplaySnapshot,
15669 range: Range<Anchor>,
15670 ) -> Vec<language::RunnableRange> {
15671 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15672 }
15673
15674 fn runnable_rows(
15675 project: Entity<Project>,
15676 snapshot: DisplaySnapshot,
15677 prefer_lsp: bool,
15678 runnable_ranges: Vec<RunnableRange>,
15679 cx: AsyncWindowContext,
15680 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15681 cx.spawn(async move |cx| {
15682 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15683 for mut runnable in runnable_ranges {
15684 let Some(tasks) = cx
15685 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15686 .ok()
15687 else {
15688 continue;
15689 };
15690 let mut tasks = tasks.await;
15691
15692 if prefer_lsp {
15693 tasks.retain(|(task_kind, _)| {
15694 !matches!(task_kind, TaskSourceKind::Language { .. })
15695 });
15696 }
15697 if tasks.is_empty() {
15698 continue;
15699 }
15700
15701 let point = runnable
15702 .run_range
15703 .start
15704 .to_point(&snapshot.buffer_snapshot());
15705 let Some(row) = snapshot
15706 .buffer_snapshot()
15707 .buffer_line_for_row(MultiBufferRow(point.row))
15708 .map(|(_, range)| range.start.row)
15709 else {
15710 continue;
15711 };
15712
15713 let context_range =
15714 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15715 runnable_rows.push((
15716 (runnable.buffer_id, row),
15717 RunnableTasks {
15718 templates: tasks,
15719 offset: snapshot
15720 .buffer_snapshot()
15721 .anchor_before(runnable.run_range.start),
15722 context_range,
15723 column: point.column,
15724 extra_variables: runnable.extra_captures,
15725 },
15726 ));
15727 }
15728 runnable_rows
15729 })
15730 }
15731
15732 fn templates_with_tags(
15733 project: &Entity<Project>,
15734 runnable: &mut Runnable,
15735 cx: &mut App,
15736 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15737 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15738 let (worktree_id, file) = project
15739 .buffer_for_id(runnable.buffer, cx)
15740 .and_then(|buffer| buffer.read(cx).file())
15741 .map(|file| (file.worktree_id(cx), file.clone()))
15742 .unzip();
15743
15744 (
15745 project.task_store().read(cx).task_inventory().cloned(),
15746 worktree_id,
15747 file,
15748 )
15749 });
15750
15751 let tags = mem::take(&mut runnable.tags);
15752 let language = runnable.language.clone();
15753 cx.spawn(async move |cx| {
15754 let mut templates_with_tags = Vec::new();
15755 if let Some(inventory) = inventory {
15756 for RunnableTag(tag) in tags {
15757 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15758 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15759 }) else {
15760 return templates_with_tags;
15761 };
15762 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15763 move |(_, template)| {
15764 template.tags.iter().any(|source_tag| source_tag == &tag)
15765 },
15766 ));
15767 }
15768 }
15769 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15770
15771 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15772 // Strongest source wins; if we have worktree tag binding, prefer that to
15773 // global and language bindings;
15774 // if we have a global binding, prefer that to language binding.
15775 let first_mismatch = templates_with_tags
15776 .iter()
15777 .position(|(tag_source, _)| tag_source != leading_tag_source);
15778 if let Some(index) = first_mismatch {
15779 templates_with_tags.truncate(index);
15780 }
15781 }
15782
15783 templates_with_tags
15784 })
15785 }
15786
15787 pub fn move_to_enclosing_bracket(
15788 &mut self,
15789 _: &MoveToEnclosingBracket,
15790 window: &mut Window,
15791 cx: &mut Context<Self>,
15792 ) {
15793 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15794 self.change_selections(Default::default(), window, cx, |s| {
15795 s.move_offsets_with(|snapshot, selection| {
15796 let Some(enclosing_bracket_ranges) =
15797 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15798 else {
15799 return;
15800 };
15801
15802 let mut best_length = usize::MAX;
15803 let mut best_inside = false;
15804 let mut best_in_bracket_range = false;
15805 let mut best_destination = None;
15806 for (open, close) in enclosing_bracket_ranges {
15807 let close = close.to_inclusive();
15808 let length = close.end() - open.start;
15809 let inside = selection.start >= open.end && selection.end <= *close.start();
15810 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15811 || close.contains(&selection.head());
15812
15813 // If best is next to a bracket and current isn't, skip
15814 if !in_bracket_range && best_in_bracket_range {
15815 continue;
15816 }
15817
15818 // Prefer smaller lengths unless best is inside and current isn't
15819 if length > best_length && (best_inside || !inside) {
15820 continue;
15821 }
15822
15823 best_length = length;
15824 best_inside = inside;
15825 best_in_bracket_range = in_bracket_range;
15826 best_destination = Some(
15827 if close.contains(&selection.start) && close.contains(&selection.end) {
15828 if inside { open.end } else { open.start }
15829 } else if inside {
15830 *close.start()
15831 } else {
15832 *close.end()
15833 },
15834 );
15835 }
15836
15837 if let Some(destination) = best_destination {
15838 selection.collapse_to(destination, SelectionGoal::None);
15839 }
15840 })
15841 });
15842 }
15843
15844 pub fn undo_selection(
15845 &mut self,
15846 _: &UndoSelection,
15847 window: &mut Window,
15848 cx: &mut Context<Self>,
15849 ) {
15850 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15851 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15852 self.selection_history.mode = SelectionHistoryMode::Undoing;
15853 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15854 this.end_selection(window, cx);
15855 this.change_selections(
15856 SelectionEffects::scroll(Autoscroll::newest()),
15857 window,
15858 cx,
15859 |s| s.select_anchors(entry.selections.to_vec()),
15860 );
15861 });
15862 self.selection_history.mode = SelectionHistoryMode::Normal;
15863
15864 self.select_next_state = entry.select_next_state;
15865 self.select_prev_state = entry.select_prev_state;
15866 self.add_selections_state = entry.add_selections_state;
15867 }
15868 }
15869
15870 pub fn redo_selection(
15871 &mut self,
15872 _: &RedoSelection,
15873 window: &mut Window,
15874 cx: &mut Context<Self>,
15875 ) {
15876 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15877 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15878 self.selection_history.mode = SelectionHistoryMode::Redoing;
15879 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15880 this.end_selection(window, cx);
15881 this.change_selections(
15882 SelectionEffects::scroll(Autoscroll::newest()),
15883 window,
15884 cx,
15885 |s| s.select_anchors(entry.selections.to_vec()),
15886 );
15887 });
15888 self.selection_history.mode = SelectionHistoryMode::Normal;
15889
15890 self.select_next_state = entry.select_next_state;
15891 self.select_prev_state = entry.select_prev_state;
15892 self.add_selections_state = entry.add_selections_state;
15893 }
15894 }
15895
15896 pub fn expand_excerpts(
15897 &mut self,
15898 action: &ExpandExcerpts,
15899 _: &mut Window,
15900 cx: &mut Context<Self>,
15901 ) {
15902 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15903 }
15904
15905 pub fn expand_excerpts_down(
15906 &mut self,
15907 action: &ExpandExcerptsDown,
15908 _: &mut Window,
15909 cx: &mut Context<Self>,
15910 ) {
15911 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15912 }
15913
15914 pub fn expand_excerpts_up(
15915 &mut self,
15916 action: &ExpandExcerptsUp,
15917 _: &mut Window,
15918 cx: &mut Context<Self>,
15919 ) {
15920 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15921 }
15922
15923 pub fn expand_excerpts_for_direction(
15924 &mut self,
15925 lines: u32,
15926 direction: ExpandExcerptDirection,
15927
15928 cx: &mut Context<Self>,
15929 ) {
15930 let selections = self.selections.disjoint_anchors_arc();
15931
15932 let lines = if lines == 0 {
15933 EditorSettings::get_global(cx).expand_excerpt_lines
15934 } else {
15935 lines
15936 };
15937
15938 self.buffer.update(cx, |buffer, cx| {
15939 let snapshot = buffer.snapshot(cx);
15940 let mut excerpt_ids = selections
15941 .iter()
15942 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15943 .collect::<Vec<_>>();
15944 excerpt_ids.sort();
15945 excerpt_ids.dedup();
15946 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15947 })
15948 }
15949
15950 pub fn expand_excerpt(
15951 &mut self,
15952 excerpt: ExcerptId,
15953 direction: ExpandExcerptDirection,
15954 window: &mut Window,
15955 cx: &mut Context<Self>,
15956 ) {
15957 let current_scroll_position = self.scroll_position(cx);
15958 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15959 let mut should_scroll_up = false;
15960
15961 if direction == ExpandExcerptDirection::Down {
15962 let multi_buffer = self.buffer.read(cx);
15963 let snapshot = multi_buffer.snapshot(cx);
15964 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15965 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15966 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
15967 {
15968 let buffer_snapshot = buffer.read(cx).snapshot();
15969 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15970 let last_row = buffer_snapshot.max_point().row;
15971 let lines_below = last_row.saturating_sub(excerpt_end_row);
15972 should_scroll_up = lines_below >= lines_to_expand;
15973 }
15974 }
15975
15976 self.buffer.update(cx, |buffer, cx| {
15977 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15978 });
15979
15980 if should_scroll_up {
15981 let new_scroll_position =
15982 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as ScrollOffset);
15983 self.set_scroll_position(new_scroll_position, window, cx);
15984 }
15985 }
15986
15987 pub fn go_to_singleton_buffer_point(
15988 &mut self,
15989 point: Point,
15990 window: &mut Window,
15991 cx: &mut Context<Self>,
15992 ) {
15993 self.go_to_singleton_buffer_range(point..point, window, cx);
15994 }
15995
15996 pub fn go_to_singleton_buffer_range(
15997 &mut self,
15998 range: Range<Point>,
15999 window: &mut Window,
16000 cx: &mut Context<Self>,
16001 ) {
16002 let multibuffer = self.buffer().read(cx);
16003 let Some(buffer) = multibuffer.as_singleton() else {
16004 return;
16005 };
16006 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16007 return;
16008 };
16009 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16010 return;
16011 };
16012 self.change_selections(
16013 SelectionEffects::default().nav_history(true),
16014 window,
16015 cx,
16016 |s| s.select_anchor_ranges([start..end]),
16017 );
16018 }
16019
16020 pub fn go_to_diagnostic(
16021 &mut self,
16022 action: &GoToDiagnostic,
16023 window: &mut Window,
16024 cx: &mut Context<Self>,
16025 ) {
16026 if !self.diagnostics_enabled() {
16027 return;
16028 }
16029 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16030 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16031 }
16032
16033 pub fn go_to_prev_diagnostic(
16034 &mut self,
16035 action: &GoToPreviousDiagnostic,
16036 window: &mut Window,
16037 cx: &mut Context<Self>,
16038 ) {
16039 if !self.diagnostics_enabled() {
16040 return;
16041 }
16042 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16043 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16044 }
16045
16046 pub fn go_to_diagnostic_impl(
16047 &mut self,
16048 direction: Direction,
16049 severity: GoToDiagnosticSeverityFilter,
16050 window: &mut Window,
16051 cx: &mut Context<Self>,
16052 ) {
16053 let buffer = self.buffer.read(cx).snapshot(cx);
16054 let selection = self.selections.newest::<usize>(cx);
16055
16056 let mut active_group_id = None;
16057 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16058 && active_group.active_range.start.to_offset(&buffer) == selection.start
16059 {
16060 active_group_id = Some(active_group.group_id);
16061 }
16062
16063 fn filtered<'a>(
16064 snapshot: EditorSnapshot,
16065 severity: GoToDiagnosticSeverityFilter,
16066 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
16067 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
16068 diagnostics
16069 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16070 .filter(|entry| entry.range.start != entry.range.end)
16071 .filter(|entry| !entry.diagnostic.is_unnecessary)
16072 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
16073 }
16074
16075 let snapshot = self.snapshot(window, cx);
16076 let before = filtered(
16077 snapshot.clone(),
16078 severity,
16079 buffer
16080 .diagnostics_in_range(0..selection.start)
16081 .filter(|entry| entry.range.start <= selection.start),
16082 );
16083 let after = filtered(
16084 snapshot,
16085 severity,
16086 buffer
16087 .diagnostics_in_range(selection.start..buffer.len())
16088 .filter(|entry| entry.range.start >= selection.start),
16089 );
16090
16091 let mut found: Option<DiagnosticEntryRef<usize>> = None;
16092 if direction == Direction::Prev {
16093 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16094 {
16095 for diagnostic in prev_diagnostics.into_iter().rev() {
16096 if diagnostic.range.start != selection.start
16097 || active_group_id
16098 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16099 {
16100 found = Some(diagnostic);
16101 break 'outer;
16102 }
16103 }
16104 }
16105 } else {
16106 for diagnostic in after.chain(before) {
16107 if diagnostic.range.start != selection.start
16108 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16109 {
16110 found = Some(diagnostic);
16111 break;
16112 }
16113 }
16114 }
16115 let Some(next_diagnostic) = found else {
16116 return;
16117 };
16118
16119 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16120 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16121 return;
16122 };
16123 self.change_selections(Default::default(), window, cx, |s| {
16124 s.select_ranges(vec![
16125 next_diagnostic.range.start..next_diagnostic.range.start,
16126 ])
16127 });
16128 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16129 self.refresh_edit_prediction(false, true, window, cx);
16130 }
16131
16132 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16133 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16134 let snapshot = self.snapshot(window, cx);
16135 let selection = self.selections.newest::<Point>(cx);
16136 self.go_to_hunk_before_or_after_position(
16137 &snapshot,
16138 selection.head(),
16139 Direction::Next,
16140 window,
16141 cx,
16142 );
16143 }
16144
16145 pub fn go_to_hunk_before_or_after_position(
16146 &mut self,
16147 snapshot: &EditorSnapshot,
16148 position: Point,
16149 direction: Direction,
16150 window: &mut Window,
16151 cx: &mut Context<Editor>,
16152 ) {
16153 let row = if direction == Direction::Next {
16154 self.hunk_after_position(snapshot, position)
16155 .map(|hunk| hunk.row_range.start)
16156 } else {
16157 self.hunk_before_position(snapshot, position)
16158 };
16159
16160 if let Some(row) = row {
16161 let destination = Point::new(row.0, 0);
16162 let autoscroll = Autoscroll::center();
16163
16164 self.unfold_ranges(&[destination..destination], false, false, cx);
16165 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16166 s.select_ranges([destination..destination]);
16167 });
16168 }
16169 }
16170
16171 fn hunk_after_position(
16172 &mut self,
16173 snapshot: &EditorSnapshot,
16174 position: Point,
16175 ) -> Option<MultiBufferDiffHunk> {
16176 snapshot
16177 .buffer_snapshot()
16178 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16179 .find(|hunk| hunk.row_range.start.0 > position.row)
16180 .or_else(|| {
16181 snapshot
16182 .buffer_snapshot()
16183 .diff_hunks_in_range(Point::zero()..position)
16184 .find(|hunk| hunk.row_range.end.0 < position.row)
16185 })
16186 }
16187
16188 fn go_to_prev_hunk(
16189 &mut self,
16190 _: &GoToPreviousHunk,
16191 window: &mut Window,
16192 cx: &mut Context<Self>,
16193 ) {
16194 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16195 let snapshot = self.snapshot(window, cx);
16196 let selection = self.selections.newest::<Point>(cx);
16197 self.go_to_hunk_before_or_after_position(
16198 &snapshot,
16199 selection.head(),
16200 Direction::Prev,
16201 window,
16202 cx,
16203 );
16204 }
16205
16206 fn hunk_before_position(
16207 &mut self,
16208 snapshot: &EditorSnapshot,
16209 position: Point,
16210 ) -> Option<MultiBufferRow> {
16211 snapshot
16212 .buffer_snapshot()
16213 .diff_hunk_before(position)
16214 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16215 }
16216
16217 fn go_to_next_change(
16218 &mut self,
16219 _: &GoToNextChange,
16220 window: &mut Window,
16221 cx: &mut Context<Self>,
16222 ) {
16223 if let Some(selections) = self
16224 .change_list
16225 .next_change(1, Direction::Next)
16226 .map(|s| s.to_vec())
16227 {
16228 self.change_selections(Default::default(), window, cx, |s| {
16229 let map = s.display_map();
16230 s.select_display_ranges(selections.iter().map(|a| {
16231 let point = a.to_display_point(&map);
16232 point..point
16233 }))
16234 })
16235 }
16236 }
16237
16238 fn go_to_previous_change(
16239 &mut self,
16240 _: &GoToPreviousChange,
16241 window: &mut Window,
16242 cx: &mut Context<Self>,
16243 ) {
16244 if let Some(selections) = self
16245 .change_list
16246 .next_change(1, Direction::Prev)
16247 .map(|s| s.to_vec())
16248 {
16249 self.change_selections(Default::default(), window, cx, |s| {
16250 let map = s.display_map();
16251 s.select_display_ranges(selections.iter().map(|a| {
16252 let point = a.to_display_point(&map);
16253 point..point
16254 }))
16255 })
16256 }
16257 }
16258
16259 pub fn go_to_next_document_highlight(
16260 &mut self,
16261 _: &GoToNextDocumentHighlight,
16262 window: &mut Window,
16263 cx: &mut Context<Self>,
16264 ) {
16265 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16266 }
16267
16268 pub fn go_to_prev_document_highlight(
16269 &mut self,
16270 _: &GoToPreviousDocumentHighlight,
16271 window: &mut Window,
16272 cx: &mut Context<Self>,
16273 ) {
16274 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16275 }
16276
16277 pub fn go_to_document_highlight_before_or_after_position(
16278 &mut self,
16279 direction: Direction,
16280 window: &mut Window,
16281 cx: &mut Context<Editor>,
16282 ) {
16283 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16284 let snapshot = self.snapshot(window, cx);
16285 let buffer = &snapshot.buffer_snapshot();
16286 let position = self.selections.newest::<Point>(cx).head();
16287 let anchor_position = buffer.anchor_after(position);
16288
16289 // Get all document highlights (both read and write)
16290 let mut all_highlights = Vec::new();
16291
16292 if let Some((_, read_highlights)) = self
16293 .background_highlights
16294 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16295 {
16296 all_highlights.extend(read_highlights.iter());
16297 }
16298
16299 if let Some((_, write_highlights)) = self
16300 .background_highlights
16301 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16302 {
16303 all_highlights.extend(write_highlights.iter());
16304 }
16305
16306 if all_highlights.is_empty() {
16307 return;
16308 }
16309
16310 // Sort highlights by position
16311 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16312
16313 let target_highlight = match direction {
16314 Direction::Next => {
16315 // Find the first highlight after the current position
16316 all_highlights
16317 .iter()
16318 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16319 }
16320 Direction::Prev => {
16321 // Find the last highlight before the current position
16322 all_highlights
16323 .iter()
16324 .rev()
16325 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16326 }
16327 };
16328
16329 if let Some(highlight) = target_highlight {
16330 let destination = highlight.start.to_point(buffer);
16331 let autoscroll = Autoscroll::center();
16332
16333 self.unfold_ranges(&[destination..destination], false, false, cx);
16334 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16335 s.select_ranges([destination..destination]);
16336 });
16337 }
16338 }
16339
16340 fn go_to_line<T: 'static>(
16341 &mut self,
16342 position: Anchor,
16343 highlight_color: Option<Hsla>,
16344 window: &mut Window,
16345 cx: &mut Context<Self>,
16346 ) {
16347 let snapshot = self.snapshot(window, cx).display_snapshot;
16348 let position = position.to_point(&snapshot.buffer_snapshot());
16349 let start = snapshot
16350 .buffer_snapshot()
16351 .clip_point(Point::new(position.row, 0), Bias::Left);
16352 let end = start + Point::new(1, 0);
16353 let start = snapshot.buffer_snapshot().anchor_before(start);
16354 let end = snapshot.buffer_snapshot().anchor_before(end);
16355
16356 self.highlight_rows::<T>(
16357 start..end,
16358 highlight_color
16359 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16360 Default::default(),
16361 cx,
16362 );
16363
16364 if self.buffer.read(cx).is_singleton() {
16365 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16366 }
16367 }
16368
16369 pub fn go_to_definition(
16370 &mut self,
16371 _: &GoToDefinition,
16372 window: &mut Window,
16373 cx: &mut Context<Self>,
16374 ) -> Task<Result<Navigated>> {
16375 let definition =
16376 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16377 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16378 cx.spawn_in(window, async move |editor, cx| {
16379 if definition.await? == Navigated::Yes {
16380 return Ok(Navigated::Yes);
16381 }
16382 match fallback_strategy {
16383 GoToDefinitionFallback::None => Ok(Navigated::No),
16384 GoToDefinitionFallback::FindAllReferences => {
16385 match editor.update_in(cx, |editor, window, cx| {
16386 editor.find_all_references(&FindAllReferences, window, cx)
16387 })? {
16388 Some(references) => references.await,
16389 None => Ok(Navigated::No),
16390 }
16391 }
16392 }
16393 })
16394 }
16395
16396 pub fn go_to_declaration(
16397 &mut self,
16398 _: &GoToDeclaration,
16399 window: &mut Window,
16400 cx: &mut Context<Self>,
16401 ) -> Task<Result<Navigated>> {
16402 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16403 }
16404
16405 pub fn go_to_declaration_split(
16406 &mut self,
16407 _: &GoToDeclaration,
16408 window: &mut Window,
16409 cx: &mut Context<Self>,
16410 ) -> Task<Result<Navigated>> {
16411 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16412 }
16413
16414 pub fn go_to_implementation(
16415 &mut self,
16416 _: &GoToImplementation,
16417 window: &mut Window,
16418 cx: &mut Context<Self>,
16419 ) -> Task<Result<Navigated>> {
16420 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16421 }
16422
16423 pub fn go_to_implementation_split(
16424 &mut self,
16425 _: &GoToImplementationSplit,
16426 window: &mut Window,
16427 cx: &mut Context<Self>,
16428 ) -> Task<Result<Navigated>> {
16429 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16430 }
16431
16432 pub fn go_to_type_definition(
16433 &mut self,
16434 _: &GoToTypeDefinition,
16435 window: &mut Window,
16436 cx: &mut Context<Self>,
16437 ) -> Task<Result<Navigated>> {
16438 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16439 }
16440
16441 pub fn go_to_definition_split(
16442 &mut self,
16443 _: &GoToDefinitionSplit,
16444 window: &mut Window,
16445 cx: &mut Context<Self>,
16446 ) -> Task<Result<Navigated>> {
16447 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16448 }
16449
16450 pub fn go_to_type_definition_split(
16451 &mut self,
16452 _: &GoToTypeDefinitionSplit,
16453 window: &mut Window,
16454 cx: &mut Context<Self>,
16455 ) -> Task<Result<Navigated>> {
16456 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16457 }
16458
16459 fn go_to_definition_of_kind(
16460 &mut self,
16461 kind: GotoDefinitionKind,
16462 split: bool,
16463 window: &mut Window,
16464 cx: &mut Context<Self>,
16465 ) -> Task<Result<Navigated>> {
16466 let Some(provider) = self.semantics_provider.clone() else {
16467 return Task::ready(Ok(Navigated::No));
16468 };
16469 let head = self.selections.newest::<usize>(cx).head();
16470 let buffer = self.buffer.read(cx);
16471 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16472 return Task::ready(Ok(Navigated::No));
16473 };
16474 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16475 return Task::ready(Ok(Navigated::No));
16476 };
16477
16478 cx.spawn_in(window, async move |editor, cx| {
16479 let Some(definitions) = definitions.await? else {
16480 return Ok(Navigated::No);
16481 };
16482 let navigated = editor
16483 .update_in(cx, |editor, window, cx| {
16484 editor.navigate_to_hover_links(
16485 Some(kind),
16486 definitions
16487 .into_iter()
16488 .filter(|location| {
16489 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16490 })
16491 .map(HoverLink::Text)
16492 .collect::<Vec<_>>(),
16493 split,
16494 window,
16495 cx,
16496 )
16497 })?
16498 .await?;
16499 anyhow::Ok(navigated)
16500 })
16501 }
16502
16503 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16504 let selection = self.selections.newest_anchor();
16505 let head = selection.head();
16506 let tail = selection.tail();
16507
16508 let Some((buffer, start_position)) =
16509 self.buffer.read(cx).text_anchor_for_position(head, cx)
16510 else {
16511 return;
16512 };
16513
16514 let end_position = if head != tail {
16515 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16516 return;
16517 };
16518 Some(pos)
16519 } else {
16520 None
16521 };
16522
16523 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16524 let url = if let Some(end_pos) = end_position {
16525 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16526 } else {
16527 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16528 };
16529
16530 if let Some(url) = url {
16531 cx.update(|window, cx| {
16532 if parse_zed_link(&url, cx).is_some() {
16533 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16534 } else {
16535 cx.open_url(&url);
16536 }
16537 })?;
16538 }
16539
16540 anyhow::Ok(())
16541 });
16542
16543 url_finder.detach();
16544 }
16545
16546 pub fn open_selected_filename(
16547 &mut self,
16548 _: &OpenSelectedFilename,
16549 window: &mut Window,
16550 cx: &mut Context<Self>,
16551 ) {
16552 let Some(workspace) = self.workspace() else {
16553 return;
16554 };
16555
16556 let position = self.selections.newest_anchor().head();
16557
16558 let Some((buffer, buffer_position)) =
16559 self.buffer.read(cx).text_anchor_for_position(position, cx)
16560 else {
16561 return;
16562 };
16563
16564 let project = self.project.clone();
16565
16566 cx.spawn_in(window, async move |_, cx| {
16567 let result = find_file(&buffer, project, buffer_position, cx).await;
16568
16569 if let Some((_, path)) = result {
16570 workspace
16571 .update_in(cx, |workspace, window, cx| {
16572 workspace.open_resolved_path(path, window, cx)
16573 })?
16574 .await?;
16575 }
16576 anyhow::Ok(())
16577 })
16578 .detach();
16579 }
16580
16581 pub(crate) fn navigate_to_hover_links(
16582 &mut self,
16583 kind: Option<GotoDefinitionKind>,
16584 definitions: Vec<HoverLink>,
16585 split: bool,
16586 window: &mut Window,
16587 cx: &mut Context<Editor>,
16588 ) -> Task<Result<Navigated>> {
16589 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16590 let mut first_url_or_file = None;
16591 let definitions: Vec<_> = definitions
16592 .into_iter()
16593 .filter_map(|def| match def {
16594 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16595 HoverLink::InlayHint(lsp_location, server_id) => {
16596 let computation =
16597 self.compute_target_location(lsp_location, server_id, window, cx);
16598 Some(cx.background_spawn(computation))
16599 }
16600 HoverLink::Url(url) => {
16601 first_url_or_file = Some(Either::Left(url));
16602 None
16603 }
16604 HoverLink::File(path) => {
16605 first_url_or_file = Some(Either::Right(path));
16606 None
16607 }
16608 })
16609 .collect();
16610
16611 let workspace = self.workspace();
16612
16613 cx.spawn_in(window, async move |editor, cx| {
16614 let locations: Vec<Location> = future::join_all(definitions)
16615 .await
16616 .into_iter()
16617 .filter_map(|location| location.transpose())
16618 .collect::<Result<_>>()
16619 .context("location tasks")?;
16620 let mut locations = cx.update(|_, cx| {
16621 locations
16622 .into_iter()
16623 .map(|location| {
16624 let buffer = location.buffer.read(cx);
16625 (location.buffer, location.range.to_point(buffer))
16626 })
16627 .into_group_map()
16628 })?;
16629 let mut num_locations = 0;
16630 for ranges in locations.values_mut() {
16631 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16632 ranges.dedup();
16633 num_locations += ranges.len();
16634 }
16635
16636 if num_locations > 1 {
16637 let Some(workspace) = workspace else {
16638 return Ok(Navigated::No);
16639 };
16640
16641 let tab_kind = match kind {
16642 Some(GotoDefinitionKind::Implementation) => "Implementations",
16643 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16644 Some(GotoDefinitionKind::Declaration) => "Declarations",
16645 Some(GotoDefinitionKind::Type) => "Types",
16646 };
16647 let title = editor
16648 .update_in(cx, |_, _, cx| {
16649 let target = locations
16650 .iter()
16651 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16652 .map(|(buffer, location)| {
16653 buffer
16654 .read(cx)
16655 .text_for_range(location.clone())
16656 .collect::<String>()
16657 })
16658 .filter(|text| !text.contains('\n'))
16659 .unique()
16660 .take(3)
16661 .join(", ");
16662 if target.is_empty() {
16663 tab_kind.to_owned()
16664 } else {
16665 format!("{tab_kind} for {target}")
16666 }
16667 })
16668 .context("buffer title")?;
16669
16670 let opened = workspace
16671 .update_in(cx, |workspace, window, cx| {
16672 Self::open_locations_in_multibuffer(
16673 workspace,
16674 locations,
16675 title,
16676 split,
16677 MultibufferSelectionMode::First,
16678 window,
16679 cx,
16680 )
16681 })
16682 .is_ok();
16683
16684 anyhow::Ok(Navigated::from_bool(opened))
16685 } else if num_locations == 0 {
16686 // If there is one url or file, open it directly
16687 match first_url_or_file {
16688 Some(Either::Left(url)) => {
16689 cx.update(|_, cx| cx.open_url(&url))?;
16690 Ok(Navigated::Yes)
16691 }
16692 Some(Either::Right(path)) => {
16693 let Some(workspace) = workspace else {
16694 return Ok(Navigated::No);
16695 };
16696
16697 workspace
16698 .update_in(cx, |workspace, window, cx| {
16699 workspace.open_resolved_path(path, window, cx)
16700 })?
16701 .await?;
16702 Ok(Navigated::Yes)
16703 }
16704 None => Ok(Navigated::No),
16705 }
16706 } else {
16707 let Some(workspace) = workspace else {
16708 return Ok(Navigated::No);
16709 };
16710
16711 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16712 let target_range = target_ranges.first().unwrap().clone();
16713
16714 editor.update_in(cx, |editor, window, cx| {
16715 let range = target_range.to_point(target_buffer.read(cx));
16716 let range = editor.range_for_match(&range);
16717 let range = collapse_multiline_range(range);
16718
16719 if !split
16720 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16721 {
16722 editor.go_to_singleton_buffer_range(range, window, cx);
16723 } else {
16724 let pane = workspace.read(cx).active_pane().clone();
16725 window.defer(cx, move |window, cx| {
16726 let target_editor: Entity<Self> =
16727 workspace.update(cx, |workspace, cx| {
16728 let pane = if split {
16729 workspace.adjacent_pane(window, cx)
16730 } else {
16731 workspace.active_pane().clone()
16732 };
16733
16734 workspace.open_project_item(
16735 pane,
16736 target_buffer.clone(),
16737 true,
16738 true,
16739 window,
16740 cx,
16741 )
16742 });
16743 target_editor.update(cx, |target_editor, cx| {
16744 // When selecting a definition in a different buffer, disable the nav history
16745 // to avoid creating a history entry at the previous cursor location.
16746 pane.update(cx, |pane, _| pane.disable_history());
16747 target_editor.go_to_singleton_buffer_range(range, window, cx);
16748 pane.update(cx, |pane, _| pane.enable_history());
16749 });
16750 });
16751 }
16752 Navigated::Yes
16753 })
16754 }
16755 })
16756 }
16757
16758 fn compute_target_location(
16759 &self,
16760 lsp_location: lsp::Location,
16761 server_id: LanguageServerId,
16762 window: &mut Window,
16763 cx: &mut Context<Self>,
16764 ) -> Task<anyhow::Result<Option<Location>>> {
16765 let Some(project) = self.project.clone() else {
16766 return Task::ready(Ok(None));
16767 };
16768
16769 cx.spawn_in(window, async move |editor, cx| {
16770 let location_task = editor.update(cx, |_, cx| {
16771 project.update(cx, |project, cx| {
16772 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16773 })
16774 })?;
16775 let location = Some({
16776 let target_buffer_handle = location_task.await.context("open local buffer")?;
16777 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16778 let target_start = target_buffer
16779 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16780 let target_end = target_buffer
16781 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16782 target_buffer.anchor_after(target_start)
16783 ..target_buffer.anchor_before(target_end)
16784 })?;
16785 Location {
16786 buffer: target_buffer_handle,
16787 range,
16788 }
16789 });
16790 Ok(location)
16791 })
16792 }
16793
16794 pub fn find_all_references(
16795 &mut self,
16796 _: &FindAllReferences,
16797 window: &mut Window,
16798 cx: &mut Context<Self>,
16799 ) -> Option<Task<Result<Navigated>>> {
16800 let selection = self.selections.newest::<usize>(cx);
16801 let multi_buffer = self.buffer.read(cx);
16802 let head = selection.head();
16803
16804 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16805 let head_anchor = multi_buffer_snapshot.anchor_at(
16806 head,
16807 if head < selection.tail() {
16808 Bias::Right
16809 } else {
16810 Bias::Left
16811 },
16812 );
16813
16814 match self
16815 .find_all_references_task_sources
16816 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16817 {
16818 Ok(_) => {
16819 log::info!(
16820 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16821 );
16822 return None;
16823 }
16824 Err(i) => {
16825 self.find_all_references_task_sources.insert(i, head_anchor);
16826 }
16827 }
16828
16829 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16830 let workspace = self.workspace()?;
16831 let project = workspace.read(cx).project().clone();
16832 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16833 Some(cx.spawn_in(window, async move |editor, cx| {
16834 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16835 if let Ok(i) = editor
16836 .find_all_references_task_sources
16837 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16838 {
16839 editor.find_all_references_task_sources.remove(i);
16840 }
16841 });
16842
16843 let Some(locations) = references.await? else {
16844 return anyhow::Ok(Navigated::No);
16845 };
16846 let mut locations = cx.update(|_, cx| {
16847 locations
16848 .into_iter()
16849 .map(|location| {
16850 let buffer = location.buffer.read(cx);
16851 (location.buffer, location.range.to_point(buffer))
16852 })
16853 .into_group_map()
16854 })?;
16855 if locations.is_empty() {
16856 return anyhow::Ok(Navigated::No);
16857 }
16858 for ranges in locations.values_mut() {
16859 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16860 ranges.dedup();
16861 }
16862
16863 workspace.update_in(cx, |workspace, window, cx| {
16864 let target = locations
16865 .iter()
16866 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16867 .map(|(buffer, location)| {
16868 buffer
16869 .read(cx)
16870 .text_for_range(location.clone())
16871 .collect::<String>()
16872 })
16873 .filter(|text| !text.contains('\n'))
16874 .unique()
16875 .take(3)
16876 .join(", ");
16877 let title = if target.is_empty() {
16878 "References".to_owned()
16879 } else {
16880 format!("References to {target}")
16881 };
16882 Self::open_locations_in_multibuffer(
16883 workspace,
16884 locations,
16885 title,
16886 false,
16887 MultibufferSelectionMode::First,
16888 window,
16889 cx,
16890 );
16891 Navigated::Yes
16892 })
16893 }))
16894 }
16895
16896 /// Opens a multibuffer with the given project locations in it
16897 pub fn open_locations_in_multibuffer(
16898 workspace: &mut Workspace,
16899 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
16900 title: String,
16901 split: bool,
16902 multibuffer_selection_mode: MultibufferSelectionMode,
16903 window: &mut Window,
16904 cx: &mut Context<Workspace>,
16905 ) {
16906 if locations.is_empty() {
16907 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16908 return;
16909 }
16910
16911 let capability = workspace.project().read(cx).capability();
16912 let mut ranges = <Vec<Range<Anchor>>>::new();
16913
16914 // a key to find existing multibuffer editors with the same set of locations
16915 // to prevent us from opening more and more multibuffer tabs for searches and the like
16916 let mut key = (title.clone(), vec![]);
16917 let excerpt_buffer = cx.new(|cx| {
16918 let key = &mut key.1;
16919 let mut multibuffer = MultiBuffer::new(capability);
16920 for (buffer, mut ranges_for_buffer) in locations {
16921 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16922 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
16923 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16924 PathKey::for_buffer(&buffer, cx),
16925 buffer.clone(),
16926 ranges_for_buffer,
16927 multibuffer_context_lines(cx),
16928 cx,
16929 );
16930 ranges.extend(new_ranges)
16931 }
16932
16933 multibuffer.with_title(title)
16934 });
16935 let existing = workspace.active_pane().update(cx, |pane, cx| {
16936 pane.items()
16937 .filter_map(|item| item.downcast::<Editor>())
16938 .find(|editor| {
16939 editor
16940 .read(cx)
16941 .lookup_key
16942 .as_ref()
16943 .and_then(|it| {
16944 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
16945 })
16946 .is_some_and(|it| *it == key)
16947 })
16948 });
16949 let editor = existing.unwrap_or_else(|| {
16950 cx.new(|cx| {
16951 let mut editor = Editor::for_multibuffer(
16952 excerpt_buffer,
16953 Some(workspace.project().clone()),
16954 window,
16955 cx,
16956 );
16957 editor.lookup_key = Some(Box::new(key));
16958 editor
16959 })
16960 });
16961 editor.update(cx, |editor, cx| {
16962 match multibuffer_selection_mode {
16963 MultibufferSelectionMode::First => {
16964 if let Some(first_range) = ranges.first() {
16965 editor.change_selections(
16966 SelectionEffects::no_scroll(),
16967 window,
16968 cx,
16969 |selections| {
16970 selections.clear_disjoint();
16971 selections
16972 .select_anchor_ranges(std::iter::once(first_range.clone()));
16973 },
16974 );
16975 }
16976 editor.highlight_background::<Self>(
16977 &ranges,
16978 |theme| theme.colors().editor_highlighted_line_background,
16979 cx,
16980 );
16981 }
16982 MultibufferSelectionMode::All => {
16983 editor.change_selections(
16984 SelectionEffects::no_scroll(),
16985 window,
16986 cx,
16987 |selections| {
16988 selections.clear_disjoint();
16989 selections.select_anchor_ranges(ranges);
16990 },
16991 );
16992 }
16993 }
16994 editor.register_buffers_with_language_servers(cx);
16995 });
16996
16997 let item = Box::new(editor);
16998 let item_id = item.item_id();
16999
17000 if split {
17001 let pane = workspace.adjacent_pane(window, cx);
17002 workspace.add_item(pane, item, None, true, true, window, cx);
17003 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17004 let (preview_item_id, preview_item_idx) =
17005 workspace.active_pane().read_with(cx, |pane, _| {
17006 (pane.preview_item_id(), pane.preview_item_idx())
17007 });
17008
17009 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17010
17011 if let Some(preview_item_id) = preview_item_id {
17012 workspace.active_pane().update(cx, |pane, cx| {
17013 pane.remove_item(preview_item_id, false, false, window, cx);
17014 });
17015 }
17016 } else {
17017 workspace.add_item_to_active_pane(item, None, true, window, cx);
17018 }
17019 workspace.active_pane().update(cx, |pane, cx| {
17020 pane.set_preview_item_id(Some(item_id), cx);
17021 });
17022 }
17023
17024 pub fn rename(
17025 &mut self,
17026 _: &Rename,
17027 window: &mut Window,
17028 cx: &mut Context<Self>,
17029 ) -> Option<Task<Result<()>>> {
17030 use language::ToOffset as _;
17031
17032 let provider = self.semantics_provider.clone()?;
17033 let selection = self.selections.newest_anchor().clone();
17034 let (cursor_buffer, cursor_buffer_position) = self
17035 .buffer
17036 .read(cx)
17037 .text_anchor_for_position(selection.head(), cx)?;
17038 let (tail_buffer, cursor_buffer_position_end) = self
17039 .buffer
17040 .read(cx)
17041 .text_anchor_for_position(selection.tail(), cx)?;
17042 if tail_buffer != cursor_buffer {
17043 return None;
17044 }
17045
17046 let snapshot = cursor_buffer.read(cx).snapshot();
17047 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17048 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17049 let prepare_rename = provider
17050 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17051 .unwrap_or_else(|| Task::ready(Ok(None)));
17052 drop(snapshot);
17053
17054 Some(cx.spawn_in(window, async move |this, cx| {
17055 let rename_range = if let Some(range) = prepare_rename.await? {
17056 Some(range)
17057 } else {
17058 this.update(cx, |this, cx| {
17059 let buffer = this.buffer.read(cx).snapshot(cx);
17060 let mut buffer_highlights = this
17061 .document_highlights_for_position(selection.head(), &buffer)
17062 .filter(|highlight| {
17063 highlight.start.excerpt_id == selection.head().excerpt_id
17064 && highlight.end.excerpt_id == selection.head().excerpt_id
17065 });
17066 buffer_highlights
17067 .next()
17068 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17069 })?
17070 };
17071 if let Some(rename_range) = rename_range {
17072 this.update_in(cx, |this, window, cx| {
17073 let snapshot = cursor_buffer.read(cx).snapshot();
17074 let rename_buffer_range = rename_range.to_offset(&snapshot);
17075 let cursor_offset_in_rename_range =
17076 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17077 let cursor_offset_in_rename_range_end =
17078 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17079
17080 this.take_rename(false, window, cx);
17081 let buffer = this.buffer.read(cx).read(cx);
17082 let cursor_offset = selection.head().to_offset(&buffer);
17083 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17084 let rename_end = rename_start + rename_buffer_range.len();
17085 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17086 let mut old_highlight_id = None;
17087 let old_name: Arc<str> = buffer
17088 .chunks(rename_start..rename_end, true)
17089 .map(|chunk| {
17090 if old_highlight_id.is_none() {
17091 old_highlight_id = chunk.syntax_highlight_id;
17092 }
17093 chunk.text
17094 })
17095 .collect::<String>()
17096 .into();
17097
17098 drop(buffer);
17099
17100 // Position the selection in the rename editor so that it matches the current selection.
17101 this.show_local_selections = false;
17102 let rename_editor = cx.new(|cx| {
17103 let mut editor = Editor::single_line(window, cx);
17104 editor.buffer.update(cx, |buffer, cx| {
17105 buffer.edit([(0..0, old_name.clone())], None, cx)
17106 });
17107 let rename_selection_range = match cursor_offset_in_rename_range
17108 .cmp(&cursor_offset_in_rename_range_end)
17109 {
17110 Ordering::Equal => {
17111 editor.select_all(&SelectAll, window, cx);
17112 return editor;
17113 }
17114 Ordering::Less => {
17115 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17116 }
17117 Ordering::Greater => {
17118 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17119 }
17120 };
17121 if rename_selection_range.end > old_name.len() {
17122 editor.select_all(&SelectAll, window, cx);
17123 } else {
17124 editor.change_selections(Default::default(), window, cx, |s| {
17125 s.select_ranges([rename_selection_range]);
17126 });
17127 }
17128 editor
17129 });
17130 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17131 if e == &EditorEvent::Focused {
17132 cx.emit(EditorEvent::FocusedIn)
17133 }
17134 })
17135 .detach();
17136
17137 let write_highlights =
17138 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17139 let read_highlights =
17140 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17141 let ranges = write_highlights
17142 .iter()
17143 .flat_map(|(_, ranges)| ranges.iter())
17144 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17145 .cloned()
17146 .collect();
17147
17148 this.highlight_text::<Rename>(
17149 ranges,
17150 HighlightStyle {
17151 fade_out: Some(0.6),
17152 ..Default::default()
17153 },
17154 cx,
17155 );
17156 let rename_focus_handle = rename_editor.focus_handle(cx);
17157 window.focus(&rename_focus_handle);
17158 let block_id = this.insert_blocks(
17159 [BlockProperties {
17160 style: BlockStyle::Flex,
17161 placement: BlockPlacement::Below(range.start),
17162 height: Some(1),
17163 render: Arc::new({
17164 let rename_editor = rename_editor.clone();
17165 move |cx: &mut BlockContext| {
17166 let mut text_style = cx.editor_style.text.clone();
17167 if let Some(highlight_style) = old_highlight_id
17168 .and_then(|h| h.style(&cx.editor_style.syntax))
17169 {
17170 text_style = text_style.highlight(highlight_style);
17171 }
17172 div()
17173 .block_mouse_except_scroll()
17174 .pl(cx.anchor_x)
17175 .child(EditorElement::new(
17176 &rename_editor,
17177 EditorStyle {
17178 background: cx.theme().system().transparent,
17179 local_player: cx.editor_style.local_player,
17180 text: text_style,
17181 scrollbar_width: cx.editor_style.scrollbar_width,
17182 syntax: cx.editor_style.syntax.clone(),
17183 status: cx.editor_style.status.clone(),
17184 inlay_hints_style: HighlightStyle {
17185 font_weight: Some(FontWeight::BOLD),
17186 ..make_inlay_hints_style(cx.app)
17187 },
17188 edit_prediction_styles: make_suggestion_styles(
17189 cx.app,
17190 ),
17191 ..EditorStyle::default()
17192 },
17193 ))
17194 .into_any_element()
17195 }
17196 }),
17197 priority: 0,
17198 }],
17199 Some(Autoscroll::fit()),
17200 cx,
17201 )[0];
17202 this.pending_rename = Some(RenameState {
17203 range,
17204 old_name,
17205 editor: rename_editor,
17206 block_id,
17207 });
17208 })?;
17209 }
17210
17211 Ok(())
17212 }))
17213 }
17214
17215 pub fn confirm_rename(
17216 &mut self,
17217 _: &ConfirmRename,
17218 window: &mut Window,
17219 cx: &mut Context<Self>,
17220 ) -> Option<Task<Result<()>>> {
17221 let rename = self.take_rename(false, window, cx)?;
17222 let workspace = self.workspace()?.downgrade();
17223 let (buffer, start) = self
17224 .buffer
17225 .read(cx)
17226 .text_anchor_for_position(rename.range.start, cx)?;
17227 let (end_buffer, _) = self
17228 .buffer
17229 .read(cx)
17230 .text_anchor_for_position(rename.range.end, cx)?;
17231 if buffer != end_buffer {
17232 return None;
17233 }
17234
17235 let old_name = rename.old_name;
17236 let new_name = rename.editor.read(cx).text(cx);
17237
17238 let rename = self.semantics_provider.as_ref()?.perform_rename(
17239 &buffer,
17240 start,
17241 new_name.clone(),
17242 cx,
17243 )?;
17244
17245 Some(cx.spawn_in(window, async move |editor, cx| {
17246 let project_transaction = rename.await?;
17247 Self::open_project_transaction(
17248 &editor,
17249 workspace,
17250 project_transaction,
17251 format!("Rename: {} → {}", old_name, new_name),
17252 cx,
17253 )
17254 .await?;
17255
17256 editor.update(cx, |editor, cx| {
17257 editor.refresh_document_highlights(cx);
17258 })?;
17259 Ok(())
17260 }))
17261 }
17262
17263 fn take_rename(
17264 &mut self,
17265 moving_cursor: bool,
17266 window: &mut Window,
17267 cx: &mut Context<Self>,
17268 ) -> Option<RenameState> {
17269 let rename = self.pending_rename.take()?;
17270 if rename.editor.focus_handle(cx).is_focused(window) {
17271 window.focus(&self.focus_handle);
17272 }
17273
17274 self.remove_blocks(
17275 [rename.block_id].into_iter().collect(),
17276 Some(Autoscroll::fit()),
17277 cx,
17278 );
17279 self.clear_highlights::<Rename>(cx);
17280 self.show_local_selections = true;
17281
17282 if moving_cursor {
17283 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17284 editor.selections.newest::<usize>(cx).head()
17285 });
17286
17287 // Update the selection to match the position of the selection inside
17288 // the rename editor.
17289 let snapshot = self.buffer.read(cx).read(cx);
17290 let rename_range = rename.range.to_offset(&snapshot);
17291 let cursor_in_editor = snapshot
17292 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17293 .min(rename_range.end);
17294 drop(snapshot);
17295
17296 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17297 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17298 });
17299 } else {
17300 self.refresh_document_highlights(cx);
17301 }
17302
17303 Some(rename)
17304 }
17305
17306 pub fn pending_rename(&self) -> Option<&RenameState> {
17307 self.pending_rename.as_ref()
17308 }
17309
17310 fn format(
17311 &mut self,
17312 _: &Format,
17313 window: &mut Window,
17314 cx: &mut Context<Self>,
17315 ) -> Option<Task<Result<()>>> {
17316 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17317
17318 let project = match &self.project {
17319 Some(project) => project.clone(),
17320 None => return None,
17321 };
17322
17323 Some(self.perform_format(
17324 project,
17325 FormatTrigger::Manual,
17326 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17327 window,
17328 cx,
17329 ))
17330 }
17331
17332 fn format_selections(
17333 &mut self,
17334 _: &FormatSelections,
17335 window: &mut Window,
17336 cx: &mut Context<Self>,
17337 ) -> Option<Task<Result<()>>> {
17338 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17339
17340 let project = match &self.project {
17341 Some(project) => project.clone(),
17342 None => return None,
17343 };
17344
17345 let ranges = self
17346 .selections
17347 .all_adjusted(cx)
17348 .into_iter()
17349 .map(|selection| selection.range())
17350 .collect_vec();
17351
17352 Some(self.perform_format(
17353 project,
17354 FormatTrigger::Manual,
17355 FormatTarget::Ranges(ranges),
17356 window,
17357 cx,
17358 ))
17359 }
17360
17361 fn perform_format(
17362 &mut self,
17363 project: Entity<Project>,
17364 trigger: FormatTrigger,
17365 target: FormatTarget,
17366 window: &mut Window,
17367 cx: &mut Context<Self>,
17368 ) -> Task<Result<()>> {
17369 let buffer = self.buffer.clone();
17370 let (buffers, target) = match target {
17371 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17372 FormatTarget::Ranges(selection_ranges) => {
17373 let multi_buffer = buffer.read(cx);
17374 let snapshot = multi_buffer.read(cx);
17375 let mut buffers = HashSet::default();
17376 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17377 BTreeMap::new();
17378 for selection_range in selection_ranges {
17379 for (buffer, buffer_range, _) in
17380 snapshot.range_to_buffer_ranges(selection_range)
17381 {
17382 let buffer_id = buffer.remote_id();
17383 let start = buffer.anchor_before(buffer_range.start);
17384 let end = buffer.anchor_after(buffer_range.end);
17385 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17386 buffer_id_to_ranges
17387 .entry(buffer_id)
17388 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17389 .or_insert_with(|| vec![start..end]);
17390 }
17391 }
17392 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17393 }
17394 };
17395
17396 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17397 let selections_prev = transaction_id_prev
17398 .and_then(|transaction_id_prev| {
17399 // default to selections as they were after the last edit, if we have them,
17400 // instead of how they are now.
17401 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17402 // will take you back to where you made the last edit, instead of staying where you scrolled
17403 self.selection_history
17404 .transaction(transaction_id_prev)
17405 .map(|t| t.0.clone())
17406 })
17407 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17408
17409 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17410 let format = project.update(cx, |project, cx| {
17411 project.format(buffers, target, true, trigger, cx)
17412 });
17413
17414 cx.spawn_in(window, async move |editor, cx| {
17415 let transaction = futures::select_biased! {
17416 transaction = format.log_err().fuse() => transaction,
17417 () = timeout => {
17418 log::warn!("timed out waiting for formatting");
17419 None
17420 }
17421 };
17422
17423 buffer
17424 .update(cx, |buffer, cx| {
17425 if let Some(transaction) = transaction
17426 && !buffer.is_singleton()
17427 {
17428 buffer.push_transaction(&transaction.0, cx);
17429 }
17430 cx.notify();
17431 })
17432 .ok();
17433
17434 if let Some(transaction_id_now) =
17435 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17436 {
17437 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17438 if has_new_transaction {
17439 _ = editor.update(cx, |editor, _| {
17440 editor
17441 .selection_history
17442 .insert_transaction(transaction_id_now, selections_prev);
17443 });
17444 }
17445 }
17446
17447 Ok(())
17448 })
17449 }
17450
17451 fn organize_imports(
17452 &mut self,
17453 _: &OrganizeImports,
17454 window: &mut Window,
17455 cx: &mut Context<Self>,
17456 ) -> Option<Task<Result<()>>> {
17457 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17458 let project = match &self.project {
17459 Some(project) => project.clone(),
17460 None => return None,
17461 };
17462 Some(self.perform_code_action_kind(
17463 project,
17464 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17465 window,
17466 cx,
17467 ))
17468 }
17469
17470 fn perform_code_action_kind(
17471 &mut self,
17472 project: Entity<Project>,
17473 kind: CodeActionKind,
17474 window: &mut Window,
17475 cx: &mut Context<Self>,
17476 ) -> Task<Result<()>> {
17477 let buffer = self.buffer.clone();
17478 let buffers = buffer.read(cx).all_buffers();
17479 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17480 let apply_action = project.update(cx, |project, cx| {
17481 project.apply_code_action_kind(buffers, kind, true, cx)
17482 });
17483 cx.spawn_in(window, async move |_, cx| {
17484 let transaction = futures::select_biased! {
17485 () = timeout => {
17486 log::warn!("timed out waiting for executing code action");
17487 None
17488 }
17489 transaction = apply_action.log_err().fuse() => transaction,
17490 };
17491 buffer
17492 .update(cx, |buffer, cx| {
17493 // check if we need this
17494 if let Some(transaction) = transaction
17495 && !buffer.is_singleton()
17496 {
17497 buffer.push_transaction(&transaction.0, cx);
17498 }
17499 cx.notify();
17500 })
17501 .ok();
17502 Ok(())
17503 })
17504 }
17505
17506 pub fn restart_language_server(
17507 &mut self,
17508 _: &RestartLanguageServer,
17509 _: &mut Window,
17510 cx: &mut Context<Self>,
17511 ) {
17512 if let Some(project) = self.project.clone() {
17513 self.buffer.update(cx, |multi_buffer, cx| {
17514 project.update(cx, |project, cx| {
17515 project.restart_language_servers_for_buffers(
17516 multi_buffer.all_buffers().into_iter().collect(),
17517 HashSet::default(),
17518 cx,
17519 );
17520 });
17521 })
17522 }
17523 }
17524
17525 pub fn stop_language_server(
17526 &mut self,
17527 _: &StopLanguageServer,
17528 _: &mut Window,
17529 cx: &mut Context<Self>,
17530 ) {
17531 if let Some(project) = self.project.clone() {
17532 self.buffer.update(cx, |multi_buffer, cx| {
17533 project.update(cx, |project, cx| {
17534 project.stop_language_servers_for_buffers(
17535 multi_buffer.all_buffers().into_iter().collect(),
17536 HashSet::default(),
17537 cx,
17538 );
17539 cx.emit(project::Event::RefreshInlayHints);
17540 });
17541 });
17542 }
17543 }
17544
17545 fn cancel_language_server_work(
17546 workspace: &mut Workspace,
17547 _: &actions::CancelLanguageServerWork,
17548 _: &mut Window,
17549 cx: &mut Context<Workspace>,
17550 ) {
17551 let project = workspace.project();
17552 let buffers = workspace
17553 .active_item(cx)
17554 .and_then(|item| item.act_as::<Editor>(cx))
17555 .map_or(HashSet::default(), |editor| {
17556 editor.read(cx).buffer.read(cx).all_buffers()
17557 });
17558 project.update(cx, |project, cx| {
17559 project.cancel_language_server_work_for_buffers(buffers, cx);
17560 });
17561 }
17562
17563 fn show_character_palette(
17564 &mut self,
17565 _: &ShowCharacterPalette,
17566 window: &mut Window,
17567 _: &mut Context<Self>,
17568 ) {
17569 window.show_character_palette();
17570 }
17571
17572 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17573 if !self.diagnostics_enabled() {
17574 return;
17575 }
17576
17577 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17578 let buffer = self.buffer.read(cx).snapshot(cx);
17579 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17580 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17581 let is_valid = buffer
17582 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17583 .any(|entry| {
17584 entry.diagnostic.is_primary
17585 && !entry.range.is_empty()
17586 && entry.range.start == primary_range_start
17587 && entry.diagnostic.message == active_diagnostics.active_message
17588 });
17589
17590 if !is_valid {
17591 self.dismiss_diagnostics(cx);
17592 }
17593 }
17594 }
17595
17596 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17597 match &self.active_diagnostics {
17598 ActiveDiagnostic::Group(group) => Some(group),
17599 _ => None,
17600 }
17601 }
17602
17603 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17604 if !self.diagnostics_enabled() {
17605 return;
17606 }
17607 self.dismiss_diagnostics(cx);
17608 self.active_diagnostics = ActiveDiagnostic::All;
17609 }
17610
17611 fn activate_diagnostics(
17612 &mut self,
17613 buffer_id: BufferId,
17614 diagnostic: DiagnosticEntryRef<'_, usize>,
17615 window: &mut Window,
17616 cx: &mut Context<Self>,
17617 ) {
17618 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17619 return;
17620 }
17621 self.dismiss_diagnostics(cx);
17622 let snapshot = self.snapshot(window, cx);
17623 let buffer = self.buffer.read(cx).snapshot(cx);
17624 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17625 return;
17626 };
17627
17628 let diagnostic_group = buffer
17629 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17630 .collect::<Vec<_>>();
17631
17632 let blocks =
17633 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17634
17635 let blocks = self.display_map.update(cx, |display_map, cx| {
17636 display_map.insert_blocks(blocks, cx).into_iter().collect()
17637 });
17638 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17639 active_range: buffer.anchor_before(diagnostic.range.start)
17640 ..buffer.anchor_after(diagnostic.range.end),
17641 active_message: diagnostic.diagnostic.message.clone(),
17642 group_id: diagnostic.diagnostic.group_id,
17643 blocks,
17644 });
17645 cx.notify();
17646 }
17647
17648 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17649 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17650 return;
17651 };
17652
17653 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17654 if let ActiveDiagnostic::Group(group) = prev {
17655 self.display_map.update(cx, |display_map, cx| {
17656 display_map.remove_blocks(group.blocks, cx);
17657 });
17658 cx.notify();
17659 }
17660 }
17661
17662 /// Disable inline diagnostics rendering for this editor.
17663 pub fn disable_inline_diagnostics(&mut self) {
17664 self.inline_diagnostics_enabled = false;
17665 self.inline_diagnostics_update = Task::ready(());
17666 self.inline_diagnostics.clear();
17667 }
17668
17669 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17670 self.diagnostics_enabled = false;
17671 self.dismiss_diagnostics(cx);
17672 self.inline_diagnostics_update = Task::ready(());
17673 self.inline_diagnostics.clear();
17674 }
17675
17676 pub fn disable_word_completions(&mut self) {
17677 self.word_completions_enabled = false;
17678 }
17679
17680 pub fn diagnostics_enabled(&self) -> bool {
17681 self.diagnostics_enabled && self.mode.is_full()
17682 }
17683
17684 pub fn inline_diagnostics_enabled(&self) -> bool {
17685 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17686 }
17687
17688 pub fn show_inline_diagnostics(&self) -> bool {
17689 self.show_inline_diagnostics
17690 }
17691
17692 pub fn toggle_inline_diagnostics(
17693 &mut self,
17694 _: &ToggleInlineDiagnostics,
17695 window: &mut Window,
17696 cx: &mut Context<Editor>,
17697 ) {
17698 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17699 self.refresh_inline_diagnostics(false, window, cx);
17700 }
17701
17702 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17703 self.diagnostics_max_severity = severity;
17704 self.display_map.update(cx, |display_map, _| {
17705 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17706 });
17707 }
17708
17709 pub fn toggle_diagnostics(
17710 &mut self,
17711 _: &ToggleDiagnostics,
17712 window: &mut Window,
17713 cx: &mut Context<Editor>,
17714 ) {
17715 if !self.diagnostics_enabled() {
17716 return;
17717 }
17718
17719 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17720 EditorSettings::get_global(cx)
17721 .diagnostics_max_severity
17722 .filter(|severity| severity != &DiagnosticSeverity::Off)
17723 .unwrap_or(DiagnosticSeverity::Hint)
17724 } else {
17725 DiagnosticSeverity::Off
17726 };
17727 self.set_max_diagnostics_severity(new_severity, cx);
17728 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17729 self.active_diagnostics = ActiveDiagnostic::None;
17730 self.inline_diagnostics_update = Task::ready(());
17731 self.inline_diagnostics.clear();
17732 } else {
17733 self.refresh_inline_diagnostics(false, window, cx);
17734 }
17735
17736 cx.notify();
17737 }
17738
17739 pub fn toggle_minimap(
17740 &mut self,
17741 _: &ToggleMinimap,
17742 window: &mut Window,
17743 cx: &mut Context<Editor>,
17744 ) {
17745 if self.supports_minimap(cx) {
17746 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17747 }
17748 }
17749
17750 fn refresh_inline_diagnostics(
17751 &mut self,
17752 debounce: bool,
17753 window: &mut Window,
17754 cx: &mut Context<Self>,
17755 ) {
17756 let max_severity = ProjectSettings::get_global(cx)
17757 .diagnostics
17758 .inline
17759 .max_severity
17760 .unwrap_or(self.diagnostics_max_severity);
17761
17762 if !self.inline_diagnostics_enabled()
17763 || !self.show_inline_diagnostics
17764 || max_severity == DiagnosticSeverity::Off
17765 {
17766 self.inline_diagnostics_update = Task::ready(());
17767 self.inline_diagnostics.clear();
17768 return;
17769 }
17770
17771 let debounce_ms = ProjectSettings::get_global(cx)
17772 .diagnostics
17773 .inline
17774 .update_debounce_ms;
17775 let debounce = if debounce && debounce_ms > 0 {
17776 Some(Duration::from_millis(debounce_ms))
17777 } else {
17778 None
17779 };
17780 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17781 if let Some(debounce) = debounce {
17782 cx.background_executor().timer(debounce).await;
17783 }
17784 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17785 editor
17786 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17787 .ok()
17788 }) else {
17789 return;
17790 };
17791
17792 let new_inline_diagnostics = cx
17793 .background_spawn(async move {
17794 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17795 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17796 let message = diagnostic_entry
17797 .diagnostic
17798 .message
17799 .split_once('\n')
17800 .map(|(line, _)| line)
17801 .map(SharedString::new)
17802 .unwrap_or_else(|| {
17803 SharedString::new(&*diagnostic_entry.diagnostic.message)
17804 });
17805 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17806 let (Ok(i) | Err(i)) = inline_diagnostics
17807 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17808 inline_diagnostics.insert(
17809 i,
17810 (
17811 start_anchor,
17812 InlineDiagnostic {
17813 message,
17814 group_id: diagnostic_entry.diagnostic.group_id,
17815 start: diagnostic_entry.range.start.to_point(&snapshot),
17816 is_primary: diagnostic_entry.diagnostic.is_primary,
17817 severity: diagnostic_entry.diagnostic.severity,
17818 },
17819 ),
17820 );
17821 }
17822 inline_diagnostics
17823 })
17824 .await;
17825
17826 editor
17827 .update(cx, |editor, cx| {
17828 editor.inline_diagnostics = new_inline_diagnostics;
17829 cx.notify();
17830 })
17831 .ok();
17832 });
17833 }
17834
17835 fn pull_diagnostics(
17836 &mut self,
17837 buffer_id: Option<BufferId>,
17838 window: &Window,
17839 cx: &mut Context<Self>,
17840 ) -> Option<()> {
17841 if !self.mode().is_full() {
17842 return None;
17843 }
17844 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17845 .diagnostics
17846 .lsp_pull_diagnostics;
17847 if !pull_diagnostics_settings.enabled {
17848 return None;
17849 }
17850 let project = self.project()?.downgrade();
17851 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17852 let mut buffers = self.buffer.read(cx).all_buffers();
17853 if let Some(buffer_id) = buffer_id {
17854 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17855 }
17856
17857 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17858 cx.background_executor().timer(debounce).await;
17859
17860 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17861 buffers
17862 .into_iter()
17863 .filter_map(|buffer| {
17864 project
17865 .update(cx, |project, cx| {
17866 project.lsp_store().update(cx, |lsp_store, cx| {
17867 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17868 })
17869 })
17870 .ok()
17871 })
17872 .collect::<FuturesUnordered<_>>()
17873 }) else {
17874 return;
17875 };
17876
17877 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17878 match pull_task {
17879 Ok(()) => {
17880 if editor
17881 .update_in(cx, |editor, window, cx| {
17882 editor.update_diagnostics_state(window, cx);
17883 })
17884 .is_err()
17885 {
17886 return;
17887 }
17888 }
17889 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17890 }
17891 }
17892 });
17893
17894 Some(())
17895 }
17896
17897 pub fn set_selections_from_remote(
17898 &mut self,
17899 selections: Vec<Selection<Anchor>>,
17900 pending_selection: Option<Selection<Anchor>>,
17901 window: &mut Window,
17902 cx: &mut Context<Self>,
17903 ) {
17904 let old_cursor_position = self.selections.newest_anchor().head();
17905 self.selections.change_with(cx, |s| {
17906 s.select_anchors(selections);
17907 if let Some(pending_selection) = pending_selection {
17908 s.set_pending(pending_selection, SelectMode::Character);
17909 } else {
17910 s.clear_pending();
17911 }
17912 });
17913 self.selections_did_change(
17914 false,
17915 &old_cursor_position,
17916 SelectionEffects::default(),
17917 window,
17918 cx,
17919 );
17920 }
17921
17922 pub fn transact(
17923 &mut self,
17924 window: &mut Window,
17925 cx: &mut Context<Self>,
17926 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17927 ) -> Option<TransactionId> {
17928 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17929 this.start_transaction_at(Instant::now(), window, cx);
17930 update(this, window, cx);
17931 this.end_transaction_at(Instant::now(), cx)
17932 })
17933 }
17934
17935 pub fn start_transaction_at(
17936 &mut self,
17937 now: Instant,
17938 window: &mut Window,
17939 cx: &mut Context<Self>,
17940 ) -> Option<TransactionId> {
17941 self.end_selection(window, cx);
17942 if let Some(tx_id) = self
17943 .buffer
17944 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17945 {
17946 self.selection_history
17947 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
17948 cx.emit(EditorEvent::TransactionBegun {
17949 transaction_id: tx_id,
17950 });
17951 Some(tx_id)
17952 } else {
17953 None
17954 }
17955 }
17956
17957 pub fn end_transaction_at(
17958 &mut self,
17959 now: Instant,
17960 cx: &mut Context<Self>,
17961 ) -> Option<TransactionId> {
17962 if let Some(transaction_id) = self
17963 .buffer
17964 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17965 {
17966 if let Some((_, end_selections)) =
17967 self.selection_history.transaction_mut(transaction_id)
17968 {
17969 *end_selections = Some(self.selections.disjoint_anchors_arc());
17970 } else {
17971 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17972 }
17973
17974 cx.emit(EditorEvent::Edited { transaction_id });
17975 Some(transaction_id)
17976 } else {
17977 None
17978 }
17979 }
17980
17981 pub fn modify_transaction_selection_history(
17982 &mut self,
17983 transaction_id: TransactionId,
17984 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17985 ) -> bool {
17986 self.selection_history
17987 .transaction_mut(transaction_id)
17988 .map(modify)
17989 .is_some()
17990 }
17991
17992 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17993 if self.selection_mark_mode {
17994 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17995 s.move_with(|_, sel| {
17996 sel.collapse_to(sel.head(), SelectionGoal::None);
17997 });
17998 })
17999 }
18000 self.selection_mark_mode = true;
18001 cx.notify();
18002 }
18003
18004 pub fn swap_selection_ends(
18005 &mut self,
18006 _: &actions::SwapSelectionEnds,
18007 window: &mut Window,
18008 cx: &mut Context<Self>,
18009 ) {
18010 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18011 s.move_with(|_, sel| {
18012 if sel.start != sel.end {
18013 sel.reversed = !sel.reversed
18014 }
18015 });
18016 });
18017 self.request_autoscroll(Autoscroll::newest(), cx);
18018 cx.notify();
18019 }
18020
18021 pub fn toggle_focus(
18022 workspace: &mut Workspace,
18023 _: &actions::ToggleFocus,
18024 window: &mut Window,
18025 cx: &mut Context<Workspace>,
18026 ) {
18027 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18028 return;
18029 };
18030 workspace.activate_item(&item, true, true, window, cx);
18031 }
18032
18033 pub fn toggle_fold(
18034 &mut self,
18035 _: &actions::ToggleFold,
18036 window: &mut Window,
18037 cx: &mut Context<Self>,
18038 ) {
18039 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18040 let selection = self.selections.newest::<Point>(cx);
18041
18042 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18043 let range = if selection.is_empty() {
18044 let point = selection.head().to_display_point(&display_map);
18045 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18046 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18047 .to_point(&display_map);
18048 start..end
18049 } else {
18050 selection.range()
18051 };
18052 if display_map.folds_in_range(range).next().is_some() {
18053 self.unfold_lines(&Default::default(), window, cx)
18054 } else {
18055 self.fold(&Default::default(), window, cx)
18056 }
18057 } else {
18058 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18059 let buffer_ids: HashSet<_> = self
18060 .selections
18061 .disjoint_anchor_ranges()
18062 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18063 .collect();
18064
18065 let should_unfold = buffer_ids
18066 .iter()
18067 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18068
18069 for buffer_id in buffer_ids {
18070 if should_unfold {
18071 self.unfold_buffer(buffer_id, cx);
18072 } else {
18073 self.fold_buffer(buffer_id, cx);
18074 }
18075 }
18076 }
18077 }
18078
18079 pub fn toggle_fold_recursive(
18080 &mut self,
18081 _: &actions::ToggleFoldRecursive,
18082 window: &mut Window,
18083 cx: &mut Context<Self>,
18084 ) {
18085 let selection = self.selections.newest::<Point>(cx);
18086
18087 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18088 let range = if selection.is_empty() {
18089 let point = selection.head().to_display_point(&display_map);
18090 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18091 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18092 .to_point(&display_map);
18093 start..end
18094 } else {
18095 selection.range()
18096 };
18097 if display_map.folds_in_range(range).next().is_some() {
18098 self.unfold_recursive(&Default::default(), window, cx)
18099 } else {
18100 self.fold_recursive(&Default::default(), window, cx)
18101 }
18102 }
18103
18104 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18105 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18106 let mut to_fold = Vec::new();
18107 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18108 let selections = self.selections.all_adjusted(cx);
18109
18110 for selection in selections {
18111 let range = selection.range().sorted();
18112 let buffer_start_row = range.start.row;
18113
18114 if range.start.row != range.end.row {
18115 let mut found = false;
18116 let mut row = range.start.row;
18117 while row <= range.end.row {
18118 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18119 {
18120 found = true;
18121 row = crease.range().end.row + 1;
18122 to_fold.push(crease);
18123 } else {
18124 row += 1
18125 }
18126 }
18127 if found {
18128 continue;
18129 }
18130 }
18131
18132 for row in (0..=range.start.row).rev() {
18133 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18134 && crease.range().end.row >= buffer_start_row
18135 {
18136 to_fold.push(crease);
18137 if row <= range.start.row {
18138 break;
18139 }
18140 }
18141 }
18142 }
18143
18144 self.fold_creases(to_fold, true, window, cx);
18145 } else {
18146 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18147 let buffer_ids = self
18148 .selections
18149 .disjoint_anchor_ranges()
18150 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18151 .collect::<HashSet<_>>();
18152 for buffer_id in buffer_ids {
18153 self.fold_buffer(buffer_id, cx);
18154 }
18155 }
18156 }
18157
18158 pub fn toggle_fold_all(
18159 &mut self,
18160 _: &actions::ToggleFoldAll,
18161 window: &mut Window,
18162 cx: &mut Context<Self>,
18163 ) {
18164 if self.buffer.read(cx).is_singleton() {
18165 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18166 let has_folds = display_map
18167 .folds_in_range(0..display_map.buffer_snapshot().len())
18168 .next()
18169 .is_some();
18170
18171 if has_folds {
18172 self.unfold_all(&actions::UnfoldAll, window, cx);
18173 } else {
18174 self.fold_all(&actions::FoldAll, window, cx);
18175 }
18176 } else {
18177 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18178 let should_unfold = buffer_ids
18179 .iter()
18180 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18181
18182 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18183 editor
18184 .update_in(cx, |editor, _, cx| {
18185 for buffer_id in buffer_ids {
18186 if should_unfold {
18187 editor.unfold_buffer(buffer_id, cx);
18188 } else {
18189 editor.fold_buffer(buffer_id, cx);
18190 }
18191 }
18192 })
18193 .ok();
18194 });
18195 }
18196 }
18197
18198 fn fold_at_level(
18199 &mut self,
18200 fold_at: &FoldAtLevel,
18201 window: &mut Window,
18202 cx: &mut Context<Self>,
18203 ) {
18204 if !self.buffer.read(cx).is_singleton() {
18205 return;
18206 }
18207
18208 let fold_at_level = fold_at.0;
18209 let snapshot = self.buffer.read(cx).snapshot(cx);
18210 let mut to_fold = Vec::new();
18211 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18212
18213 let row_ranges_to_keep: Vec<Range<u32>> = self
18214 .selections
18215 .all::<Point>(cx)
18216 .into_iter()
18217 .map(|sel| sel.start.row..sel.end.row)
18218 .collect();
18219
18220 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18221 while start_row < end_row {
18222 match self
18223 .snapshot(window, cx)
18224 .crease_for_buffer_row(MultiBufferRow(start_row))
18225 {
18226 Some(crease) => {
18227 let nested_start_row = crease.range().start.row + 1;
18228 let nested_end_row = crease.range().end.row;
18229
18230 if current_level < fold_at_level {
18231 stack.push((nested_start_row, nested_end_row, current_level + 1));
18232 } else if current_level == fold_at_level {
18233 // Fold iff there is no selection completely contained within the fold region
18234 if !row_ranges_to_keep.iter().any(|selection| {
18235 selection.end >= nested_start_row
18236 && selection.start <= nested_end_row
18237 }) {
18238 to_fold.push(crease);
18239 }
18240 }
18241
18242 start_row = nested_end_row + 1;
18243 }
18244 None => start_row += 1,
18245 }
18246 }
18247 }
18248
18249 self.fold_creases(to_fold, true, window, cx);
18250 }
18251
18252 pub fn fold_at_level_1(
18253 &mut self,
18254 _: &actions::FoldAtLevel1,
18255 window: &mut Window,
18256 cx: &mut Context<Self>,
18257 ) {
18258 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18259 }
18260
18261 pub fn fold_at_level_2(
18262 &mut self,
18263 _: &actions::FoldAtLevel2,
18264 window: &mut Window,
18265 cx: &mut Context<Self>,
18266 ) {
18267 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18268 }
18269
18270 pub fn fold_at_level_3(
18271 &mut self,
18272 _: &actions::FoldAtLevel3,
18273 window: &mut Window,
18274 cx: &mut Context<Self>,
18275 ) {
18276 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18277 }
18278
18279 pub fn fold_at_level_4(
18280 &mut self,
18281 _: &actions::FoldAtLevel4,
18282 window: &mut Window,
18283 cx: &mut Context<Self>,
18284 ) {
18285 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18286 }
18287
18288 pub fn fold_at_level_5(
18289 &mut self,
18290 _: &actions::FoldAtLevel5,
18291 window: &mut Window,
18292 cx: &mut Context<Self>,
18293 ) {
18294 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18295 }
18296
18297 pub fn fold_at_level_6(
18298 &mut self,
18299 _: &actions::FoldAtLevel6,
18300 window: &mut Window,
18301 cx: &mut Context<Self>,
18302 ) {
18303 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18304 }
18305
18306 pub fn fold_at_level_7(
18307 &mut self,
18308 _: &actions::FoldAtLevel7,
18309 window: &mut Window,
18310 cx: &mut Context<Self>,
18311 ) {
18312 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18313 }
18314
18315 pub fn fold_at_level_8(
18316 &mut self,
18317 _: &actions::FoldAtLevel8,
18318 window: &mut Window,
18319 cx: &mut Context<Self>,
18320 ) {
18321 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18322 }
18323
18324 pub fn fold_at_level_9(
18325 &mut self,
18326 _: &actions::FoldAtLevel9,
18327 window: &mut Window,
18328 cx: &mut Context<Self>,
18329 ) {
18330 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18331 }
18332
18333 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18334 if self.buffer.read(cx).is_singleton() {
18335 let mut fold_ranges = Vec::new();
18336 let snapshot = self.buffer.read(cx).snapshot(cx);
18337
18338 for row in 0..snapshot.max_row().0 {
18339 if let Some(foldable_range) = self
18340 .snapshot(window, cx)
18341 .crease_for_buffer_row(MultiBufferRow(row))
18342 {
18343 fold_ranges.push(foldable_range);
18344 }
18345 }
18346
18347 self.fold_creases(fold_ranges, true, window, cx);
18348 } else {
18349 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18350 editor
18351 .update_in(cx, |editor, _, cx| {
18352 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18353 editor.fold_buffer(buffer_id, cx);
18354 }
18355 })
18356 .ok();
18357 });
18358 }
18359 }
18360
18361 pub fn fold_function_bodies(
18362 &mut self,
18363 _: &actions::FoldFunctionBodies,
18364 window: &mut Window,
18365 cx: &mut Context<Self>,
18366 ) {
18367 let snapshot = self.buffer.read(cx).snapshot(cx);
18368
18369 let ranges = snapshot
18370 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18371 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18372 .collect::<Vec<_>>();
18373
18374 let creases = ranges
18375 .into_iter()
18376 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18377 .collect();
18378
18379 self.fold_creases(creases, true, window, cx);
18380 }
18381
18382 pub fn fold_recursive(
18383 &mut self,
18384 _: &actions::FoldRecursive,
18385 window: &mut Window,
18386 cx: &mut Context<Self>,
18387 ) {
18388 let mut to_fold = Vec::new();
18389 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18390 let selections = self.selections.all_adjusted(cx);
18391
18392 for selection in selections {
18393 let range = selection.range().sorted();
18394 let buffer_start_row = range.start.row;
18395
18396 if range.start.row != range.end.row {
18397 let mut found = false;
18398 for row in range.start.row..=range.end.row {
18399 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18400 found = true;
18401 to_fold.push(crease);
18402 }
18403 }
18404 if found {
18405 continue;
18406 }
18407 }
18408
18409 for row in (0..=range.start.row).rev() {
18410 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18411 if crease.range().end.row >= buffer_start_row {
18412 to_fold.push(crease);
18413 } else {
18414 break;
18415 }
18416 }
18417 }
18418 }
18419
18420 self.fold_creases(to_fold, true, window, cx);
18421 }
18422
18423 pub fn fold_at(
18424 &mut self,
18425 buffer_row: MultiBufferRow,
18426 window: &mut Window,
18427 cx: &mut Context<Self>,
18428 ) {
18429 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18430
18431 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18432 let autoscroll = self
18433 .selections
18434 .all::<Point>(cx)
18435 .iter()
18436 .any(|selection| crease.range().overlaps(&selection.range()));
18437
18438 self.fold_creases(vec![crease], autoscroll, window, cx);
18439 }
18440 }
18441
18442 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18443 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18444 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18445 let buffer = display_map.buffer_snapshot();
18446 let selections = self.selections.all::<Point>(cx);
18447 let ranges = selections
18448 .iter()
18449 .map(|s| {
18450 let range = s.display_range(&display_map).sorted();
18451 let mut start = range.start.to_point(&display_map);
18452 let mut end = range.end.to_point(&display_map);
18453 start.column = 0;
18454 end.column = buffer.line_len(MultiBufferRow(end.row));
18455 start..end
18456 })
18457 .collect::<Vec<_>>();
18458
18459 self.unfold_ranges(&ranges, true, true, cx);
18460 } else {
18461 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18462 let buffer_ids = self
18463 .selections
18464 .disjoint_anchor_ranges()
18465 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18466 .collect::<HashSet<_>>();
18467 for buffer_id in buffer_ids {
18468 self.unfold_buffer(buffer_id, cx);
18469 }
18470 }
18471 }
18472
18473 pub fn unfold_recursive(
18474 &mut self,
18475 _: &UnfoldRecursive,
18476 _window: &mut Window,
18477 cx: &mut Context<Self>,
18478 ) {
18479 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18480 let selections = self.selections.all::<Point>(cx);
18481 let ranges = selections
18482 .iter()
18483 .map(|s| {
18484 let mut range = s.display_range(&display_map).sorted();
18485 *range.start.column_mut() = 0;
18486 *range.end.column_mut() = display_map.line_len(range.end.row());
18487 let start = range.start.to_point(&display_map);
18488 let end = range.end.to_point(&display_map);
18489 start..end
18490 })
18491 .collect::<Vec<_>>();
18492
18493 self.unfold_ranges(&ranges, true, true, cx);
18494 }
18495
18496 pub fn unfold_at(
18497 &mut self,
18498 buffer_row: MultiBufferRow,
18499 _window: &mut Window,
18500 cx: &mut Context<Self>,
18501 ) {
18502 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18503
18504 let intersection_range = Point::new(buffer_row.0, 0)
18505 ..Point::new(
18506 buffer_row.0,
18507 display_map.buffer_snapshot().line_len(buffer_row),
18508 );
18509
18510 let autoscroll = self
18511 .selections
18512 .all::<Point>(cx)
18513 .iter()
18514 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18515
18516 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18517 }
18518
18519 pub fn unfold_all(
18520 &mut self,
18521 _: &actions::UnfoldAll,
18522 _window: &mut Window,
18523 cx: &mut Context<Self>,
18524 ) {
18525 if self.buffer.read(cx).is_singleton() {
18526 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18527 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18528 } else {
18529 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18530 editor
18531 .update(cx, |editor, cx| {
18532 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18533 editor.unfold_buffer(buffer_id, cx);
18534 }
18535 })
18536 .ok();
18537 });
18538 }
18539 }
18540
18541 pub fn fold_selected_ranges(
18542 &mut self,
18543 _: &FoldSelectedRanges,
18544 window: &mut Window,
18545 cx: &mut Context<Self>,
18546 ) {
18547 let selections = self.selections.all_adjusted(cx);
18548 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18549 let ranges = selections
18550 .into_iter()
18551 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18552 .collect::<Vec<_>>();
18553 self.fold_creases(ranges, true, window, cx);
18554 }
18555
18556 pub fn fold_ranges<T: ToOffset + Clone>(
18557 &mut self,
18558 ranges: Vec<Range<T>>,
18559 auto_scroll: bool,
18560 window: &mut Window,
18561 cx: &mut Context<Self>,
18562 ) {
18563 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18564 let ranges = ranges
18565 .into_iter()
18566 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18567 .collect::<Vec<_>>();
18568 self.fold_creases(ranges, auto_scroll, window, cx);
18569 }
18570
18571 pub fn fold_creases<T: ToOffset + Clone>(
18572 &mut self,
18573 creases: Vec<Crease<T>>,
18574 auto_scroll: bool,
18575 _window: &mut Window,
18576 cx: &mut Context<Self>,
18577 ) {
18578 if creases.is_empty() {
18579 return;
18580 }
18581
18582 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18583
18584 if auto_scroll {
18585 self.request_autoscroll(Autoscroll::fit(), cx);
18586 }
18587
18588 cx.notify();
18589
18590 self.scrollbar_marker_state.dirty = true;
18591 self.folds_did_change(cx);
18592 }
18593
18594 /// Removes any folds whose ranges intersect any of the given ranges.
18595 pub fn unfold_ranges<T: ToOffset + Clone>(
18596 &mut self,
18597 ranges: &[Range<T>],
18598 inclusive: bool,
18599 auto_scroll: bool,
18600 cx: &mut Context<Self>,
18601 ) {
18602 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18603 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18604 });
18605 self.folds_did_change(cx);
18606 }
18607
18608 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18609 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18610 return;
18611 }
18612 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18613 self.display_map.update(cx, |display_map, cx| {
18614 display_map.fold_buffers([buffer_id], cx)
18615 });
18616 cx.emit(EditorEvent::BufferFoldToggled {
18617 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18618 folded: true,
18619 });
18620 cx.notify();
18621 }
18622
18623 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18624 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18625 return;
18626 }
18627 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18628 self.display_map.update(cx, |display_map, cx| {
18629 display_map.unfold_buffers([buffer_id], cx);
18630 });
18631 cx.emit(EditorEvent::BufferFoldToggled {
18632 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18633 folded: false,
18634 });
18635 cx.notify();
18636 }
18637
18638 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18639 self.display_map.read(cx).is_buffer_folded(buffer)
18640 }
18641
18642 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18643 self.display_map.read(cx).folded_buffers()
18644 }
18645
18646 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18647 self.display_map.update(cx, |display_map, cx| {
18648 display_map.disable_header_for_buffer(buffer_id, cx);
18649 });
18650 cx.notify();
18651 }
18652
18653 /// Removes any folds with the given ranges.
18654 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18655 &mut self,
18656 ranges: &[Range<T>],
18657 type_id: TypeId,
18658 auto_scroll: bool,
18659 cx: &mut Context<Self>,
18660 ) {
18661 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18662 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18663 });
18664 self.folds_did_change(cx);
18665 }
18666
18667 fn remove_folds_with<T: ToOffset + Clone>(
18668 &mut self,
18669 ranges: &[Range<T>],
18670 auto_scroll: bool,
18671 cx: &mut Context<Self>,
18672 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18673 ) {
18674 if ranges.is_empty() {
18675 return;
18676 }
18677
18678 let mut buffers_affected = HashSet::default();
18679 let multi_buffer = self.buffer().read(cx);
18680 for range in ranges {
18681 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18682 buffers_affected.insert(buffer.read(cx).remote_id());
18683 };
18684 }
18685
18686 self.display_map.update(cx, update);
18687
18688 if auto_scroll {
18689 self.request_autoscroll(Autoscroll::fit(), cx);
18690 }
18691
18692 cx.notify();
18693 self.scrollbar_marker_state.dirty = true;
18694 self.active_indent_guides_state.dirty = true;
18695 }
18696
18697 pub fn update_renderer_widths(
18698 &mut self,
18699 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18700 cx: &mut Context<Self>,
18701 ) -> bool {
18702 self.display_map
18703 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18704 }
18705
18706 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18707 self.display_map.read(cx).fold_placeholder.clone()
18708 }
18709
18710 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18711 self.buffer.update(cx, |buffer, cx| {
18712 buffer.set_all_diff_hunks_expanded(cx);
18713 });
18714 }
18715
18716 pub fn expand_all_diff_hunks(
18717 &mut self,
18718 _: &ExpandAllDiffHunks,
18719 _window: &mut Window,
18720 cx: &mut Context<Self>,
18721 ) {
18722 self.buffer.update(cx, |buffer, cx| {
18723 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18724 });
18725 }
18726
18727 pub fn toggle_selected_diff_hunks(
18728 &mut self,
18729 _: &ToggleSelectedDiffHunks,
18730 _window: &mut Window,
18731 cx: &mut Context<Self>,
18732 ) {
18733 let ranges: Vec<_> = self
18734 .selections
18735 .disjoint_anchors()
18736 .iter()
18737 .map(|s| s.range())
18738 .collect();
18739 self.toggle_diff_hunks_in_ranges(ranges, cx);
18740 }
18741
18742 pub fn diff_hunks_in_ranges<'a>(
18743 &'a self,
18744 ranges: &'a [Range<Anchor>],
18745 buffer: &'a MultiBufferSnapshot,
18746 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18747 ranges.iter().flat_map(move |range| {
18748 let end_excerpt_id = range.end.excerpt_id;
18749 let range = range.to_point(buffer);
18750 let mut peek_end = range.end;
18751 if range.end.row < buffer.max_row().0 {
18752 peek_end = Point::new(range.end.row + 1, 0);
18753 }
18754 buffer
18755 .diff_hunks_in_range(range.start..peek_end)
18756 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18757 })
18758 }
18759
18760 pub fn has_stageable_diff_hunks_in_ranges(
18761 &self,
18762 ranges: &[Range<Anchor>],
18763 snapshot: &MultiBufferSnapshot,
18764 ) -> bool {
18765 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18766 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18767 }
18768
18769 pub fn toggle_staged_selected_diff_hunks(
18770 &mut self,
18771 _: &::git::ToggleStaged,
18772 _: &mut Window,
18773 cx: &mut Context<Self>,
18774 ) {
18775 let snapshot = self.buffer.read(cx).snapshot(cx);
18776 let ranges: Vec<_> = self
18777 .selections
18778 .disjoint_anchors()
18779 .iter()
18780 .map(|s| s.range())
18781 .collect();
18782 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18783 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18784 }
18785
18786 pub fn set_render_diff_hunk_controls(
18787 &mut self,
18788 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18789 cx: &mut Context<Self>,
18790 ) {
18791 self.render_diff_hunk_controls = render_diff_hunk_controls;
18792 cx.notify();
18793 }
18794
18795 pub fn stage_and_next(
18796 &mut self,
18797 _: &::git::StageAndNext,
18798 window: &mut Window,
18799 cx: &mut Context<Self>,
18800 ) {
18801 self.do_stage_or_unstage_and_next(true, window, cx);
18802 }
18803
18804 pub fn unstage_and_next(
18805 &mut self,
18806 _: &::git::UnstageAndNext,
18807 window: &mut Window,
18808 cx: &mut Context<Self>,
18809 ) {
18810 self.do_stage_or_unstage_and_next(false, window, cx);
18811 }
18812
18813 pub fn stage_or_unstage_diff_hunks(
18814 &mut self,
18815 stage: bool,
18816 ranges: Vec<Range<Anchor>>,
18817 cx: &mut Context<Self>,
18818 ) {
18819 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18820 cx.spawn(async move |this, cx| {
18821 task.await?;
18822 this.update(cx, |this, cx| {
18823 let snapshot = this.buffer.read(cx).snapshot(cx);
18824 let chunk_by = this
18825 .diff_hunks_in_ranges(&ranges, &snapshot)
18826 .chunk_by(|hunk| hunk.buffer_id);
18827 for (buffer_id, hunks) in &chunk_by {
18828 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18829 }
18830 })
18831 })
18832 .detach_and_log_err(cx);
18833 }
18834
18835 fn save_buffers_for_ranges_if_needed(
18836 &mut self,
18837 ranges: &[Range<Anchor>],
18838 cx: &mut Context<Editor>,
18839 ) -> Task<Result<()>> {
18840 let multibuffer = self.buffer.read(cx);
18841 let snapshot = multibuffer.read(cx);
18842 let buffer_ids: HashSet<_> = ranges
18843 .iter()
18844 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18845 .collect();
18846 drop(snapshot);
18847
18848 let mut buffers = HashSet::default();
18849 for buffer_id in buffer_ids {
18850 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18851 let buffer = buffer_entity.read(cx);
18852 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18853 {
18854 buffers.insert(buffer_entity);
18855 }
18856 }
18857 }
18858
18859 if let Some(project) = &self.project {
18860 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18861 } else {
18862 Task::ready(Ok(()))
18863 }
18864 }
18865
18866 fn do_stage_or_unstage_and_next(
18867 &mut self,
18868 stage: bool,
18869 window: &mut Window,
18870 cx: &mut Context<Self>,
18871 ) {
18872 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18873
18874 if ranges.iter().any(|range| range.start != range.end) {
18875 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18876 return;
18877 }
18878
18879 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18880 let snapshot = self.snapshot(window, cx);
18881 let position = self.selections.newest::<Point>(cx).head();
18882 let mut row = snapshot
18883 .buffer_snapshot()
18884 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
18885 .find(|hunk| hunk.row_range.start.0 > position.row)
18886 .map(|hunk| hunk.row_range.start);
18887
18888 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18889 // Outside of the project diff editor, wrap around to the beginning.
18890 if !all_diff_hunks_expanded {
18891 row = row.or_else(|| {
18892 snapshot
18893 .buffer_snapshot()
18894 .diff_hunks_in_range(Point::zero()..position)
18895 .find(|hunk| hunk.row_range.end.0 < position.row)
18896 .map(|hunk| hunk.row_range.start)
18897 });
18898 }
18899
18900 if let Some(row) = row {
18901 let destination = Point::new(row.0, 0);
18902 let autoscroll = Autoscroll::center();
18903
18904 self.unfold_ranges(&[destination..destination], false, false, cx);
18905 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18906 s.select_ranges([destination..destination]);
18907 });
18908 }
18909 }
18910
18911 fn do_stage_or_unstage(
18912 &self,
18913 stage: bool,
18914 buffer_id: BufferId,
18915 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18916 cx: &mut App,
18917 ) -> Option<()> {
18918 let project = self.project()?;
18919 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18920 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18921 let buffer_snapshot = buffer.read(cx).snapshot();
18922 let file_exists = buffer_snapshot
18923 .file()
18924 .is_some_and(|file| file.disk_state().exists());
18925 diff.update(cx, |diff, cx| {
18926 diff.stage_or_unstage_hunks(
18927 stage,
18928 &hunks
18929 .map(|hunk| buffer_diff::DiffHunk {
18930 buffer_range: hunk.buffer_range,
18931 diff_base_byte_range: hunk.diff_base_byte_range,
18932 secondary_status: hunk.secondary_status,
18933 range: Point::zero()..Point::zero(), // unused
18934 })
18935 .collect::<Vec<_>>(),
18936 &buffer_snapshot,
18937 file_exists,
18938 cx,
18939 )
18940 });
18941 None
18942 }
18943
18944 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18945 let ranges: Vec<_> = self
18946 .selections
18947 .disjoint_anchors()
18948 .iter()
18949 .map(|s| s.range())
18950 .collect();
18951 self.buffer
18952 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18953 }
18954
18955 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18956 self.buffer.update(cx, |buffer, cx| {
18957 let ranges = vec![Anchor::min()..Anchor::max()];
18958 if !buffer.all_diff_hunks_expanded()
18959 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18960 {
18961 buffer.collapse_diff_hunks(ranges, cx);
18962 true
18963 } else {
18964 false
18965 }
18966 })
18967 }
18968
18969 fn toggle_diff_hunks_in_ranges(
18970 &mut self,
18971 ranges: Vec<Range<Anchor>>,
18972 cx: &mut Context<Editor>,
18973 ) {
18974 self.buffer.update(cx, |buffer, cx| {
18975 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18976 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18977 })
18978 }
18979
18980 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18981 self.buffer.update(cx, |buffer, cx| {
18982 let snapshot = buffer.snapshot(cx);
18983 let excerpt_id = range.end.excerpt_id;
18984 let point_range = range.to_point(&snapshot);
18985 let expand = !buffer.single_hunk_is_expanded(range, cx);
18986 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18987 })
18988 }
18989
18990 pub(crate) fn apply_all_diff_hunks(
18991 &mut self,
18992 _: &ApplyAllDiffHunks,
18993 window: &mut Window,
18994 cx: &mut Context<Self>,
18995 ) {
18996 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18997
18998 let buffers = self.buffer.read(cx).all_buffers();
18999 for branch_buffer in buffers {
19000 branch_buffer.update(cx, |branch_buffer, cx| {
19001 branch_buffer.merge_into_base(Vec::new(), cx);
19002 });
19003 }
19004
19005 if let Some(project) = self.project.clone() {
19006 self.save(
19007 SaveOptions {
19008 format: true,
19009 autosave: false,
19010 },
19011 project,
19012 window,
19013 cx,
19014 )
19015 .detach_and_log_err(cx);
19016 }
19017 }
19018
19019 pub(crate) fn apply_selected_diff_hunks(
19020 &mut self,
19021 _: &ApplyDiffHunk,
19022 window: &mut Window,
19023 cx: &mut Context<Self>,
19024 ) {
19025 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19026 let snapshot = self.snapshot(window, cx);
19027 let hunks = snapshot.hunks_for_ranges(
19028 self.selections
19029 .all(cx)
19030 .into_iter()
19031 .map(|selection| selection.range()),
19032 );
19033 let mut ranges_by_buffer = HashMap::default();
19034 self.transact(window, cx, |editor, _window, cx| {
19035 for hunk in hunks {
19036 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19037 ranges_by_buffer
19038 .entry(buffer.clone())
19039 .or_insert_with(Vec::new)
19040 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19041 }
19042 }
19043
19044 for (buffer, ranges) in ranges_by_buffer {
19045 buffer.update(cx, |buffer, cx| {
19046 buffer.merge_into_base(ranges, cx);
19047 });
19048 }
19049 });
19050
19051 if let Some(project) = self.project.clone() {
19052 self.save(
19053 SaveOptions {
19054 format: true,
19055 autosave: false,
19056 },
19057 project,
19058 window,
19059 cx,
19060 )
19061 .detach_and_log_err(cx);
19062 }
19063 }
19064
19065 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19066 if hovered != self.gutter_hovered {
19067 self.gutter_hovered = hovered;
19068 cx.notify();
19069 }
19070 }
19071
19072 pub fn insert_blocks(
19073 &mut self,
19074 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19075 autoscroll: Option<Autoscroll>,
19076 cx: &mut Context<Self>,
19077 ) -> Vec<CustomBlockId> {
19078 let blocks = self
19079 .display_map
19080 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19081 if let Some(autoscroll) = autoscroll {
19082 self.request_autoscroll(autoscroll, cx);
19083 }
19084 cx.notify();
19085 blocks
19086 }
19087
19088 pub fn resize_blocks(
19089 &mut self,
19090 heights: HashMap<CustomBlockId, u32>,
19091 autoscroll: Option<Autoscroll>,
19092 cx: &mut Context<Self>,
19093 ) {
19094 self.display_map
19095 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19096 if let Some(autoscroll) = autoscroll {
19097 self.request_autoscroll(autoscroll, cx);
19098 }
19099 cx.notify();
19100 }
19101
19102 pub fn replace_blocks(
19103 &mut self,
19104 renderers: HashMap<CustomBlockId, RenderBlock>,
19105 autoscroll: Option<Autoscroll>,
19106 cx: &mut Context<Self>,
19107 ) {
19108 self.display_map
19109 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19110 if let Some(autoscroll) = autoscroll {
19111 self.request_autoscroll(autoscroll, cx);
19112 }
19113 cx.notify();
19114 }
19115
19116 pub fn remove_blocks(
19117 &mut self,
19118 block_ids: HashSet<CustomBlockId>,
19119 autoscroll: Option<Autoscroll>,
19120 cx: &mut Context<Self>,
19121 ) {
19122 self.display_map.update(cx, |display_map, cx| {
19123 display_map.remove_blocks(block_ids, cx)
19124 });
19125 if let Some(autoscroll) = autoscroll {
19126 self.request_autoscroll(autoscroll, cx);
19127 }
19128 cx.notify();
19129 }
19130
19131 pub fn row_for_block(
19132 &self,
19133 block_id: CustomBlockId,
19134 cx: &mut Context<Self>,
19135 ) -> Option<DisplayRow> {
19136 self.display_map
19137 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19138 }
19139
19140 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19141 self.focused_block = Some(focused_block);
19142 }
19143
19144 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19145 self.focused_block.take()
19146 }
19147
19148 pub fn insert_creases(
19149 &mut self,
19150 creases: impl IntoIterator<Item = Crease<Anchor>>,
19151 cx: &mut Context<Self>,
19152 ) -> Vec<CreaseId> {
19153 self.display_map
19154 .update(cx, |map, cx| map.insert_creases(creases, cx))
19155 }
19156
19157 pub fn remove_creases(
19158 &mut self,
19159 ids: impl IntoIterator<Item = CreaseId>,
19160 cx: &mut Context<Self>,
19161 ) -> Vec<(CreaseId, Range<Anchor>)> {
19162 self.display_map
19163 .update(cx, |map, cx| map.remove_creases(ids, cx))
19164 }
19165
19166 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19167 self.display_map
19168 .update(cx, |map, cx| map.snapshot(cx))
19169 .longest_row()
19170 }
19171
19172 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19173 self.display_map
19174 .update(cx, |map, cx| map.snapshot(cx))
19175 .max_point()
19176 }
19177
19178 pub fn text(&self, cx: &App) -> String {
19179 self.buffer.read(cx).read(cx).text()
19180 }
19181
19182 pub fn is_empty(&self, cx: &App) -> bool {
19183 self.buffer.read(cx).read(cx).is_empty()
19184 }
19185
19186 pub fn text_option(&self, cx: &App) -> Option<String> {
19187 let text = self.text(cx);
19188 let text = text.trim();
19189
19190 if text.is_empty() {
19191 return None;
19192 }
19193
19194 Some(text.to_string())
19195 }
19196
19197 pub fn set_text(
19198 &mut self,
19199 text: impl Into<Arc<str>>,
19200 window: &mut Window,
19201 cx: &mut Context<Self>,
19202 ) {
19203 self.transact(window, cx, |this, _, cx| {
19204 this.buffer
19205 .read(cx)
19206 .as_singleton()
19207 .expect("you can only call set_text on editors for singleton buffers")
19208 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19209 });
19210 }
19211
19212 pub fn display_text(&self, cx: &mut App) -> String {
19213 self.display_map
19214 .update(cx, |map, cx| map.snapshot(cx))
19215 .text()
19216 }
19217
19218 fn create_minimap(
19219 &self,
19220 minimap_settings: MinimapSettings,
19221 window: &mut Window,
19222 cx: &mut Context<Self>,
19223 ) -> Option<Entity<Self>> {
19224 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19225 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19226 }
19227
19228 fn initialize_new_minimap(
19229 &self,
19230 minimap_settings: MinimapSettings,
19231 window: &mut Window,
19232 cx: &mut Context<Self>,
19233 ) -> Entity<Self> {
19234 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19235
19236 let mut minimap = Editor::new_internal(
19237 EditorMode::Minimap {
19238 parent: cx.weak_entity(),
19239 },
19240 self.buffer.clone(),
19241 None,
19242 Some(self.display_map.clone()),
19243 window,
19244 cx,
19245 );
19246 minimap.scroll_manager.clone_state(&self.scroll_manager);
19247 minimap.set_text_style_refinement(TextStyleRefinement {
19248 font_size: Some(MINIMAP_FONT_SIZE),
19249 font_weight: Some(MINIMAP_FONT_WEIGHT),
19250 ..Default::default()
19251 });
19252 minimap.update_minimap_configuration(minimap_settings, cx);
19253 cx.new(|_| minimap)
19254 }
19255
19256 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19257 let current_line_highlight = minimap_settings
19258 .current_line_highlight
19259 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19260 self.set_current_line_highlight(Some(current_line_highlight));
19261 }
19262
19263 pub fn minimap(&self) -> Option<&Entity<Self>> {
19264 self.minimap
19265 .as_ref()
19266 .filter(|_| self.minimap_visibility.visible())
19267 }
19268
19269 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19270 let mut wrap_guides = smallvec![];
19271
19272 if self.show_wrap_guides == Some(false) {
19273 return wrap_guides;
19274 }
19275
19276 let settings = self.buffer.read(cx).language_settings(cx);
19277 if settings.show_wrap_guides {
19278 match self.soft_wrap_mode(cx) {
19279 SoftWrap::Column(soft_wrap) => {
19280 wrap_guides.push((soft_wrap as usize, true));
19281 }
19282 SoftWrap::Bounded(soft_wrap) => {
19283 wrap_guides.push((soft_wrap as usize, true));
19284 }
19285 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19286 }
19287 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19288 }
19289
19290 wrap_guides
19291 }
19292
19293 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19294 let settings = self.buffer.read(cx).language_settings(cx);
19295 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19296 match mode {
19297 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19298 SoftWrap::None
19299 }
19300 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19301 language_settings::SoftWrap::PreferredLineLength => {
19302 SoftWrap::Column(settings.preferred_line_length)
19303 }
19304 language_settings::SoftWrap::Bounded => {
19305 SoftWrap::Bounded(settings.preferred_line_length)
19306 }
19307 }
19308 }
19309
19310 pub fn set_soft_wrap_mode(
19311 &mut self,
19312 mode: language_settings::SoftWrap,
19313
19314 cx: &mut Context<Self>,
19315 ) {
19316 self.soft_wrap_mode_override = Some(mode);
19317 cx.notify();
19318 }
19319
19320 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19321 self.hard_wrap = hard_wrap;
19322 cx.notify();
19323 }
19324
19325 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19326 self.text_style_refinement = Some(style);
19327 }
19328
19329 /// called by the Element so we know what style we were most recently rendered with.
19330 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19331 // We intentionally do not inform the display map about the minimap style
19332 // so that wrapping is not recalculated and stays consistent for the editor
19333 // and its linked minimap.
19334 if !self.mode.is_minimap() {
19335 let font = style.text.font();
19336 let font_size = style.text.font_size.to_pixels(window.rem_size());
19337 let display_map = self
19338 .placeholder_display_map
19339 .as_ref()
19340 .filter(|_| self.is_empty(cx))
19341 .unwrap_or(&self.display_map);
19342
19343 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19344 }
19345 self.style = Some(style);
19346 }
19347
19348 pub fn style(&self) -> Option<&EditorStyle> {
19349 self.style.as_ref()
19350 }
19351
19352 // Called by the element. This method is not designed to be called outside of the editor
19353 // element's layout code because it does not notify when rewrapping is computed synchronously.
19354 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19355 if self.is_empty(cx) {
19356 self.placeholder_display_map
19357 .as_ref()
19358 .map_or(false, |display_map| {
19359 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19360 })
19361 } else {
19362 self.display_map
19363 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19364 }
19365 }
19366
19367 pub fn set_soft_wrap(&mut self) {
19368 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19369 }
19370
19371 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19372 if self.soft_wrap_mode_override.is_some() {
19373 self.soft_wrap_mode_override.take();
19374 } else {
19375 let soft_wrap = match self.soft_wrap_mode(cx) {
19376 SoftWrap::GitDiff => return,
19377 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19378 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19379 language_settings::SoftWrap::None
19380 }
19381 };
19382 self.soft_wrap_mode_override = Some(soft_wrap);
19383 }
19384 cx.notify();
19385 }
19386
19387 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19388 let Some(workspace) = self.workspace() else {
19389 return;
19390 };
19391 let fs = workspace.read(cx).app_state().fs.clone();
19392 let current_show = TabBarSettings::get_global(cx).show;
19393 update_settings_file(fs, cx, move |setting, _| {
19394 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19395 });
19396 }
19397
19398 pub fn toggle_indent_guides(
19399 &mut self,
19400 _: &ToggleIndentGuides,
19401 _: &mut Window,
19402 cx: &mut Context<Self>,
19403 ) {
19404 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19405 self.buffer
19406 .read(cx)
19407 .language_settings(cx)
19408 .indent_guides
19409 .enabled
19410 });
19411 self.show_indent_guides = Some(!currently_enabled);
19412 cx.notify();
19413 }
19414
19415 fn should_show_indent_guides(&self) -> Option<bool> {
19416 self.show_indent_guides
19417 }
19418
19419 pub fn toggle_line_numbers(
19420 &mut self,
19421 _: &ToggleLineNumbers,
19422 _: &mut Window,
19423 cx: &mut Context<Self>,
19424 ) {
19425 let mut editor_settings = EditorSettings::get_global(cx).clone();
19426 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19427 EditorSettings::override_global(editor_settings, cx);
19428 }
19429
19430 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19431 if let Some(show_line_numbers) = self.show_line_numbers {
19432 return show_line_numbers;
19433 }
19434 EditorSettings::get_global(cx).gutter.line_numbers
19435 }
19436
19437 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19438 self.use_relative_line_numbers
19439 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19440 }
19441
19442 pub fn toggle_relative_line_numbers(
19443 &mut self,
19444 _: &ToggleRelativeLineNumbers,
19445 _: &mut Window,
19446 cx: &mut Context<Self>,
19447 ) {
19448 let is_relative = self.should_use_relative_line_numbers(cx);
19449 self.set_relative_line_number(Some(!is_relative), cx)
19450 }
19451
19452 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19453 self.use_relative_line_numbers = is_relative;
19454 cx.notify();
19455 }
19456
19457 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19458 self.show_gutter = show_gutter;
19459 cx.notify();
19460 }
19461
19462 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19463 self.show_scrollbars = ScrollbarAxes {
19464 horizontal: show,
19465 vertical: show,
19466 };
19467 cx.notify();
19468 }
19469
19470 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19471 self.show_scrollbars.vertical = show;
19472 cx.notify();
19473 }
19474
19475 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19476 self.show_scrollbars.horizontal = show;
19477 cx.notify();
19478 }
19479
19480 pub fn set_minimap_visibility(
19481 &mut self,
19482 minimap_visibility: MinimapVisibility,
19483 window: &mut Window,
19484 cx: &mut Context<Self>,
19485 ) {
19486 if self.minimap_visibility != minimap_visibility {
19487 if minimap_visibility.visible() && self.minimap.is_none() {
19488 let minimap_settings = EditorSettings::get_global(cx).minimap;
19489 self.minimap =
19490 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19491 }
19492 self.minimap_visibility = minimap_visibility;
19493 cx.notify();
19494 }
19495 }
19496
19497 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19498 self.set_show_scrollbars(false, cx);
19499 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19500 }
19501
19502 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19503 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19504 }
19505
19506 /// Normally the text in full mode and auto height editors is padded on the
19507 /// left side by roughly half a character width for improved hit testing.
19508 ///
19509 /// Use this method to disable this for cases where this is not wanted (e.g.
19510 /// if you want to align the editor text with some other text above or below)
19511 /// or if you want to add this padding to single-line editors.
19512 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19513 self.offset_content = offset_content;
19514 cx.notify();
19515 }
19516
19517 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19518 self.show_line_numbers = Some(show_line_numbers);
19519 cx.notify();
19520 }
19521
19522 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19523 self.disable_expand_excerpt_buttons = true;
19524 cx.notify();
19525 }
19526
19527 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19528 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19529 cx.notify();
19530 }
19531
19532 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19533 self.show_code_actions = Some(show_code_actions);
19534 cx.notify();
19535 }
19536
19537 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19538 self.show_runnables = Some(show_runnables);
19539 cx.notify();
19540 }
19541
19542 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19543 self.show_breakpoints = Some(show_breakpoints);
19544 cx.notify();
19545 }
19546
19547 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19548 if self.display_map.read(cx).masked != masked {
19549 self.display_map.update(cx, |map, _| map.masked = masked);
19550 }
19551 cx.notify()
19552 }
19553
19554 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19555 self.show_wrap_guides = Some(show_wrap_guides);
19556 cx.notify();
19557 }
19558
19559 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19560 self.show_indent_guides = Some(show_indent_guides);
19561 cx.notify();
19562 }
19563
19564 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19565 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19566 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19567 && let Some(dir) = file.abs_path(cx).parent()
19568 {
19569 return Some(dir.to_owned());
19570 }
19571 }
19572
19573 None
19574 }
19575
19576 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19577 self.active_excerpt(cx)?
19578 .1
19579 .read(cx)
19580 .file()
19581 .and_then(|f| f.as_local())
19582 }
19583
19584 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19585 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19586 let buffer = buffer.read(cx);
19587 if let Some(project_path) = buffer.project_path(cx) {
19588 let project = self.project()?.read(cx);
19589 project.absolute_path(&project_path, cx)
19590 } else {
19591 buffer
19592 .file()
19593 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19594 }
19595 })
19596 }
19597
19598 pub fn reveal_in_finder(
19599 &mut self,
19600 _: &RevealInFileManager,
19601 _window: &mut Window,
19602 cx: &mut Context<Self>,
19603 ) {
19604 if let Some(target) = self.target_file(cx) {
19605 cx.reveal_path(&target.abs_path(cx));
19606 }
19607 }
19608
19609 pub fn copy_path(
19610 &mut self,
19611 _: &zed_actions::workspace::CopyPath,
19612 _window: &mut Window,
19613 cx: &mut Context<Self>,
19614 ) {
19615 if let Some(path) = self.target_file_abs_path(cx)
19616 && let Some(path) = path.to_str()
19617 {
19618 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19619 } else {
19620 cx.propagate();
19621 }
19622 }
19623
19624 pub fn copy_relative_path(
19625 &mut self,
19626 _: &zed_actions::workspace::CopyRelativePath,
19627 _window: &mut Window,
19628 cx: &mut Context<Self>,
19629 ) {
19630 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19631 let project = self.project()?.read(cx);
19632 let path = buffer.read(cx).file()?.path();
19633 let path = path.display(project.path_style(cx));
19634 Some(path)
19635 }) {
19636 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19637 } else {
19638 cx.propagate();
19639 }
19640 }
19641
19642 /// Returns the project path for the editor's buffer, if any buffer is
19643 /// opened in the editor.
19644 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19645 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19646 buffer.read(cx).project_path(cx)
19647 } else {
19648 None
19649 }
19650 }
19651
19652 // Returns true if the editor handled a go-to-line request
19653 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19654 maybe!({
19655 let breakpoint_store = self.breakpoint_store.as_ref()?;
19656
19657 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19658 else {
19659 self.clear_row_highlights::<ActiveDebugLine>();
19660 return None;
19661 };
19662
19663 let position = active_stack_frame.position;
19664 let buffer_id = position.buffer_id?;
19665 let snapshot = self
19666 .project
19667 .as_ref()?
19668 .read(cx)
19669 .buffer_for_id(buffer_id, cx)?
19670 .read(cx)
19671 .snapshot();
19672
19673 let mut handled = false;
19674 for (id, ExcerptRange { context, .. }) in
19675 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19676 {
19677 if context.start.cmp(&position, &snapshot).is_ge()
19678 || context.end.cmp(&position, &snapshot).is_lt()
19679 {
19680 continue;
19681 }
19682 let snapshot = self.buffer.read(cx).snapshot(cx);
19683 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19684
19685 handled = true;
19686 self.clear_row_highlights::<ActiveDebugLine>();
19687
19688 self.go_to_line::<ActiveDebugLine>(
19689 multibuffer_anchor,
19690 Some(cx.theme().colors().editor_debugger_active_line_background),
19691 window,
19692 cx,
19693 );
19694
19695 cx.notify();
19696 }
19697
19698 handled.then_some(())
19699 })
19700 .is_some()
19701 }
19702
19703 pub fn copy_file_name_without_extension(
19704 &mut self,
19705 _: &CopyFileNameWithoutExtension,
19706 _: &mut Window,
19707 cx: &mut Context<Self>,
19708 ) {
19709 if let Some(file) = self.target_file(cx)
19710 && let Some(file_stem) = file.path().file_stem()
19711 {
19712 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19713 }
19714 }
19715
19716 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19717 if let Some(file) = self.target_file(cx)
19718 && let Some(name) = file.path().file_name()
19719 {
19720 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19721 }
19722 }
19723
19724 pub fn toggle_git_blame(
19725 &mut self,
19726 _: &::git::Blame,
19727 window: &mut Window,
19728 cx: &mut Context<Self>,
19729 ) {
19730 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19731
19732 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19733 self.start_git_blame(true, window, cx);
19734 }
19735
19736 cx.notify();
19737 }
19738
19739 pub fn toggle_git_blame_inline(
19740 &mut self,
19741 _: &ToggleGitBlameInline,
19742 window: &mut Window,
19743 cx: &mut Context<Self>,
19744 ) {
19745 self.toggle_git_blame_inline_internal(true, window, cx);
19746 cx.notify();
19747 }
19748
19749 pub fn open_git_blame_commit(
19750 &mut self,
19751 _: &OpenGitBlameCommit,
19752 window: &mut Window,
19753 cx: &mut Context<Self>,
19754 ) {
19755 self.open_git_blame_commit_internal(window, cx);
19756 }
19757
19758 fn open_git_blame_commit_internal(
19759 &mut self,
19760 window: &mut Window,
19761 cx: &mut Context<Self>,
19762 ) -> Option<()> {
19763 let blame = self.blame.as_ref()?;
19764 let snapshot = self.snapshot(window, cx);
19765 let cursor = self.selections.newest::<Point>(cx).head();
19766 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
19767 let (_, blame_entry) = blame
19768 .update(cx, |blame, cx| {
19769 blame
19770 .blame_for_rows(
19771 &[RowInfo {
19772 buffer_id: Some(buffer.remote_id()),
19773 buffer_row: Some(point.row),
19774 ..Default::default()
19775 }],
19776 cx,
19777 )
19778 .next()
19779 })
19780 .flatten()?;
19781 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19782 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19783 let workspace = self.workspace()?.downgrade();
19784 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19785 None
19786 }
19787
19788 pub fn git_blame_inline_enabled(&self) -> bool {
19789 self.git_blame_inline_enabled
19790 }
19791
19792 pub fn toggle_selection_menu(
19793 &mut self,
19794 _: &ToggleSelectionMenu,
19795 _: &mut Window,
19796 cx: &mut Context<Self>,
19797 ) {
19798 self.show_selection_menu = self
19799 .show_selection_menu
19800 .map(|show_selections_menu| !show_selections_menu)
19801 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19802
19803 cx.notify();
19804 }
19805
19806 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19807 self.show_selection_menu
19808 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19809 }
19810
19811 fn start_git_blame(
19812 &mut self,
19813 user_triggered: bool,
19814 window: &mut Window,
19815 cx: &mut Context<Self>,
19816 ) {
19817 if let Some(project) = self.project() {
19818 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19819 && buffer.read(cx).file().is_none()
19820 {
19821 return;
19822 }
19823
19824 let focused = self.focus_handle(cx).contains_focused(window, cx);
19825
19826 let project = project.clone();
19827 let blame = cx
19828 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19829 self.blame_subscription =
19830 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19831 self.blame = Some(blame);
19832 }
19833 }
19834
19835 fn toggle_git_blame_inline_internal(
19836 &mut self,
19837 user_triggered: bool,
19838 window: &mut Window,
19839 cx: &mut Context<Self>,
19840 ) {
19841 if self.git_blame_inline_enabled {
19842 self.git_blame_inline_enabled = false;
19843 self.show_git_blame_inline = false;
19844 self.show_git_blame_inline_delay_task.take();
19845 } else {
19846 self.git_blame_inline_enabled = true;
19847 self.start_git_blame_inline(user_triggered, window, cx);
19848 }
19849
19850 cx.notify();
19851 }
19852
19853 fn start_git_blame_inline(
19854 &mut self,
19855 user_triggered: bool,
19856 window: &mut Window,
19857 cx: &mut Context<Self>,
19858 ) {
19859 self.start_git_blame(user_triggered, window, cx);
19860
19861 if ProjectSettings::get_global(cx)
19862 .git
19863 .inline_blame_delay()
19864 .is_some()
19865 {
19866 self.start_inline_blame_timer(window, cx);
19867 } else {
19868 self.show_git_blame_inline = true
19869 }
19870 }
19871
19872 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19873 self.blame.as_ref()
19874 }
19875
19876 pub fn show_git_blame_gutter(&self) -> bool {
19877 self.show_git_blame_gutter
19878 }
19879
19880 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19881 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19882 }
19883
19884 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19885 self.show_git_blame_inline
19886 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19887 && !self.newest_selection_head_on_empty_line(cx)
19888 && self.has_blame_entries(cx)
19889 }
19890
19891 fn has_blame_entries(&self, cx: &App) -> bool {
19892 self.blame()
19893 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19894 }
19895
19896 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19897 let cursor_anchor = self.selections.newest_anchor().head();
19898
19899 let snapshot = self.buffer.read(cx).snapshot(cx);
19900 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19901
19902 snapshot.line_len(buffer_row) == 0
19903 }
19904
19905 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19906 let buffer_and_selection = maybe!({
19907 let selection = self.selections.newest::<Point>(cx);
19908 let selection_range = selection.range();
19909
19910 let multi_buffer = self.buffer().read(cx);
19911 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19912 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19913
19914 let (buffer, range, _) = if selection.reversed {
19915 buffer_ranges.first()
19916 } else {
19917 buffer_ranges.last()
19918 }?;
19919
19920 let selection = text::ToPoint::to_point(&range.start, buffer).row
19921 ..text::ToPoint::to_point(&range.end, buffer).row;
19922 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19923 });
19924
19925 let Some((buffer, selection)) = buffer_and_selection else {
19926 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19927 };
19928
19929 let Some(project) = self.project() else {
19930 return Task::ready(Err(anyhow!("editor does not have project")));
19931 };
19932
19933 project.update(cx, |project, cx| {
19934 project.get_permalink_to_line(&buffer, selection, cx)
19935 })
19936 }
19937
19938 pub fn copy_permalink_to_line(
19939 &mut self,
19940 _: &CopyPermalinkToLine,
19941 window: &mut Window,
19942 cx: &mut Context<Self>,
19943 ) {
19944 let permalink_task = self.get_permalink_to_line(cx);
19945 let workspace = self.workspace();
19946
19947 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19948 Ok(permalink) => {
19949 cx.update(|_, cx| {
19950 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19951 })
19952 .ok();
19953 }
19954 Err(err) => {
19955 let message = format!("Failed to copy permalink: {err}");
19956
19957 anyhow::Result::<()>::Err(err).log_err();
19958
19959 if let Some(workspace) = workspace {
19960 workspace
19961 .update_in(cx, |workspace, _, cx| {
19962 struct CopyPermalinkToLine;
19963
19964 workspace.show_toast(
19965 Toast::new(
19966 NotificationId::unique::<CopyPermalinkToLine>(),
19967 message,
19968 ),
19969 cx,
19970 )
19971 })
19972 .ok();
19973 }
19974 }
19975 })
19976 .detach();
19977 }
19978
19979 pub fn copy_file_location(
19980 &mut self,
19981 _: &CopyFileLocation,
19982 _: &mut Window,
19983 cx: &mut Context<Self>,
19984 ) {
19985 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19986 if let Some(file) = self.target_file(cx) {
19987 let path = file.path().display(file.path_style(cx));
19988 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19989 }
19990 }
19991
19992 pub fn open_permalink_to_line(
19993 &mut self,
19994 _: &OpenPermalinkToLine,
19995 window: &mut Window,
19996 cx: &mut Context<Self>,
19997 ) {
19998 let permalink_task = self.get_permalink_to_line(cx);
19999 let workspace = self.workspace();
20000
20001 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20002 Ok(permalink) => {
20003 cx.update(|_, cx| {
20004 cx.open_url(permalink.as_ref());
20005 })
20006 .ok();
20007 }
20008 Err(err) => {
20009 let message = format!("Failed to open permalink: {err}");
20010
20011 anyhow::Result::<()>::Err(err).log_err();
20012
20013 if let Some(workspace) = workspace {
20014 workspace
20015 .update(cx, |workspace, cx| {
20016 struct OpenPermalinkToLine;
20017
20018 workspace.show_toast(
20019 Toast::new(
20020 NotificationId::unique::<OpenPermalinkToLine>(),
20021 message,
20022 ),
20023 cx,
20024 )
20025 })
20026 .ok();
20027 }
20028 }
20029 })
20030 .detach();
20031 }
20032
20033 pub fn insert_uuid_v4(
20034 &mut self,
20035 _: &InsertUuidV4,
20036 window: &mut Window,
20037 cx: &mut Context<Self>,
20038 ) {
20039 self.insert_uuid(UuidVersion::V4, window, cx);
20040 }
20041
20042 pub fn insert_uuid_v7(
20043 &mut self,
20044 _: &InsertUuidV7,
20045 window: &mut Window,
20046 cx: &mut Context<Self>,
20047 ) {
20048 self.insert_uuid(UuidVersion::V7, window, cx);
20049 }
20050
20051 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20052 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20053 self.transact(window, cx, |this, window, cx| {
20054 let edits = this
20055 .selections
20056 .all::<Point>(cx)
20057 .into_iter()
20058 .map(|selection| {
20059 let uuid = match version {
20060 UuidVersion::V4 => uuid::Uuid::new_v4(),
20061 UuidVersion::V7 => uuid::Uuid::now_v7(),
20062 };
20063
20064 (selection.range(), uuid.to_string())
20065 });
20066 this.edit(edits, cx);
20067 this.refresh_edit_prediction(true, false, window, cx);
20068 });
20069 }
20070
20071 pub fn open_selections_in_multibuffer(
20072 &mut self,
20073 _: &OpenSelectionsInMultibuffer,
20074 window: &mut Window,
20075 cx: &mut Context<Self>,
20076 ) {
20077 let multibuffer = self.buffer.read(cx);
20078
20079 let Some(buffer) = multibuffer.as_singleton() else {
20080 return;
20081 };
20082
20083 let Some(workspace) = self.workspace() else {
20084 return;
20085 };
20086
20087 let title = multibuffer.title(cx).to_string();
20088
20089 let locations = self
20090 .selections
20091 .all_anchors(cx)
20092 .iter()
20093 .map(|selection| {
20094 (
20095 buffer.clone(),
20096 (selection.start.text_anchor..selection.end.text_anchor)
20097 .to_point(buffer.read(cx)),
20098 )
20099 })
20100 .into_group_map();
20101
20102 cx.spawn_in(window, async move |_, cx| {
20103 workspace.update_in(cx, |workspace, window, cx| {
20104 Self::open_locations_in_multibuffer(
20105 workspace,
20106 locations,
20107 format!("Selections for '{title}'"),
20108 false,
20109 MultibufferSelectionMode::All,
20110 window,
20111 cx,
20112 );
20113 })
20114 })
20115 .detach();
20116 }
20117
20118 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20119 /// last highlight added will be used.
20120 ///
20121 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20122 pub fn highlight_rows<T: 'static>(
20123 &mut self,
20124 range: Range<Anchor>,
20125 color: Hsla,
20126 options: RowHighlightOptions,
20127 cx: &mut Context<Self>,
20128 ) {
20129 let snapshot = self.buffer().read(cx).snapshot(cx);
20130 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20131 let ix = row_highlights.binary_search_by(|highlight| {
20132 Ordering::Equal
20133 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20134 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20135 });
20136
20137 if let Err(mut ix) = ix {
20138 let index = post_inc(&mut self.highlight_order);
20139
20140 // If this range intersects with the preceding highlight, then merge it with
20141 // the preceding highlight. Otherwise insert a new highlight.
20142 let mut merged = false;
20143 if ix > 0 {
20144 let prev_highlight = &mut row_highlights[ix - 1];
20145 if prev_highlight
20146 .range
20147 .end
20148 .cmp(&range.start, &snapshot)
20149 .is_ge()
20150 {
20151 ix -= 1;
20152 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20153 prev_highlight.range.end = range.end;
20154 }
20155 merged = true;
20156 prev_highlight.index = index;
20157 prev_highlight.color = color;
20158 prev_highlight.options = options;
20159 }
20160 }
20161
20162 if !merged {
20163 row_highlights.insert(
20164 ix,
20165 RowHighlight {
20166 range,
20167 index,
20168 color,
20169 options,
20170 type_id: TypeId::of::<T>(),
20171 },
20172 );
20173 }
20174
20175 // If any of the following highlights intersect with this one, merge them.
20176 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20177 let highlight = &row_highlights[ix];
20178 if next_highlight
20179 .range
20180 .start
20181 .cmp(&highlight.range.end, &snapshot)
20182 .is_le()
20183 {
20184 if next_highlight
20185 .range
20186 .end
20187 .cmp(&highlight.range.end, &snapshot)
20188 .is_gt()
20189 {
20190 row_highlights[ix].range.end = next_highlight.range.end;
20191 }
20192 row_highlights.remove(ix + 1);
20193 } else {
20194 break;
20195 }
20196 }
20197 }
20198 }
20199
20200 /// Remove any highlighted row ranges of the given type that intersect the
20201 /// given ranges.
20202 pub fn remove_highlighted_rows<T: 'static>(
20203 &mut self,
20204 ranges_to_remove: Vec<Range<Anchor>>,
20205 cx: &mut Context<Self>,
20206 ) {
20207 let snapshot = self.buffer().read(cx).snapshot(cx);
20208 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20209 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20210 row_highlights.retain(|highlight| {
20211 while let Some(range_to_remove) = ranges_to_remove.peek() {
20212 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20213 Ordering::Less | Ordering::Equal => {
20214 ranges_to_remove.next();
20215 }
20216 Ordering::Greater => {
20217 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20218 Ordering::Less | Ordering::Equal => {
20219 return false;
20220 }
20221 Ordering::Greater => break,
20222 }
20223 }
20224 }
20225 }
20226
20227 true
20228 })
20229 }
20230
20231 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20232 pub fn clear_row_highlights<T: 'static>(&mut self) {
20233 self.highlighted_rows.remove(&TypeId::of::<T>());
20234 }
20235
20236 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20237 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20238 self.highlighted_rows
20239 .get(&TypeId::of::<T>())
20240 .map_or(&[] as &[_], |vec| vec.as_slice())
20241 .iter()
20242 .map(|highlight| (highlight.range.clone(), highlight.color))
20243 }
20244
20245 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20246 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20247 /// Allows to ignore certain kinds of highlights.
20248 pub fn highlighted_display_rows(
20249 &self,
20250 window: &mut Window,
20251 cx: &mut App,
20252 ) -> BTreeMap<DisplayRow, LineHighlight> {
20253 let snapshot = self.snapshot(window, cx);
20254 let mut used_highlight_orders = HashMap::default();
20255 self.highlighted_rows
20256 .iter()
20257 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20258 .fold(
20259 BTreeMap::<DisplayRow, LineHighlight>::new(),
20260 |mut unique_rows, highlight| {
20261 let start = highlight.range.start.to_display_point(&snapshot);
20262 let end = highlight.range.end.to_display_point(&snapshot);
20263 let start_row = start.row().0;
20264 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20265 && end.column() == 0
20266 {
20267 end.row().0.saturating_sub(1)
20268 } else {
20269 end.row().0
20270 };
20271 for row in start_row..=end_row {
20272 let used_index =
20273 used_highlight_orders.entry(row).or_insert(highlight.index);
20274 if highlight.index >= *used_index {
20275 *used_index = highlight.index;
20276 unique_rows.insert(
20277 DisplayRow(row),
20278 LineHighlight {
20279 include_gutter: highlight.options.include_gutter,
20280 border: None,
20281 background: highlight.color.into(),
20282 type_id: Some(highlight.type_id),
20283 },
20284 );
20285 }
20286 }
20287 unique_rows
20288 },
20289 )
20290 }
20291
20292 pub fn highlighted_display_row_for_autoscroll(
20293 &self,
20294 snapshot: &DisplaySnapshot,
20295 ) -> Option<DisplayRow> {
20296 self.highlighted_rows
20297 .values()
20298 .flat_map(|highlighted_rows| highlighted_rows.iter())
20299 .filter_map(|highlight| {
20300 if highlight.options.autoscroll {
20301 Some(highlight.range.start.to_display_point(snapshot).row())
20302 } else {
20303 None
20304 }
20305 })
20306 .min()
20307 }
20308
20309 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20310 self.highlight_background::<SearchWithinRange>(
20311 ranges,
20312 |colors| colors.colors().editor_document_highlight_read_background,
20313 cx,
20314 )
20315 }
20316
20317 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20318 self.breadcrumb_header = Some(new_header);
20319 }
20320
20321 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20322 self.clear_background_highlights::<SearchWithinRange>(cx);
20323 }
20324
20325 pub fn highlight_background<T: 'static>(
20326 &mut self,
20327 ranges: &[Range<Anchor>],
20328 color_fetcher: fn(&Theme) -> Hsla,
20329 cx: &mut Context<Self>,
20330 ) {
20331 self.background_highlights.insert(
20332 HighlightKey::Type(TypeId::of::<T>()),
20333 (color_fetcher, Arc::from(ranges)),
20334 );
20335 self.scrollbar_marker_state.dirty = true;
20336 cx.notify();
20337 }
20338
20339 pub fn highlight_background_key<T: 'static>(
20340 &mut self,
20341 key: usize,
20342 ranges: &[Range<Anchor>],
20343 color_fetcher: fn(&Theme) -> Hsla,
20344 cx: &mut Context<Self>,
20345 ) {
20346 self.background_highlights.insert(
20347 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20348 (color_fetcher, Arc::from(ranges)),
20349 );
20350 self.scrollbar_marker_state.dirty = true;
20351 cx.notify();
20352 }
20353
20354 pub fn clear_background_highlights<T: 'static>(
20355 &mut self,
20356 cx: &mut Context<Self>,
20357 ) -> Option<BackgroundHighlight> {
20358 let text_highlights = self
20359 .background_highlights
20360 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20361 if !text_highlights.1.is_empty() {
20362 self.scrollbar_marker_state.dirty = true;
20363 cx.notify();
20364 }
20365 Some(text_highlights)
20366 }
20367
20368 pub fn highlight_gutter<T: 'static>(
20369 &mut self,
20370 ranges: impl Into<Vec<Range<Anchor>>>,
20371 color_fetcher: fn(&App) -> Hsla,
20372 cx: &mut Context<Self>,
20373 ) {
20374 self.gutter_highlights
20375 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20376 cx.notify();
20377 }
20378
20379 pub fn clear_gutter_highlights<T: 'static>(
20380 &mut self,
20381 cx: &mut Context<Self>,
20382 ) -> Option<GutterHighlight> {
20383 cx.notify();
20384 self.gutter_highlights.remove(&TypeId::of::<T>())
20385 }
20386
20387 pub fn insert_gutter_highlight<T: 'static>(
20388 &mut self,
20389 range: Range<Anchor>,
20390 color_fetcher: fn(&App) -> Hsla,
20391 cx: &mut Context<Self>,
20392 ) {
20393 let snapshot = self.buffer().read(cx).snapshot(cx);
20394 let mut highlights = self
20395 .gutter_highlights
20396 .remove(&TypeId::of::<T>())
20397 .map(|(_, highlights)| highlights)
20398 .unwrap_or_default();
20399 let ix = highlights.binary_search_by(|highlight| {
20400 Ordering::Equal
20401 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20402 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20403 });
20404 if let Err(ix) = ix {
20405 highlights.insert(ix, range);
20406 }
20407 self.gutter_highlights
20408 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20409 }
20410
20411 pub fn remove_gutter_highlights<T: 'static>(
20412 &mut self,
20413 ranges_to_remove: Vec<Range<Anchor>>,
20414 cx: &mut Context<Self>,
20415 ) {
20416 let snapshot = self.buffer().read(cx).snapshot(cx);
20417 let Some((color_fetcher, mut gutter_highlights)) =
20418 self.gutter_highlights.remove(&TypeId::of::<T>())
20419 else {
20420 return;
20421 };
20422 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20423 gutter_highlights.retain(|highlight| {
20424 while let Some(range_to_remove) = ranges_to_remove.peek() {
20425 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20426 Ordering::Less | Ordering::Equal => {
20427 ranges_to_remove.next();
20428 }
20429 Ordering::Greater => {
20430 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20431 Ordering::Less | Ordering::Equal => {
20432 return false;
20433 }
20434 Ordering::Greater => break,
20435 }
20436 }
20437 }
20438 }
20439
20440 true
20441 });
20442 self.gutter_highlights
20443 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20444 }
20445
20446 #[cfg(feature = "test-support")]
20447 pub fn all_text_highlights(
20448 &self,
20449 window: &mut Window,
20450 cx: &mut Context<Self>,
20451 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20452 let snapshot = self.snapshot(window, cx);
20453 self.display_map.update(cx, |display_map, _| {
20454 display_map
20455 .all_text_highlights()
20456 .map(|highlight| {
20457 let (style, ranges) = highlight.as_ref();
20458 (
20459 *style,
20460 ranges
20461 .iter()
20462 .map(|range| range.clone().to_display_points(&snapshot))
20463 .collect(),
20464 )
20465 })
20466 .collect()
20467 })
20468 }
20469
20470 #[cfg(feature = "test-support")]
20471 pub fn all_text_background_highlights(
20472 &self,
20473 window: &mut Window,
20474 cx: &mut Context<Self>,
20475 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20476 let snapshot = self.snapshot(window, cx);
20477 let buffer = &snapshot.buffer_snapshot();
20478 let start = buffer.anchor_before(0);
20479 let end = buffer.anchor_after(buffer.len());
20480 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20481 }
20482
20483 #[cfg(any(test, feature = "test-support"))]
20484 pub fn sorted_background_highlights_in_range(
20485 &self,
20486 search_range: Range<Anchor>,
20487 display_snapshot: &DisplaySnapshot,
20488 theme: &Theme,
20489 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20490 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20491 res.sort_by(|a, b| {
20492 a.0.start
20493 .cmp(&b.0.start)
20494 .then_with(|| a.0.end.cmp(&b.0.end))
20495 .then_with(|| a.1.cmp(&b.1))
20496 });
20497 res
20498 }
20499
20500 #[cfg(feature = "test-support")]
20501 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20502 let snapshot = self.buffer().read(cx).snapshot(cx);
20503
20504 let highlights = self
20505 .background_highlights
20506 .get(&HighlightKey::Type(TypeId::of::<
20507 items::BufferSearchHighlights,
20508 >()));
20509
20510 if let Some((_color, ranges)) = highlights {
20511 ranges
20512 .iter()
20513 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20514 .collect_vec()
20515 } else {
20516 vec![]
20517 }
20518 }
20519
20520 fn document_highlights_for_position<'a>(
20521 &'a self,
20522 position: Anchor,
20523 buffer: &'a MultiBufferSnapshot,
20524 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20525 let read_highlights = self
20526 .background_highlights
20527 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20528 .map(|h| &h.1);
20529 let write_highlights = self
20530 .background_highlights
20531 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20532 .map(|h| &h.1);
20533 let left_position = position.bias_left(buffer);
20534 let right_position = position.bias_right(buffer);
20535 read_highlights
20536 .into_iter()
20537 .chain(write_highlights)
20538 .flat_map(move |ranges| {
20539 let start_ix = match ranges.binary_search_by(|probe| {
20540 let cmp = probe.end.cmp(&left_position, buffer);
20541 if cmp.is_ge() {
20542 Ordering::Greater
20543 } else {
20544 Ordering::Less
20545 }
20546 }) {
20547 Ok(i) | Err(i) => i,
20548 };
20549
20550 ranges[start_ix..]
20551 .iter()
20552 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20553 })
20554 }
20555
20556 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20557 self.background_highlights
20558 .get(&HighlightKey::Type(TypeId::of::<T>()))
20559 .is_some_and(|(_, highlights)| !highlights.is_empty())
20560 }
20561
20562 /// Returns all background highlights for a given range.
20563 ///
20564 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20565 pub fn background_highlights_in_range(
20566 &self,
20567 search_range: Range<Anchor>,
20568 display_snapshot: &DisplaySnapshot,
20569 theme: &Theme,
20570 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20571 let mut results = Vec::new();
20572 for (color_fetcher, ranges) in self.background_highlights.values() {
20573 let color = color_fetcher(theme);
20574 let start_ix = match ranges.binary_search_by(|probe| {
20575 let cmp = probe
20576 .end
20577 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20578 if cmp.is_gt() {
20579 Ordering::Greater
20580 } else {
20581 Ordering::Less
20582 }
20583 }) {
20584 Ok(i) | Err(i) => i,
20585 };
20586 for range in &ranges[start_ix..] {
20587 if range
20588 .start
20589 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20590 .is_ge()
20591 {
20592 break;
20593 }
20594
20595 let start = range.start.to_display_point(display_snapshot);
20596 let end = range.end.to_display_point(display_snapshot);
20597 results.push((start..end, color))
20598 }
20599 }
20600 results
20601 }
20602
20603 pub fn gutter_highlights_in_range(
20604 &self,
20605 search_range: Range<Anchor>,
20606 display_snapshot: &DisplaySnapshot,
20607 cx: &App,
20608 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20609 let mut results = Vec::new();
20610 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20611 let color = color_fetcher(cx);
20612 let start_ix = match ranges.binary_search_by(|probe| {
20613 let cmp = probe
20614 .end
20615 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20616 if cmp.is_gt() {
20617 Ordering::Greater
20618 } else {
20619 Ordering::Less
20620 }
20621 }) {
20622 Ok(i) | Err(i) => i,
20623 };
20624 for range in &ranges[start_ix..] {
20625 if range
20626 .start
20627 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20628 .is_ge()
20629 {
20630 break;
20631 }
20632
20633 let start = range.start.to_display_point(display_snapshot);
20634 let end = range.end.to_display_point(display_snapshot);
20635 results.push((start..end, color))
20636 }
20637 }
20638 results
20639 }
20640
20641 /// Get the text ranges corresponding to the redaction query
20642 pub fn redacted_ranges(
20643 &self,
20644 search_range: Range<Anchor>,
20645 display_snapshot: &DisplaySnapshot,
20646 cx: &App,
20647 ) -> Vec<Range<DisplayPoint>> {
20648 display_snapshot
20649 .buffer_snapshot()
20650 .redacted_ranges(search_range, |file| {
20651 if let Some(file) = file {
20652 file.is_private()
20653 && EditorSettings::get(
20654 Some(SettingsLocation {
20655 worktree_id: file.worktree_id(cx),
20656 path: file.path().as_ref(),
20657 }),
20658 cx,
20659 )
20660 .redact_private_values
20661 } else {
20662 false
20663 }
20664 })
20665 .map(|range| {
20666 range.start.to_display_point(display_snapshot)
20667 ..range.end.to_display_point(display_snapshot)
20668 })
20669 .collect()
20670 }
20671
20672 pub fn highlight_text_key<T: 'static>(
20673 &mut self,
20674 key: usize,
20675 ranges: Vec<Range<Anchor>>,
20676 style: HighlightStyle,
20677 cx: &mut Context<Self>,
20678 ) {
20679 self.display_map.update(cx, |map, _| {
20680 map.highlight_text(
20681 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20682 ranges,
20683 style,
20684 );
20685 });
20686 cx.notify();
20687 }
20688
20689 pub fn highlight_text<T: 'static>(
20690 &mut self,
20691 ranges: Vec<Range<Anchor>>,
20692 style: HighlightStyle,
20693 cx: &mut Context<Self>,
20694 ) {
20695 self.display_map.update(cx, |map, _| {
20696 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20697 });
20698 cx.notify();
20699 }
20700
20701 pub(crate) fn highlight_inlays<T: 'static>(
20702 &mut self,
20703 highlights: Vec<InlayHighlight>,
20704 style: HighlightStyle,
20705 cx: &mut Context<Self>,
20706 ) {
20707 self.display_map.update(cx, |map, _| {
20708 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20709 });
20710 cx.notify();
20711 }
20712
20713 pub fn text_highlights<'a, T: 'static>(
20714 &'a self,
20715 cx: &'a App,
20716 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20717 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20718 }
20719
20720 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20721 let cleared = self
20722 .display_map
20723 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20724 if cleared {
20725 cx.notify();
20726 }
20727 }
20728
20729 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20730 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20731 && self.focus_handle.is_focused(window)
20732 }
20733
20734 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20735 self.show_cursor_when_unfocused = is_enabled;
20736 cx.notify();
20737 }
20738
20739 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20740 cx.notify();
20741 }
20742
20743 fn on_debug_session_event(
20744 &mut self,
20745 _session: Entity<Session>,
20746 event: &SessionEvent,
20747 cx: &mut Context<Self>,
20748 ) {
20749 if let SessionEvent::InvalidateInlineValue = event {
20750 self.refresh_inline_values(cx);
20751 }
20752 }
20753
20754 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20755 let Some(project) = self.project.clone() else {
20756 return;
20757 };
20758
20759 if !self.inline_value_cache.enabled {
20760 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20761 self.splice_inlays(&inlays, Vec::new(), cx);
20762 return;
20763 }
20764
20765 let current_execution_position = self
20766 .highlighted_rows
20767 .get(&TypeId::of::<ActiveDebugLine>())
20768 .and_then(|lines| lines.last().map(|line| line.range.end));
20769
20770 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20771 let inline_values = editor
20772 .update(cx, |editor, cx| {
20773 let Some(current_execution_position) = current_execution_position else {
20774 return Some(Task::ready(Ok(Vec::new())));
20775 };
20776
20777 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20778 let snapshot = buffer.snapshot(cx);
20779
20780 let excerpt = snapshot.excerpt_containing(
20781 current_execution_position..current_execution_position,
20782 )?;
20783
20784 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20785 })?;
20786
20787 let range =
20788 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20789
20790 project.inline_values(buffer, range, cx)
20791 })
20792 .ok()
20793 .flatten()?
20794 .await
20795 .context("refreshing debugger inlays")
20796 .log_err()?;
20797
20798 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20799
20800 for (buffer_id, inline_value) in inline_values
20801 .into_iter()
20802 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20803 {
20804 buffer_inline_values
20805 .entry(buffer_id)
20806 .or_default()
20807 .push(inline_value);
20808 }
20809
20810 editor
20811 .update(cx, |editor, cx| {
20812 let snapshot = editor.buffer.read(cx).snapshot(cx);
20813 let mut new_inlays = Vec::default();
20814
20815 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20816 let buffer_id = buffer_snapshot.remote_id();
20817 buffer_inline_values
20818 .get(&buffer_id)
20819 .into_iter()
20820 .flatten()
20821 .for_each(|hint| {
20822 let inlay = Inlay::debugger(
20823 post_inc(&mut editor.next_inlay_id),
20824 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20825 hint.text(),
20826 );
20827 if !inlay.text().chars().contains(&'\n') {
20828 new_inlays.push(inlay);
20829 }
20830 });
20831 }
20832
20833 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20834 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20835
20836 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20837 })
20838 .ok()?;
20839 Some(())
20840 });
20841 }
20842
20843 fn on_buffer_event(
20844 &mut self,
20845 multibuffer: &Entity<MultiBuffer>,
20846 event: &multi_buffer::Event,
20847 window: &mut Window,
20848 cx: &mut Context<Self>,
20849 ) {
20850 match event {
20851 multi_buffer::Event::Edited {
20852 singleton_buffer_edited,
20853 edited_buffer,
20854 } => {
20855 self.scrollbar_marker_state.dirty = true;
20856 self.active_indent_guides_state.dirty = true;
20857 self.refresh_active_diagnostics(cx);
20858 self.refresh_code_actions(window, cx);
20859 self.refresh_selected_text_highlights(true, window, cx);
20860 self.refresh_single_line_folds(window, cx);
20861 refresh_matching_bracket_highlights(self, cx);
20862 if self.has_active_edit_prediction() {
20863 self.update_visible_edit_prediction(window, cx);
20864 }
20865 if let Some(project) = self.project.as_ref()
20866 && let Some(edited_buffer) = edited_buffer
20867 {
20868 project.update(cx, |project, cx| {
20869 self.registered_buffers
20870 .entry(edited_buffer.read(cx).remote_id())
20871 .or_insert_with(|| {
20872 project.register_buffer_with_language_servers(edited_buffer, cx)
20873 });
20874 });
20875 }
20876 cx.emit(EditorEvent::BufferEdited);
20877 cx.emit(SearchEvent::MatchesInvalidated);
20878
20879 if let Some(buffer) = edited_buffer {
20880 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20881 }
20882
20883 if *singleton_buffer_edited {
20884 if let Some(buffer) = edited_buffer
20885 && buffer.read(cx).file().is_none()
20886 {
20887 cx.emit(EditorEvent::TitleChanged);
20888 }
20889 if let Some(project) = &self.project {
20890 #[allow(clippy::mutable_key_type)]
20891 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20892 multibuffer
20893 .all_buffers()
20894 .into_iter()
20895 .filter_map(|buffer| {
20896 buffer.update(cx, |buffer, cx| {
20897 let language = buffer.language()?;
20898 let should_discard = project.update(cx, |project, cx| {
20899 project.is_local()
20900 && !project.has_language_servers_for(buffer, cx)
20901 });
20902 should_discard.not().then_some(language.clone())
20903 })
20904 })
20905 .collect::<HashSet<_>>()
20906 });
20907 if !languages_affected.is_empty() {
20908 self.refresh_inlay_hints(
20909 InlayHintRefreshReason::BufferEdited(languages_affected),
20910 cx,
20911 );
20912 }
20913 }
20914 }
20915
20916 let Some(project) = &self.project else { return };
20917 let (telemetry, is_via_ssh) = {
20918 let project = project.read(cx);
20919 let telemetry = project.client().telemetry().clone();
20920 let is_via_ssh = project.is_via_remote_server();
20921 (telemetry, is_via_ssh)
20922 };
20923 refresh_linked_ranges(self, window, cx);
20924 telemetry.log_edit_event("editor", is_via_ssh);
20925 }
20926 multi_buffer::Event::ExcerptsAdded {
20927 buffer,
20928 predecessor,
20929 excerpts,
20930 } => {
20931 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20932 let buffer_id = buffer.read(cx).remote_id();
20933 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20934 && let Some(project) = &self.project
20935 {
20936 update_uncommitted_diff_for_buffer(
20937 cx.entity(),
20938 project,
20939 [buffer.clone()],
20940 self.buffer.clone(),
20941 cx,
20942 )
20943 .detach();
20944 }
20945 if self.active_diagnostics != ActiveDiagnostic::All {
20946 self.update_lsp_data(false, Some(buffer_id), window, cx);
20947 }
20948 cx.emit(EditorEvent::ExcerptsAdded {
20949 buffer: buffer.clone(),
20950 predecessor: *predecessor,
20951 excerpts: excerpts.clone(),
20952 });
20953 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20954 }
20955 multi_buffer::Event::ExcerptsRemoved {
20956 ids,
20957 removed_buffer_ids,
20958 } => {
20959 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20960 let buffer = self.buffer.read(cx);
20961 self.registered_buffers
20962 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20963 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20964 cx.emit(EditorEvent::ExcerptsRemoved {
20965 ids: ids.clone(),
20966 removed_buffer_ids: removed_buffer_ids.clone(),
20967 });
20968 }
20969 multi_buffer::Event::ExcerptsEdited {
20970 excerpt_ids,
20971 buffer_ids,
20972 } => {
20973 self.display_map.update(cx, |map, cx| {
20974 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20975 });
20976 cx.emit(EditorEvent::ExcerptsEdited {
20977 ids: excerpt_ids.clone(),
20978 });
20979 }
20980 multi_buffer::Event::ExcerptsExpanded { ids } => {
20981 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20982 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20983 }
20984 multi_buffer::Event::Reparsed(buffer_id) => {
20985 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20986 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20987
20988 cx.emit(EditorEvent::Reparsed(*buffer_id));
20989 }
20990 multi_buffer::Event::DiffHunksToggled => {
20991 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20992 }
20993 multi_buffer::Event::LanguageChanged(buffer_id) => {
20994 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20995 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20996 cx.emit(EditorEvent::Reparsed(*buffer_id));
20997 cx.notify();
20998 }
20999 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21000 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21001 multi_buffer::Event::FileHandleChanged
21002 | multi_buffer::Event::Reloaded
21003 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21004 multi_buffer::Event::DiagnosticsUpdated => {
21005 self.update_diagnostics_state(window, cx);
21006 }
21007 _ => {}
21008 };
21009 }
21010
21011 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21012 if !self.diagnostics_enabled() {
21013 return;
21014 }
21015 self.refresh_active_diagnostics(cx);
21016 self.refresh_inline_diagnostics(true, window, cx);
21017 self.scrollbar_marker_state.dirty = true;
21018 cx.notify();
21019 }
21020
21021 pub fn start_temporary_diff_override(&mut self) {
21022 self.load_diff_task.take();
21023 self.temporary_diff_override = true;
21024 }
21025
21026 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21027 self.temporary_diff_override = false;
21028 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21029 self.buffer.update(cx, |buffer, cx| {
21030 buffer.set_all_diff_hunks_collapsed(cx);
21031 });
21032
21033 if let Some(project) = self.project.clone() {
21034 self.load_diff_task = Some(
21035 update_uncommitted_diff_for_buffer(
21036 cx.entity(),
21037 &project,
21038 self.buffer.read(cx).all_buffers(),
21039 self.buffer.clone(),
21040 cx,
21041 )
21042 .shared(),
21043 );
21044 }
21045 }
21046
21047 fn on_display_map_changed(
21048 &mut self,
21049 _: Entity<DisplayMap>,
21050 _: &mut Window,
21051 cx: &mut Context<Self>,
21052 ) {
21053 cx.notify();
21054 }
21055
21056 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21057 if self.diagnostics_enabled() {
21058 let new_severity = EditorSettings::get_global(cx)
21059 .diagnostics_max_severity
21060 .unwrap_or(DiagnosticSeverity::Hint);
21061 self.set_max_diagnostics_severity(new_severity, cx);
21062 }
21063 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21064 self.update_edit_prediction_settings(cx);
21065 self.refresh_edit_prediction(true, false, window, cx);
21066 self.refresh_inline_values(cx);
21067 self.refresh_inlay_hints(
21068 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21069 self.selections.newest_anchor().head(),
21070 &self.buffer.read(cx).snapshot(cx),
21071 cx,
21072 )),
21073 cx,
21074 );
21075
21076 let old_cursor_shape = self.cursor_shape;
21077 let old_show_breadcrumbs = self.show_breadcrumbs;
21078
21079 {
21080 let editor_settings = EditorSettings::get_global(cx);
21081 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21082 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21083 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21084 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21085 }
21086
21087 if old_cursor_shape != self.cursor_shape {
21088 cx.emit(EditorEvent::CursorShapeChanged);
21089 }
21090
21091 if old_show_breadcrumbs != self.show_breadcrumbs {
21092 cx.emit(EditorEvent::BreadcrumbsChanged);
21093 }
21094
21095 let project_settings = ProjectSettings::get_global(cx);
21096 self.serialize_dirty_buffers =
21097 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
21098
21099 if self.mode.is_full() {
21100 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21101 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21102 if self.show_inline_diagnostics != show_inline_diagnostics {
21103 self.show_inline_diagnostics = show_inline_diagnostics;
21104 self.refresh_inline_diagnostics(false, window, cx);
21105 }
21106
21107 if self.git_blame_inline_enabled != inline_blame_enabled {
21108 self.toggle_git_blame_inline_internal(false, window, cx);
21109 }
21110
21111 let minimap_settings = EditorSettings::get_global(cx).minimap;
21112 if self.minimap_visibility != MinimapVisibility::Disabled {
21113 if self.minimap_visibility.settings_visibility()
21114 != minimap_settings.minimap_enabled()
21115 {
21116 self.set_minimap_visibility(
21117 MinimapVisibility::for_mode(self.mode(), cx),
21118 window,
21119 cx,
21120 );
21121 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21122 minimap_entity.update(cx, |minimap_editor, cx| {
21123 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21124 })
21125 }
21126 }
21127 }
21128
21129 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21130 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21131 }) {
21132 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
21133 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21134 }
21135 self.refresh_colors(false, None, window, cx);
21136 }
21137
21138 cx.notify();
21139 }
21140
21141 pub fn set_searchable(&mut self, searchable: bool) {
21142 self.searchable = searchable;
21143 }
21144
21145 pub fn searchable(&self) -> bool {
21146 self.searchable
21147 }
21148
21149 fn open_proposed_changes_editor(
21150 &mut self,
21151 _: &OpenProposedChangesEditor,
21152 window: &mut Window,
21153 cx: &mut Context<Self>,
21154 ) {
21155 let Some(workspace) = self.workspace() else {
21156 cx.propagate();
21157 return;
21158 };
21159
21160 let selections = self.selections.all::<usize>(cx);
21161 let multi_buffer = self.buffer.read(cx);
21162 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
21163 let mut new_selections_by_buffer = HashMap::default();
21164 for selection in selections {
21165 for (buffer, range, _) in
21166 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
21167 {
21168 let mut range = range.to_point(buffer);
21169 range.start.column = 0;
21170 range.end.column = buffer.line_len(range.end.row);
21171 new_selections_by_buffer
21172 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
21173 .or_insert(Vec::new())
21174 .push(range)
21175 }
21176 }
21177
21178 let proposed_changes_buffers = new_selections_by_buffer
21179 .into_iter()
21180 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
21181 .collect::<Vec<_>>();
21182 let proposed_changes_editor = cx.new(|cx| {
21183 ProposedChangesEditor::new(
21184 "Proposed changes",
21185 proposed_changes_buffers,
21186 self.project.clone(),
21187 window,
21188 cx,
21189 )
21190 });
21191
21192 window.defer(cx, move |window, cx| {
21193 workspace.update(cx, |workspace, cx| {
21194 workspace.active_pane().update(cx, |pane, cx| {
21195 pane.add_item(
21196 Box::new(proposed_changes_editor),
21197 true,
21198 true,
21199 None,
21200 window,
21201 cx,
21202 );
21203 });
21204 });
21205 });
21206 }
21207
21208 pub fn open_excerpts_in_split(
21209 &mut self,
21210 _: &OpenExcerptsSplit,
21211 window: &mut Window,
21212 cx: &mut Context<Self>,
21213 ) {
21214 self.open_excerpts_common(None, true, window, cx)
21215 }
21216
21217 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21218 self.open_excerpts_common(None, false, window, cx)
21219 }
21220
21221 fn open_excerpts_common(
21222 &mut self,
21223 jump_data: Option<JumpData>,
21224 split: bool,
21225 window: &mut Window,
21226 cx: &mut Context<Self>,
21227 ) {
21228 let Some(workspace) = self.workspace() else {
21229 cx.propagate();
21230 return;
21231 };
21232
21233 if self.buffer.read(cx).is_singleton() {
21234 cx.propagate();
21235 return;
21236 }
21237
21238 let mut new_selections_by_buffer = HashMap::default();
21239 match &jump_data {
21240 Some(JumpData::MultiBufferPoint {
21241 excerpt_id,
21242 position,
21243 anchor,
21244 line_offset_from_top,
21245 }) => {
21246 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21247 if let Some(buffer) = multi_buffer_snapshot
21248 .buffer_id_for_excerpt(*excerpt_id)
21249 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21250 {
21251 let buffer_snapshot = buffer.read(cx).snapshot();
21252 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21253 language::ToPoint::to_point(anchor, &buffer_snapshot)
21254 } else {
21255 buffer_snapshot.clip_point(*position, Bias::Left)
21256 };
21257 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21258 new_selections_by_buffer.insert(
21259 buffer,
21260 (
21261 vec![jump_to_offset..jump_to_offset],
21262 Some(*line_offset_from_top),
21263 ),
21264 );
21265 }
21266 }
21267 Some(JumpData::MultiBufferRow {
21268 row,
21269 line_offset_from_top,
21270 }) => {
21271 let point = MultiBufferPoint::new(row.0, 0);
21272 if let Some((buffer, buffer_point, _)) =
21273 self.buffer.read(cx).point_to_buffer_point(point, cx)
21274 {
21275 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21276 new_selections_by_buffer
21277 .entry(buffer)
21278 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21279 .0
21280 .push(buffer_offset..buffer_offset)
21281 }
21282 }
21283 None => {
21284 let selections = self.selections.all::<usize>(cx);
21285 let multi_buffer = self.buffer.read(cx);
21286 for selection in selections {
21287 for (snapshot, range, _, anchor) in multi_buffer
21288 .snapshot(cx)
21289 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21290 {
21291 if let Some(anchor) = anchor {
21292 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21293 else {
21294 continue;
21295 };
21296 let offset = text::ToOffset::to_offset(
21297 &anchor.text_anchor,
21298 &buffer_handle.read(cx).snapshot(),
21299 );
21300 let range = offset..offset;
21301 new_selections_by_buffer
21302 .entry(buffer_handle)
21303 .or_insert((Vec::new(), None))
21304 .0
21305 .push(range)
21306 } else {
21307 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21308 else {
21309 continue;
21310 };
21311 new_selections_by_buffer
21312 .entry(buffer_handle)
21313 .or_insert((Vec::new(), None))
21314 .0
21315 .push(range)
21316 }
21317 }
21318 }
21319 }
21320 }
21321
21322 new_selections_by_buffer
21323 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21324
21325 if new_selections_by_buffer.is_empty() {
21326 return;
21327 }
21328
21329 // We defer the pane interaction because we ourselves are a workspace item
21330 // and activating a new item causes the pane to call a method on us reentrantly,
21331 // which panics if we're on the stack.
21332 window.defer(cx, move |window, cx| {
21333 workspace.update(cx, |workspace, cx| {
21334 let pane = if split {
21335 workspace.adjacent_pane(window, cx)
21336 } else {
21337 workspace.active_pane().clone()
21338 };
21339
21340 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21341 let editor = buffer
21342 .read(cx)
21343 .file()
21344 .is_none()
21345 .then(|| {
21346 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21347 // so `workspace.open_project_item` will never find them, always opening a new editor.
21348 // Instead, we try to activate the existing editor in the pane first.
21349 let (editor, pane_item_index) =
21350 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21351 let editor = item.downcast::<Editor>()?;
21352 let singleton_buffer =
21353 editor.read(cx).buffer().read(cx).as_singleton()?;
21354 if singleton_buffer == buffer {
21355 Some((editor, i))
21356 } else {
21357 None
21358 }
21359 })?;
21360 pane.update(cx, |pane, cx| {
21361 pane.activate_item(pane_item_index, true, true, window, cx)
21362 });
21363 Some(editor)
21364 })
21365 .flatten()
21366 .unwrap_or_else(|| {
21367 workspace.open_project_item::<Self>(
21368 pane.clone(),
21369 buffer,
21370 true,
21371 true,
21372 window,
21373 cx,
21374 )
21375 });
21376
21377 editor.update(cx, |editor, cx| {
21378 let autoscroll = match scroll_offset {
21379 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21380 None => Autoscroll::newest(),
21381 };
21382 let nav_history = editor.nav_history.take();
21383 editor.change_selections(
21384 SelectionEffects::scroll(autoscroll),
21385 window,
21386 cx,
21387 |s| {
21388 s.select_ranges(ranges);
21389 },
21390 );
21391 editor.nav_history = nav_history;
21392 });
21393 }
21394 })
21395 });
21396 }
21397
21398 // For now, don't allow opening excerpts in buffers that aren't backed by
21399 // regular project files.
21400 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21401 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21402 }
21403
21404 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21405 let snapshot = self.buffer.read(cx).read(cx);
21406 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21407 Some(
21408 ranges
21409 .iter()
21410 .map(move |range| {
21411 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21412 })
21413 .collect(),
21414 )
21415 }
21416
21417 fn selection_replacement_ranges(
21418 &self,
21419 range: Range<OffsetUtf16>,
21420 cx: &mut App,
21421 ) -> Vec<Range<OffsetUtf16>> {
21422 let selections = self.selections.all::<OffsetUtf16>(cx);
21423 let newest_selection = selections
21424 .iter()
21425 .max_by_key(|selection| selection.id)
21426 .unwrap();
21427 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21428 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21429 let snapshot = self.buffer.read(cx).read(cx);
21430 selections
21431 .into_iter()
21432 .map(|mut selection| {
21433 selection.start.0 =
21434 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21435 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21436 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21437 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21438 })
21439 .collect()
21440 }
21441
21442 fn report_editor_event(
21443 &self,
21444 reported_event: ReportEditorEvent,
21445 file_extension: Option<String>,
21446 cx: &App,
21447 ) {
21448 if cfg!(any(test, feature = "test-support")) {
21449 return;
21450 }
21451
21452 let Some(project) = &self.project else { return };
21453
21454 // If None, we are in a file without an extension
21455 let file = self
21456 .buffer
21457 .read(cx)
21458 .as_singleton()
21459 .and_then(|b| b.read(cx).file());
21460 let file_extension = file_extension.or(file
21461 .as_ref()
21462 .and_then(|file| Path::new(file.file_name(cx)).extension())
21463 .and_then(|e| e.to_str())
21464 .map(|a| a.to_string()));
21465
21466 let vim_mode = vim_enabled(cx);
21467
21468 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21469 let copilot_enabled = edit_predictions_provider
21470 == language::language_settings::EditPredictionProvider::Copilot;
21471 let copilot_enabled_for_language = self
21472 .buffer
21473 .read(cx)
21474 .language_settings(cx)
21475 .show_edit_predictions;
21476
21477 let project = project.read(cx);
21478 let event_type = reported_event.event_type();
21479
21480 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21481 telemetry::event!(
21482 event_type,
21483 type = if auto_saved {"autosave"} else {"manual"},
21484 file_extension,
21485 vim_mode,
21486 copilot_enabled,
21487 copilot_enabled_for_language,
21488 edit_predictions_provider,
21489 is_via_ssh = project.is_via_remote_server(),
21490 );
21491 } else {
21492 telemetry::event!(
21493 event_type,
21494 file_extension,
21495 vim_mode,
21496 copilot_enabled,
21497 copilot_enabled_for_language,
21498 edit_predictions_provider,
21499 is_via_ssh = project.is_via_remote_server(),
21500 );
21501 };
21502 }
21503
21504 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21505 /// with each line being an array of {text, highlight} objects.
21506 fn copy_highlight_json(
21507 &mut self,
21508 _: &CopyHighlightJson,
21509 window: &mut Window,
21510 cx: &mut Context<Self>,
21511 ) {
21512 #[derive(Serialize)]
21513 struct Chunk<'a> {
21514 text: String,
21515 highlight: Option<&'a str>,
21516 }
21517
21518 let snapshot = self.buffer.read(cx).snapshot(cx);
21519 let range = self
21520 .selected_text_range(false, window, cx)
21521 .and_then(|selection| {
21522 if selection.range.is_empty() {
21523 None
21524 } else {
21525 Some(selection.range)
21526 }
21527 })
21528 .unwrap_or_else(|| 0..snapshot.len());
21529
21530 let chunks = snapshot.chunks(range, true);
21531 let mut lines = Vec::new();
21532 let mut line: VecDeque<Chunk> = VecDeque::new();
21533
21534 let Some(style) = self.style.as_ref() else {
21535 return;
21536 };
21537
21538 for chunk in chunks {
21539 let highlight = chunk
21540 .syntax_highlight_id
21541 .and_then(|id| id.name(&style.syntax));
21542 let mut chunk_lines = chunk.text.split('\n').peekable();
21543 while let Some(text) = chunk_lines.next() {
21544 let mut merged_with_last_token = false;
21545 if let Some(last_token) = line.back_mut()
21546 && last_token.highlight == highlight
21547 {
21548 last_token.text.push_str(text);
21549 merged_with_last_token = true;
21550 }
21551
21552 if !merged_with_last_token {
21553 line.push_back(Chunk {
21554 text: text.into(),
21555 highlight,
21556 });
21557 }
21558
21559 if chunk_lines.peek().is_some() {
21560 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21561 line.pop_front();
21562 }
21563 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21564 line.pop_back();
21565 }
21566
21567 lines.push(mem::take(&mut line));
21568 }
21569 }
21570 }
21571
21572 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21573 return;
21574 };
21575 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21576 }
21577
21578 pub fn open_context_menu(
21579 &mut self,
21580 _: &OpenContextMenu,
21581 window: &mut Window,
21582 cx: &mut Context<Self>,
21583 ) {
21584 self.request_autoscroll(Autoscroll::newest(), cx);
21585 let position = self.selections.newest_display(cx).start;
21586 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21587 }
21588
21589 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
21590 &self.inlay_hint_cache
21591 }
21592
21593 pub fn replay_insert_event(
21594 &mut self,
21595 text: &str,
21596 relative_utf16_range: Option<Range<isize>>,
21597 window: &mut Window,
21598 cx: &mut Context<Self>,
21599 ) {
21600 if !self.input_enabled {
21601 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21602 return;
21603 }
21604 if let Some(relative_utf16_range) = relative_utf16_range {
21605 let selections = self.selections.all::<OffsetUtf16>(cx);
21606 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21607 let new_ranges = selections.into_iter().map(|range| {
21608 let start = OffsetUtf16(
21609 range
21610 .head()
21611 .0
21612 .saturating_add_signed(relative_utf16_range.start),
21613 );
21614 let end = OffsetUtf16(
21615 range
21616 .head()
21617 .0
21618 .saturating_add_signed(relative_utf16_range.end),
21619 );
21620 start..end
21621 });
21622 s.select_ranges(new_ranges);
21623 });
21624 }
21625
21626 self.handle_input(text, window, cx);
21627 }
21628
21629 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
21630 let Some(provider) = self.semantics_provider.as_ref() else {
21631 return false;
21632 };
21633
21634 let mut supports = false;
21635 self.buffer().update(cx, |this, cx| {
21636 this.for_each_buffer(|buffer| {
21637 supports |= provider.supports_inlay_hints(buffer, cx);
21638 });
21639 });
21640
21641 supports
21642 }
21643
21644 pub fn is_focused(&self, window: &Window) -> bool {
21645 self.focus_handle.is_focused(window)
21646 }
21647
21648 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21649 cx.emit(EditorEvent::Focused);
21650
21651 if let Some(descendant) = self
21652 .last_focused_descendant
21653 .take()
21654 .and_then(|descendant| descendant.upgrade())
21655 {
21656 window.focus(&descendant);
21657 } else {
21658 if let Some(blame) = self.blame.as_ref() {
21659 blame.update(cx, GitBlame::focus)
21660 }
21661
21662 self.blink_manager.update(cx, BlinkManager::enable);
21663 self.show_cursor_names(window, cx);
21664 self.buffer.update(cx, |buffer, cx| {
21665 buffer.finalize_last_transaction(cx);
21666 if self.leader_id.is_none() {
21667 buffer.set_active_selections(
21668 &self.selections.disjoint_anchors_arc(),
21669 self.selections.line_mode(),
21670 self.cursor_shape,
21671 cx,
21672 );
21673 }
21674 });
21675 }
21676 }
21677
21678 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21679 cx.emit(EditorEvent::FocusedIn)
21680 }
21681
21682 fn handle_focus_out(
21683 &mut self,
21684 event: FocusOutEvent,
21685 _window: &mut Window,
21686 cx: &mut Context<Self>,
21687 ) {
21688 if event.blurred != self.focus_handle {
21689 self.last_focused_descendant = Some(event.blurred);
21690 }
21691 self.selection_drag_state = SelectionDragState::None;
21692 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21693 }
21694
21695 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21696 self.blink_manager.update(cx, BlinkManager::disable);
21697 self.buffer
21698 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21699
21700 if let Some(blame) = self.blame.as_ref() {
21701 blame.update(cx, GitBlame::blur)
21702 }
21703 if !self.hover_state.focused(window, cx) {
21704 hide_hover(self, cx);
21705 }
21706 if !self
21707 .context_menu
21708 .borrow()
21709 .as_ref()
21710 .is_some_and(|context_menu| context_menu.focused(window, cx))
21711 {
21712 self.hide_context_menu(window, cx);
21713 }
21714 self.take_active_edit_prediction(cx);
21715 cx.emit(EditorEvent::Blurred);
21716 cx.notify();
21717 }
21718
21719 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21720 let mut pending: String = window
21721 .pending_input_keystrokes()
21722 .into_iter()
21723 .flatten()
21724 .filter_map(|keystroke| {
21725 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21726 keystroke.key_char.clone()
21727 } else {
21728 None
21729 }
21730 })
21731 .collect();
21732
21733 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21734 pending = "".to_string();
21735 }
21736
21737 let existing_pending = self
21738 .text_highlights::<PendingInput>(cx)
21739 .map(|(_, ranges)| ranges.to_vec());
21740 if existing_pending.is_none() && pending.is_empty() {
21741 return;
21742 }
21743 let transaction =
21744 self.transact(window, cx, |this, window, cx| {
21745 let selections = this.selections.all::<usize>(cx);
21746 let edits = selections
21747 .iter()
21748 .map(|selection| (selection.end..selection.end, pending.clone()));
21749 this.edit(edits, cx);
21750 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21751 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21752 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21753 }));
21754 });
21755 if let Some(existing_ranges) = existing_pending {
21756 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21757 this.edit(edits, cx);
21758 }
21759 });
21760
21761 let snapshot = self.snapshot(window, cx);
21762 let ranges = self
21763 .selections
21764 .all::<usize>(cx)
21765 .into_iter()
21766 .map(|selection| {
21767 snapshot.buffer_snapshot().anchor_after(selection.end)
21768 ..snapshot
21769 .buffer_snapshot()
21770 .anchor_before(selection.end + pending.len())
21771 })
21772 .collect();
21773
21774 if pending.is_empty() {
21775 self.clear_highlights::<PendingInput>(cx);
21776 } else {
21777 self.highlight_text::<PendingInput>(
21778 ranges,
21779 HighlightStyle {
21780 underline: Some(UnderlineStyle {
21781 thickness: px(1.),
21782 color: None,
21783 wavy: false,
21784 }),
21785 ..Default::default()
21786 },
21787 cx,
21788 );
21789 }
21790
21791 self.ime_transaction = self.ime_transaction.or(transaction);
21792 if let Some(transaction) = self.ime_transaction {
21793 self.buffer.update(cx, |buffer, cx| {
21794 buffer.group_until_transaction(transaction, cx);
21795 });
21796 }
21797
21798 if self.text_highlights::<PendingInput>(cx).is_none() {
21799 self.ime_transaction.take();
21800 }
21801 }
21802
21803 pub fn register_action_renderer(
21804 &mut self,
21805 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21806 ) -> Subscription {
21807 let id = self.next_editor_action_id.post_inc();
21808 self.editor_actions
21809 .borrow_mut()
21810 .insert(id, Box::new(listener));
21811
21812 let editor_actions = self.editor_actions.clone();
21813 Subscription::new(move || {
21814 editor_actions.borrow_mut().remove(&id);
21815 })
21816 }
21817
21818 pub fn register_action<A: Action>(
21819 &mut self,
21820 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21821 ) -> Subscription {
21822 let id = self.next_editor_action_id.post_inc();
21823 let listener = Arc::new(listener);
21824 self.editor_actions.borrow_mut().insert(
21825 id,
21826 Box::new(move |_, window, _| {
21827 let listener = listener.clone();
21828 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21829 let action = action.downcast_ref().unwrap();
21830 if phase == DispatchPhase::Bubble {
21831 listener(action, window, cx)
21832 }
21833 })
21834 }),
21835 );
21836
21837 let editor_actions = self.editor_actions.clone();
21838 Subscription::new(move || {
21839 editor_actions.borrow_mut().remove(&id);
21840 })
21841 }
21842
21843 pub fn file_header_size(&self) -> u32 {
21844 FILE_HEADER_HEIGHT
21845 }
21846
21847 pub fn restore(
21848 &mut self,
21849 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21850 window: &mut Window,
21851 cx: &mut Context<Self>,
21852 ) {
21853 let workspace = self.workspace();
21854 let project = self.project();
21855 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21856 let mut tasks = Vec::new();
21857 for (buffer_id, changes) in revert_changes {
21858 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21859 buffer.update(cx, |buffer, cx| {
21860 buffer.edit(
21861 changes
21862 .into_iter()
21863 .map(|(range, text)| (range, text.to_string())),
21864 None,
21865 cx,
21866 );
21867 });
21868
21869 if let Some(project) =
21870 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21871 {
21872 project.update(cx, |project, cx| {
21873 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21874 })
21875 }
21876 }
21877 }
21878 tasks
21879 });
21880 cx.spawn_in(window, async move |_, cx| {
21881 for (buffer, task) in save_tasks {
21882 let result = task.await;
21883 if result.is_err() {
21884 let Some(path) = buffer
21885 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21886 .ok()
21887 else {
21888 continue;
21889 };
21890 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21891 let Some(task) = cx
21892 .update_window_entity(workspace, |workspace, window, cx| {
21893 workspace
21894 .open_path_preview(path, None, false, false, false, window, cx)
21895 })
21896 .ok()
21897 else {
21898 continue;
21899 };
21900 task.await.log_err();
21901 }
21902 }
21903 }
21904 })
21905 .detach();
21906 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21907 selections.refresh()
21908 });
21909 }
21910
21911 pub fn to_pixel_point(
21912 &self,
21913 source: multi_buffer::Anchor,
21914 editor_snapshot: &EditorSnapshot,
21915 window: &mut Window,
21916 ) -> Option<gpui::Point<Pixels>> {
21917 let source_point = source.to_display_point(editor_snapshot);
21918 self.display_to_pixel_point(source_point, editor_snapshot, window)
21919 }
21920
21921 pub fn display_to_pixel_point(
21922 &self,
21923 source: DisplayPoint,
21924 editor_snapshot: &EditorSnapshot,
21925 window: &mut Window,
21926 ) -> Option<gpui::Point<Pixels>> {
21927 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21928 let text_layout_details = self.text_layout_details(window);
21929 let scroll_top = text_layout_details
21930 .scroll_anchor
21931 .scroll_position(editor_snapshot)
21932 .y;
21933
21934 if source.row().as_f64() < scroll_top.floor() {
21935 return None;
21936 }
21937 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21938 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
21939 Some(gpui::Point::new(source_x, source_y))
21940 }
21941
21942 pub fn has_visible_completions_menu(&self) -> bool {
21943 !self.edit_prediction_preview_is_active()
21944 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21945 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21946 })
21947 }
21948
21949 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21950 if self.mode.is_minimap() {
21951 return;
21952 }
21953 self.addons
21954 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21955 }
21956
21957 pub fn unregister_addon<T: Addon>(&mut self) {
21958 self.addons.remove(&std::any::TypeId::of::<T>());
21959 }
21960
21961 pub fn addon<T: Addon>(&self) -> Option<&T> {
21962 let type_id = std::any::TypeId::of::<T>();
21963 self.addons
21964 .get(&type_id)
21965 .and_then(|item| item.to_any().downcast_ref::<T>())
21966 }
21967
21968 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21969 let type_id = std::any::TypeId::of::<T>();
21970 self.addons
21971 .get_mut(&type_id)
21972 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21973 }
21974
21975 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21976 let text_layout_details = self.text_layout_details(window);
21977 let style = &text_layout_details.editor_style;
21978 let font_id = window.text_system().resolve_font(&style.text.font());
21979 let font_size = style.text.font_size.to_pixels(window.rem_size());
21980 let line_height = style.text.line_height_in_pixels(window.rem_size());
21981 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21982 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21983
21984 CharacterDimensions {
21985 em_width,
21986 em_advance,
21987 line_height,
21988 }
21989 }
21990
21991 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21992 self.load_diff_task.clone()
21993 }
21994
21995 fn read_metadata_from_db(
21996 &mut self,
21997 item_id: u64,
21998 workspace_id: WorkspaceId,
21999 window: &mut Window,
22000 cx: &mut Context<Editor>,
22001 ) {
22002 if self.buffer_kind(cx) == ItemBufferKind::Singleton
22003 && !self.mode.is_minimap()
22004 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
22005 {
22006 let buffer_snapshot = OnceCell::new();
22007
22008 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
22009 && !folds.is_empty()
22010 {
22011 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22012 self.fold_ranges(
22013 folds
22014 .into_iter()
22015 .map(|(start, end)| {
22016 snapshot.clip_offset(start, Bias::Left)
22017 ..snapshot.clip_offset(end, Bias::Right)
22018 })
22019 .collect(),
22020 false,
22021 window,
22022 cx,
22023 );
22024 }
22025
22026 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22027 && !selections.is_empty()
22028 {
22029 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22030 // skip adding the initial selection to selection history
22031 self.selection_history.mode = SelectionHistoryMode::Skipping;
22032 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22033 s.select_ranges(selections.into_iter().map(|(start, end)| {
22034 snapshot.clip_offset(start, Bias::Left)
22035 ..snapshot.clip_offset(end, Bias::Right)
22036 }));
22037 });
22038 self.selection_history.mode = SelectionHistoryMode::Normal;
22039 };
22040 }
22041
22042 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22043 }
22044
22045 fn update_lsp_data(
22046 &mut self,
22047 ignore_cache: bool,
22048 for_buffer: Option<BufferId>,
22049 window: &mut Window,
22050 cx: &mut Context<'_, Self>,
22051 ) {
22052 self.pull_diagnostics(for_buffer, window, cx);
22053 self.refresh_colors(ignore_cache, for_buffer, window, cx);
22054 }
22055}
22056
22057fn edit_for_markdown_paste<'a>(
22058 buffer: &MultiBufferSnapshot,
22059 range: Range<usize>,
22060 to_insert: &'a str,
22061 url: Option<url::Url>,
22062) -> (Range<usize>, Cow<'a, str>) {
22063 if url.is_none() {
22064 return (range, Cow::Borrowed(to_insert));
22065 };
22066
22067 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22068
22069 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22070 Cow::Borrowed(to_insert)
22071 } else {
22072 Cow::Owned(format!("[{old_text}]({to_insert})"))
22073 };
22074 (range, new_text)
22075}
22076
22077fn vim_enabled(cx: &App) -> bool {
22078 vim_mode_setting::VimModeSetting::try_get(cx)
22079 .map(|vim_mode| vim_mode.0)
22080 .unwrap_or(false)
22081}
22082
22083fn process_completion_for_edit(
22084 completion: &Completion,
22085 intent: CompletionIntent,
22086 buffer: &Entity<Buffer>,
22087 cursor_position: &text::Anchor,
22088 cx: &mut Context<Editor>,
22089) -> CompletionEdit {
22090 let buffer = buffer.read(cx);
22091 let buffer_snapshot = buffer.snapshot();
22092 let (snippet, new_text) = if completion.is_snippet() {
22093 let mut snippet_source = completion.new_text.clone();
22094 // Workaround for typescript language server issues so that methods don't expand within
22095 // strings and functions with type expressions. The previous point is used because the query
22096 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22097 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22098 let previous_point = if previous_point.column > 0 {
22099 cursor_position.to_previous_offset(&buffer_snapshot)
22100 } else {
22101 cursor_position.to_offset(&buffer_snapshot)
22102 };
22103 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22104 && scope.prefers_label_for_snippet_in_completion()
22105 && let Some(label) = completion.label()
22106 && matches!(
22107 completion.kind(),
22108 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22109 )
22110 {
22111 snippet_source = label;
22112 }
22113 match Snippet::parse(&snippet_source).log_err() {
22114 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22115 None => (None, completion.new_text.clone()),
22116 }
22117 } else {
22118 (None, completion.new_text.clone())
22119 };
22120
22121 let mut range_to_replace = {
22122 let replace_range = &completion.replace_range;
22123 if let CompletionSource::Lsp {
22124 insert_range: Some(insert_range),
22125 ..
22126 } = &completion.source
22127 {
22128 debug_assert_eq!(
22129 insert_range.start, replace_range.start,
22130 "insert_range and replace_range should start at the same position"
22131 );
22132 debug_assert!(
22133 insert_range
22134 .start
22135 .cmp(cursor_position, &buffer_snapshot)
22136 .is_le(),
22137 "insert_range should start before or at cursor position"
22138 );
22139 debug_assert!(
22140 replace_range
22141 .start
22142 .cmp(cursor_position, &buffer_snapshot)
22143 .is_le(),
22144 "replace_range should start before or at cursor position"
22145 );
22146
22147 let should_replace = match intent {
22148 CompletionIntent::CompleteWithInsert => false,
22149 CompletionIntent::CompleteWithReplace => true,
22150 CompletionIntent::Complete | CompletionIntent::Compose => {
22151 let insert_mode =
22152 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22153 .completions
22154 .lsp_insert_mode;
22155 match insert_mode {
22156 LspInsertMode::Insert => false,
22157 LspInsertMode::Replace => true,
22158 LspInsertMode::ReplaceSubsequence => {
22159 let mut text_to_replace = buffer.chars_for_range(
22160 buffer.anchor_before(replace_range.start)
22161 ..buffer.anchor_after(replace_range.end),
22162 );
22163 let mut current_needle = text_to_replace.next();
22164 for haystack_ch in completion.label.text.chars() {
22165 if let Some(needle_ch) = current_needle
22166 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22167 {
22168 current_needle = text_to_replace.next();
22169 }
22170 }
22171 current_needle.is_none()
22172 }
22173 LspInsertMode::ReplaceSuffix => {
22174 if replace_range
22175 .end
22176 .cmp(cursor_position, &buffer_snapshot)
22177 .is_gt()
22178 {
22179 let range_after_cursor = *cursor_position..replace_range.end;
22180 let text_after_cursor = buffer
22181 .text_for_range(
22182 buffer.anchor_before(range_after_cursor.start)
22183 ..buffer.anchor_after(range_after_cursor.end),
22184 )
22185 .collect::<String>()
22186 .to_ascii_lowercase();
22187 completion
22188 .label
22189 .text
22190 .to_ascii_lowercase()
22191 .ends_with(&text_after_cursor)
22192 } else {
22193 true
22194 }
22195 }
22196 }
22197 }
22198 };
22199
22200 if should_replace {
22201 replace_range.clone()
22202 } else {
22203 insert_range.clone()
22204 }
22205 } else {
22206 replace_range.clone()
22207 }
22208 };
22209
22210 if range_to_replace
22211 .end
22212 .cmp(cursor_position, &buffer_snapshot)
22213 .is_lt()
22214 {
22215 range_to_replace.end = *cursor_position;
22216 }
22217
22218 CompletionEdit {
22219 new_text,
22220 replace_range: range_to_replace.to_offset(buffer),
22221 snippet,
22222 }
22223}
22224
22225struct CompletionEdit {
22226 new_text: String,
22227 replace_range: Range<usize>,
22228 snippet: Option<Snippet>,
22229}
22230
22231fn insert_extra_newline_brackets(
22232 buffer: &MultiBufferSnapshot,
22233 range: Range<usize>,
22234 language: &language::LanguageScope,
22235) -> bool {
22236 let leading_whitespace_len = buffer
22237 .reversed_chars_at(range.start)
22238 .take_while(|c| c.is_whitespace() && *c != '\n')
22239 .map(|c| c.len_utf8())
22240 .sum::<usize>();
22241 let trailing_whitespace_len = buffer
22242 .chars_at(range.end)
22243 .take_while(|c| c.is_whitespace() && *c != '\n')
22244 .map(|c| c.len_utf8())
22245 .sum::<usize>();
22246 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22247
22248 language.brackets().any(|(pair, enabled)| {
22249 let pair_start = pair.start.trim_end();
22250 let pair_end = pair.end.trim_start();
22251
22252 enabled
22253 && pair.newline
22254 && buffer.contains_str_at(range.end, pair_end)
22255 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22256 })
22257}
22258
22259fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22260 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22261 [(buffer, range, _)] => (*buffer, range.clone()),
22262 _ => return false,
22263 };
22264 let pair = {
22265 let mut result: Option<BracketMatch> = None;
22266
22267 for pair in buffer
22268 .all_bracket_ranges(range.clone())
22269 .filter(move |pair| {
22270 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22271 })
22272 {
22273 let len = pair.close_range.end - pair.open_range.start;
22274
22275 if let Some(existing) = &result {
22276 let existing_len = existing.close_range.end - existing.open_range.start;
22277 if len > existing_len {
22278 continue;
22279 }
22280 }
22281
22282 result = Some(pair);
22283 }
22284
22285 result
22286 };
22287 let Some(pair) = pair else {
22288 return false;
22289 };
22290 pair.newline_only
22291 && buffer
22292 .chars_for_range(pair.open_range.end..range.start)
22293 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22294 .all(|c| c.is_whitespace() && c != '\n')
22295}
22296
22297fn update_uncommitted_diff_for_buffer(
22298 editor: Entity<Editor>,
22299 project: &Entity<Project>,
22300 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22301 buffer: Entity<MultiBuffer>,
22302 cx: &mut App,
22303) -> Task<()> {
22304 let mut tasks = Vec::new();
22305 project.update(cx, |project, cx| {
22306 for buffer in buffers {
22307 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22308 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22309 }
22310 }
22311 });
22312 cx.spawn(async move |cx| {
22313 let diffs = future::join_all(tasks).await;
22314 if editor
22315 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22316 .unwrap_or(false)
22317 {
22318 return;
22319 }
22320
22321 buffer
22322 .update(cx, |buffer, cx| {
22323 for diff in diffs.into_iter().flatten() {
22324 buffer.add_diff(diff, cx);
22325 }
22326 })
22327 .ok();
22328 })
22329}
22330
22331fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22332 let tab_size = tab_size.get() as usize;
22333 let mut width = offset;
22334
22335 for ch in text.chars() {
22336 width += if ch == '\t' {
22337 tab_size - (width % tab_size)
22338 } else {
22339 1
22340 };
22341 }
22342
22343 width - offset
22344}
22345
22346#[cfg(test)]
22347mod tests {
22348 use super::*;
22349
22350 #[test]
22351 fn test_string_size_with_expanded_tabs() {
22352 let nz = |val| NonZeroU32::new(val).unwrap();
22353 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22354 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22355 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22356 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22357 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22358 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22359 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22360 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22361 }
22362}
22363
22364/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22365struct WordBreakingTokenizer<'a> {
22366 input: &'a str,
22367}
22368
22369impl<'a> WordBreakingTokenizer<'a> {
22370 fn new(input: &'a str) -> Self {
22371 Self { input }
22372 }
22373}
22374
22375fn is_char_ideographic(ch: char) -> bool {
22376 use unicode_script::Script::*;
22377 use unicode_script::UnicodeScript;
22378 matches!(ch.script(), Han | Tangut | Yi)
22379}
22380
22381fn is_grapheme_ideographic(text: &str) -> bool {
22382 text.chars().any(is_char_ideographic)
22383}
22384
22385fn is_grapheme_whitespace(text: &str) -> bool {
22386 text.chars().any(|x| x.is_whitespace())
22387}
22388
22389fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22390 text.chars()
22391 .next()
22392 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22393}
22394
22395#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22396enum WordBreakToken<'a> {
22397 Word { token: &'a str, grapheme_len: usize },
22398 InlineWhitespace { token: &'a str, grapheme_len: usize },
22399 Newline,
22400}
22401
22402impl<'a> Iterator for WordBreakingTokenizer<'a> {
22403 /// Yields a span, the count of graphemes in the token, and whether it was
22404 /// whitespace. Note that it also breaks at word boundaries.
22405 type Item = WordBreakToken<'a>;
22406
22407 fn next(&mut self) -> Option<Self::Item> {
22408 use unicode_segmentation::UnicodeSegmentation;
22409 if self.input.is_empty() {
22410 return None;
22411 }
22412
22413 let mut iter = self.input.graphemes(true).peekable();
22414 let mut offset = 0;
22415 let mut grapheme_len = 0;
22416 if let Some(first_grapheme) = iter.next() {
22417 let is_newline = first_grapheme == "\n";
22418 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22419 offset += first_grapheme.len();
22420 grapheme_len += 1;
22421 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22422 if let Some(grapheme) = iter.peek().copied()
22423 && should_stay_with_preceding_ideograph(grapheme)
22424 {
22425 offset += grapheme.len();
22426 grapheme_len += 1;
22427 }
22428 } else {
22429 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22430 let mut next_word_bound = words.peek().copied();
22431 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22432 next_word_bound = words.next();
22433 }
22434 while let Some(grapheme) = iter.peek().copied() {
22435 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22436 break;
22437 };
22438 if is_grapheme_whitespace(grapheme) != is_whitespace
22439 || (grapheme == "\n") != is_newline
22440 {
22441 break;
22442 };
22443 offset += grapheme.len();
22444 grapheme_len += 1;
22445 iter.next();
22446 }
22447 }
22448 let token = &self.input[..offset];
22449 self.input = &self.input[offset..];
22450 if token == "\n" {
22451 Some(WordBreakToken::Newline)
22452 } else if is_whitespace {
22453 Some(WordBreakToken::InlineWhitespace {
22454 token,
22455 grapheme_len,
22456 })
22457 } else {
22458 Some(WordBreakToken::Word {
22459 token,
22460 grapheme_len,
22461 })
22462 }
22463 } else {
22464 None
22465 }
22466 }
22467}
22468
22469#[test]
22470fn test_word_breaking_tokenizer() {
22471 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22472 ("", &[]),
22473 (" ", &[whitespace(" ", 2)]),
22474 ("Ʒ", &[word("Ʒ", 1)]),
22475 ("Ǽ", &[word("Ǽ", 1)]),
22476 ("⋑", &[word("⋑", 1)]),
22477 ("⋑⋑", &[word("⋑⋑", 2)]),
22478 (
22479 "原理,进而",
22480 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22481 ),
22482 (
22483 "hello world",
22484 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22485 ),
22486 (
22487 "hello, world",
22488 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22489 ),
22490 (
22491 " hello world",
22492 &[
22493 whitespace(" ", 2),
22494 word("hello", 5),
22495 whitespace(" ", 1),
22496 word("world", 5),
22497 ],
22498 ),
22499 (
22500 "这是什么 \n 钢笔",
22501 &[
22502 word("这", 1),
22503 word("是", 1),
22504 word("什", 1),
22505 word("么", 1),
22506 whitespace(" ", 1),
22507 newline(),
22508 whitespace(" ", 1),
22509 word("钢", 1),
22510 word("笔", 1),
22511 ],
22512 ),
22513 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22514 ];
22515
22516 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22517 WordBreakToken::Word {
22518 token,
22519 grapheme_len,
22520 }
22521 }
22522
22523 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22524 WordBreakToken::InlineWhitespace {
22525 token,
22526 grapheme_len,
22527 }
22528 }
22529
22530 fn newline() -> WordBreakToken<'static> {
22531 WordBreakToken::Newline
22532 }
22533
22534 for (input, result) in tests {
22535 assert_eq!(
22536 WordBreakingTokenizer::new(input)
22537 .collect::<Vec<_>>()
22538 .as_slice(),
22539 *result,
22540 );
22541 }
22542}
22543
22544fn wrap_with_prefix(
22545 first_line_prefix: String,
22546 subsequent_lines_prefix: String,
22547 unwrapped_text: String,
22548 wrap_column: usize,
22549 tab_size: NonZeroU32,
22550 preserve_existing_whitespace: bool,
22551) -> String {
22552 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22553 let subsequent_lines_prefix_len =
22554 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22555 let mut wrapped_text = String::new();
22556 let mut current_line = first_line_prefix;
22557 let mut is_first_line = true;
22558
22559 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22560 let mut current_line_len = first_line_prefix_len;
22561 let mut in_whitespace = false;
22562 for token in tokenizer {
22563 let have_preceding_whitespace = in_whitespace;
22564 match token {
22565 WordBreakToken::Word {
22566 token,
22567 grapheme_len,
22568 } => {
22569 in_whitespace = false;
22570 let current_prefix_len = if is_first_line {
22571 first_line_prefix_len
22572 } else {
22573 subsequent_lines_prefix_len
22574 };
22575 if current_line_len + grapheme_len > wrap_column
22576 && current_line_len != current_prefix_len
22577 {
22578 wrapped_text.push_str(current_line.trim_end());
22579 wrapped_text.push('\n');
22580 is_first_line = false;
22581 current_line = subsequent_lines_prefix.clone();
22582 current_line_len = subsequent_lines_prefix_len;
22583 }
22584 current_line.push_str(token);
22585 current_line_len += grapheme_len;
22586 }
22587 WordBreakToken::InlineWhitespace {
22588 mut token,
22589 mut grapheme_len,
22590 } => {
22591 in_whitespace = true;
22592 if have_preceding_whitespace && !preserve_existing_whitespace {
22593 continue;
22594 }
22595 if !preserve_existing_whitespace {
22596 // Keep a single whitespace grapheme as-is
22597 if let Some(first) =
22598 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22599 {
22600 token = first;
22601 } else {
22602 token = " ";
22603 }
22604 grapheme_len = 1;
22605 }
22606 let current_prefix_len = if is_first_line {
22607 first_line_prefix_len
22608 } else {
22609 subsequent_lines_prefix_len
22610 };
22611 if current_line_len + grapheme_len > wrap_column {
22612 wrapped_text.push_str(current_line.trim_end());
22613 wrapped_text.push('\n');
22614 is_first_line = false;
22615 current_line = subsequent_lines_prefix.clone();
22616 current_line_len = subsequent_lines_prefix_len;
22617 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22618 current_line.push_str(token);
22619 current_line_len += grapheme_len;
22620 }
22621 }
22622 WordBreakToken::Newline => {
22623 in_whitespace = true;
22624 let current_prefix_len = if is_first_line {
22625 first_line_prefix_len
22626 } else {
22627 subsequent_lines_prefix_len
22628 };
22629 if preserve_existing_whitespace {
22630 wrapped_text.push_str(current_line.trim_end());
22631 wrapped_text.push('\n');
22632 is_first_line = false;
22633 current_line = subsequent_lines_prefix.clone();
22634 current_line_len = subsequent_lines_prefix_len;
22635 } else if have_preceding_whitespace {
22636 continue;
22637 } else if current_line_len + 1 > wrap_column
22638 && current_line_len != current_prefix_len
22639 {
22640 wrapped_text.push_str(current_line.trim_end());
22641 wrapped_text.push('\n');
22642 is_first_line = false;
22643 current_line = subsequent_lines_prefix.clone();
22644 current_line_len = subsequent_lines_prefix_len;
22645 } else if current_line_len != current_prefix_len {
22646 current_line.push(' ');
22647 current_line_len += 1;
22648 }
22649 }
22650 }
22651 }
22652
22653 if !current_line.is_empty() {
22654 wrapped_text.push_str(¤t_line);
22655 }
22656 wrapped_text
22657}
22658
22659#[test]
22660fn test_wrap_with_prefix() {
22661 assert_eq!(
22662 wrap_with_prefix(
22663 "# ".to_string(),
22664 "# ".to_string(),
22665 "abcdefg".to_string(),
22666 4,
22667 NonZeroU32::new(4).unwrap(),
22668 false,
22669 ),
22670 "# abcdefg"
22671 );
22672 assert_eq!(
22673 wrap_with_prefix(
22674 "".to_string(),
22675 "".to_string(),
22676 "\thello world".to_string(),
22677 8,
22678 NonZeroU32::new(4).unwrap(),
22679 false,
22680 ),
22681 "hello\nworld"
22682 );
22683 assert_eq!(
22684 wrap_with_prefix(
22685 "// ".to_string(),
22686 "// ".to_string(),
22687 "xx \nyy zz aa bb cc".to_string(),
22688 12,
22689 NonZeroU32::new(4).unwrap(),
22690 false,
22691 ),
22692 "// xx yy zz\n// aa bb cc"
22693 );
22694 assert_eq!(
22695 wrap_with_prefix(
22696 String::new(),
22697 String::new(),
22698 "这是什么 \n 钢笔".to_string(),
22699 3,
22700 NonZeroU32::new(4).unwrap(),
22701 false,
22702 ),
22703 "这是什\n么 钢\n笔"
22704 );
22705 assert_eq!(
22706 wrap_with_prefix(
22707 String::new(),
22708 String::new(),
22709 format!("foo{}bar", '\u{2009}'), // thin space
22710 80,
22711 NonZeroU32::new(4).unwrap(),
22712 false,
22713 ),
22714 format!("foo{}bar", '\u{2009}')
22715 );
22716}
22717
22718pub trait CollaborationHub {
22719 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22720 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22721 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22722}
22723
22724impl CollaborationHub for Entity<Project> {
22725 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22726 self.read(cx).collaborators()
22727 }
22728
22729 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22730 self.read(cx).user_store().read(cx).participant_indices()
22731 }
22732
22733 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22734 let this = self.read(cx);
22735 let user_ids = this.collaborators().values().map(|c| c.user_id);
22736 this.user_store().read(cx).participant_names(user_ids, cx)
22737 }
22738}
22739
22740pub trait SemanticsProvider {
22741 fn hover(
22742 &self,
22743 buffer: &Entity<Buffer>,
22744 position: text::Anchor,
22745 cx: &mut App,
22746 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22747
22748 fn inline_values(
22749 &self,
22750 buffer_handle: Entity<Buffer>,
22751 range: Range<text::Anchor>,
22752 cx: &mut App,
22753 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22754
22755 fn inlay_hints(
22756 &self,
22757 buffer_handle: Entity<Buffer>,
22758 range: Range<text::Anchor>,
22759 cx: &mut App,
22760 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22761
22762 fn resolve_inlay_hint(
22763 &self,
22764 hint: InlayHint,
22765 buffer_handle: Entity<Buffer>,
22766 server_id: LanguageServerId,
22767 cx: &mut App,
22768 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22769
22770 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22771
22772 fn document_highlights(
22773 &self,
22774 buffer: &Entity<Buffer>,
22775 position: text::Anchor,
22776 cx: &mut App,
22777 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22778
22779 fn definitions(
22780 &self,
22781 buffer: &Entity<Buffer>,
22782 position: text::Anchor,
22783 kind: GotoDefinitionKind,
22784 cx: &mut App,
22785 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22786
22787 fn range_for_rename(
22788 &self,
22789 buffer: &Entity<Buffer>,
22790 position: text::Anchor,
22791 cx: &mut App,
22792 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22793
22794 fn perform_rename(
22795 &self,
22796 buffer: &Entity<Buffer>,
22797 position: text::Anchor,
22798 new_name: String,
22799 cx: &mut App,
22800 ) -> Option<Task<Result<ProjectTransaction>>>;
22801}
22802
22803pub trait CompletionProvider {
22804 fn completions(
22805 &self,
22806 excerpt_id: ExcerptId,
22807 buffer: &Entity<Buffer>,
22808 buffer_position: text::Anchor,
22809 trigger: CompletionContext,
22810 window: &mut Window,
22811 cx: &mut Context<Editor>,
22812 ) -> Task<Result<Vec<CompletionResponse>>>;
22813
22814 fn resolve_completions(
22815 &self,
22816 _buffer: Entity<Buffer>,
22817 _completion_indices: Vec<usize>,
22818 _completions: Rc<RefCell<Box<[Completion]>>>,
22819 _cx: &mut Context<Editor>,
22820 ) -> Task<Result<bool>> {
22821 Task::ready(Ok(false))
22822 }
22823
22824 fn apply_additional_edits_for_completion(
22825 &self,
22826 _buffer: Entity<Buffer>,
22827 _completions: Rc<RefCell<Box<[Completion]>>>,
22828 _completion_index: usize,
22829 _push_to_history: bool,
22830 _cx: &mut Context<Editor>,
22831 ) -> Task<Result<Option<language::Transaction>>> {
22832 Task::ready(Ok(None))
22833 }
22834
22835 fn is_completion_trigger(
22836 &self,
22837 buffer: &Entity<Buffer>,
22838 position: language::Anchor,
22839 text: &str,
22840 trigger_in_words: bool,
22841 menu_is_open: bool,
22842 cx: &mut Context<Editor>,
22843 ) -> bool;
22844
22845 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22846
22847 fn sort_completions(&self) -> bool {
22848 true
22849 }
22850
22851 fn filter_completions(&self) -> bool {
22852 true
22853 }
22854}
22855
22856pub trait CodeActionProvider {
22857 fn id(&self) -> Arc<str>;
22858
22859 fn code_actions(
22860 &self,
22861 buffer: &Entity<Buffer>,
22862 range: Range<text::Anchor>,
22863 window: &mut Window,
22864 cx: &mut App,
22865 ) -> Task<Result<Vec<CodeAction>>>;
22866
22867 fn apply_code_action(
22868 &self,
22869 buffer_handle: Entity<Buffer>,
22870 action: CodeAction,
22871 excerpt_id: ExcerptId,
22872 push_to_history: bool,
22873 window: &mut Window,
22874 cx: &mut App,
22875 ) -> Task<Result<ProjectTransaction>>;
22876}
22877
22878impl CodeActionProvider for Entity<Project> {
22879 fn id(&self) -> Arc<str> {
22880 "project".into()
22881 }
22882
22883 fn code_actions(
22884 &self,
22885 buffer: &Entity<Buffer>,
22886 range: Range<text::Anchor>,
22887 _window: &mut Window,
22888 cx: &mut App,
22889 ) -> Task<Result<Vec<CodeAction>>> {
22890 self.update(cx, |project, cx| {
22891 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22892 let code_actions = project.code_actions(buffer, range, None, cx);
22893 cx.background_spawn(async move {
22894 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22895 Ok(code_lens_actions
22896 .context("code lens fetch")?
22897 .into_iter()
22898 .flatten()
22899 .chain(
22900 code_actions
22901 .context("code action fetch")?
22902 .into_iter()
22903 .flatten(),
22904 )
22905 .collect())
22906 })
22907 })
22908 }
22909
22910 fn apply_code_action(
22911 &self,
22912 buffer_handle: Entity<Buffer>,
22913 action: CodeAction,
22914 _excerpt_id: ExcerptId,
22915 push_to_history: bool,
22916 _window: &mut Window,
22917 cx: &mut App,
22918 ) -> Task<Result<ProjectTransaction>> {
22919 self.update(cx, |project, cx| {
22920 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22921 })
22922 }
22923}
22924
22925fn snippet_completions(
22926 project: &Project,
22927 buffer: &Entity<Buffer>,
22928 buffer_position: text::Anchor,
22929 cx: &mut App,
22930) -> Task<Result<CompletionResponse>> {
22931 let languages = buffer.read(cx).languages_at(buffer_position);
22932 let snippet_store = project.snippets().read(cx);
22933
22934 let scopes: Vec<_> = languages
22935 .iter()
22936 .filter_map(|language| {
22937 let language_name = language.lsp_id();
22938 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22939
22940 if snippets.is_empty() {
22941 None
22942 } else {
22943 Some((language.default_scope(), snippets))
22944 }
22945 })
22946 .collect();
22947
22948 if scopes.is_empty() {
22949 return Task::ready(Ok(CompletionResponse {
22950 completions: vec![],
22951 display_options: CompletionDisplayOptions::default(),
22952 is_incomplete: false,
22953 }));
22954 }
22955
22956 let snapshot = buffer.read(cx).text_snapshot();
22957 let executor = cx.background_executor().clone();
22958
22959 cx.background_spawn(async move {
22960 let mut is_incomplete = false;
22961 let mut completions: Vec<Completion> = Vec::new();
22962 for (scope, snippets) in scopes.into_iter() {
22963 let classifier =
22964 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
22965
22966 const MAX_WORD_PREFIX_LEN: usize = 128;
22967 let last_word: String = snapshot
22968 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22969 .take(MAX_WORD_PREFIX_LEN)
22970 .take_while(|c| classifier.is_word(*c))
22971 .collect::<String>()
22972 .chars()
22973 .rev()
22974 .collect();
22975
22976 if last_word.is_empty() {
22977 return Ok(CompletionResponse {
22978 completions: vec![],
22979 display_options: CompletionDisplayOptions::default(),
22980 is_incomplete: true,
22981 });
22982 }
22983
22984 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22985 let to_lsp = |point: &text::Anchor| {
22986 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22987 point_to_lsp(end)
22988 };
22989 let lsp_end = to_lsp(&buffer_position);
22990
22991 let candidates = snippets
22992 .iter()
22993 .enumerate()
22994 .flat_map(|(ix, snippet)| {
22995 snippet
22996 .prefix
22997 .iter()
22998 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22999 })
23000 .collect::<Vec<StringMatchCandidate>>();
23001
23002 const MAX_RESULTS: usize = 100;
23003 let mut matches = fuzzy::match_strings(
23004 &candidates,
23005 &last_word,
23006 last_word.chars().any(|c| c.is_uppercase()),
23007 true,
23008 MAX_RESULTS,
23009 &Default::default(),
23010 executor.clone(),
23011 )
23012 .await;
23013
23014 if matches.len() >= MAX_RESULTS {
23015 is_incomplete = true;
23016 }
23017
23018 // Remove all candidates where the query's start does not match the start of any word in the candidate
23019 if let Some(query_start) = last_word.chars().next() {
23020 matches.retain(|string_match| {
23021 split_words(&string_match.string).any(|word| {
23022 // Check that the first codepoint of the word as lowercase matches the first
23023 // codepoint of the query as lowercase
23024 word.chars()
23025 .flat_map(|codepoint| codepoint.to_lowercase())
23026 .zip(query_start.to_lowercase())
23027 .all(|(word_cp, query_cp)| word_cp == query_cp)
23028 })
23029 });
23030 }
23031
23032 let matched_strings = matches
23033 .into_iter()
23034 .map(|m| m.string)
23035 .collect::<HashSet<_>>();
23036
23037 completions.extend(snippets.iter().filter_map(|snippet| {
23038 let matching_prefix = snippet
23039 .prefix
23040 .iter()
23041 .find(|prefix| matched_strings.contains(*prefix))?;
23042 let start = as_offset - last_word.len();
23043 let start = snapshot.anchor_before(start);
23044 let range = start..buffer_position;
23045 let lsp_start = to_lsp(&start);
23046 let lsp_range = lsp::Range {
23047 start: lsp_start,
23048 end: lsp_end,
23049 };
23050 Some(Completion {
23051 replace_range: range,
23052 new_text: snippet.body.clone(),
23053 source: CompletionSource::Lsp {
23054 insert_range: None,
23055 server_id: LanguageServerId(usize::MAX),
23056 resolved: true,
23057 lsp_completion: Box::new(lsp::CompletionItem {
23058 label: snippet.prefix.first().unwrap().clone(),
23059 kind: Some(CompletionItemKind::SNIPPET),
23060 label_details: snippet.description.as_ref().map(|description| {
23061 lsp::CompletionItemLabelDetails {
23062 detail: Some(description.clone()),
23063 description: None,
23064 }
23065 }),
23066 insert_text_format: Some(InsertTextFormat::SNIPPET),
23067 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23068 lsp::InsertReplaceEdit {
23069 new_text: snippet.body.clone(),
23070 insert: lsp_range,
23071 replace: lsp_range,
23072 },
23073 )),
23074 filter_text: Some(snippet.body.clone()),
23075 sort_text: Some(char::MAX.to_string()),
23076 ..lsp::CompletionItem::default()
23077 }),
23078 lsp_defaults: None,
23079 },
23080 label: CodeLabel {
23081 text: matching_prefix.clone(),
23082 runs: Vec::new(),
23083 filter_range: 0..matching_prefix.len(),
23084 },
23085 icon_path: None,
23086 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23087 single_line: snippet.name.clone().into(),
23088 plain_text: snippet
23089 .description
23090 .clone()
23091 .map(|description| description.into()),
23092 }),
23093 insert_text_mode: None,
23094 confirm: None,
23095 })
23096 }))
23097 }
23098
23099 Ok(CompletionResponse {
23100 completions,
23101 display_options: CompletionDisplayOptions::default(),
23102 is_incomplete,
23103 })
23104 })
23105}
23106
23107impl CompletionProvider for Entity<Project> {
23108 fn completions(
23109 &self,
23110 _excerpt_id: ExcerptId,
23111 buffer: &Entity<Buffer>,
23112 buffer_position: text::Anchor,
23113 options: CompletionContext,
23114 _window: &mut Window,
23115 cx: &mut Context<Editor>,
23116 ) -> Task<Result<Vec<CompletionResponse>>> {
23117 self.update(cx, |project, cx| {
23118 let snippets = snippet_completions(project, buffer, buffer_position, cx);
23119 let project_completions = project.completions(buffer, buffer_position, options, cx);
23120 cx.background_spawn(async move {
23121 let mut responses = project_completions.await?;
23122 let snippets = snippets.await?;
23123 if !snippets.completions.is_empty() {
23124 responses.push(snippets);
23125 }
23126 Ok(responses)
23127 })
23128 })
23129 }
23130
23131 fn resolve_completions(
23132 &self,
23133 buffer: Entity<Buffer>,
23134 completion_indices: Vec<usize>,
23135 completions: Rc<RefCell<Box<[Completion]>>>,
23136 cx: &mut Context<Editor>,
23137 ) -> Task<Result<bool>> {
23138 self.update(cx, |project, cx| {
23139 project.lsp_store().update(cx, |lsp_store, cx| {
23140 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23141 })
23142 })
23143 }
23144
23145 fn apply_additional_edits_for_completion(
23146 &self,
23147 buffer: Entity<Buffer>,
23148 completions: Rc<RefCell<Box<[Completion]>>>,
23149 completion_index: usize,
23150 push_to_history: bool,
23151 cx: &mut Context<Editor>,
23152 ) -> Task<Result<Option<language::Transaction>>> {
23153 self.update(cx, |project, cx| {
23154 project.lsp_store().update(cx, |lsp_store, cx| {
23155 lsp_store.apply_additional_edits_for_completion(
23156 buffer,
23157 completions,
23158 completion_index,
23159 push_to_history,
23160 cx,
23161 )
23162 })
23163 })
23164 }
23165
23166 fn is_completion_trigger(
23167 &self,
23168 buffer: &Entity<Buffer>,
23169 position: language::Anchor,
23170 text: &str,
23171 trigger_in_words: bool,
23172 menu_is_open: bool,
23173 cx: &mut Context<Editor>,
23174 ) -> bool {
23175 let mut chars = text.chars();
23176 let char = if let Some(char) = chars.next() {
23177 char
23178 } else {
23179 return false;
23180 };
23181 if chars.next().is_some() {
23182 return false;
23183 }
23184
23185 let buffer = buffer.read(cx);
23186 let snapshot = buffer.snapshot();
23187 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23188 return false;
23189 }
23190 let classifier = snapshot
23191 .char_classifier_at(position)
23192 .scope_context(Some(CharScopeContext::Completion));
23193 if trigger_in_words && classifier.is_word(char) {
23194 return true;
23195 }
23196
23197 buffer.completion_triggers().contains(text)
23198 }
23199}
23200
23201impl SemanticsProvider for Entity<Project> {
23202 fn hover(
23203 &self,
23204 buffer: &Entity<Buffer>,
23205 position: text::Anchor,
23206 cx: &mut App,
23207 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23208 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23209 }
23210
23211 fn document_highlights(
23212 &self,
23213 buffer: &Entity<Buffer>,
23214 position: text::Anchor,
23215 cx: &mut App,
23216 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23217 Some(self.update(cx, |project, cx| {
23218 project.document_highlights(buffer, position, cx)
23219 }))
23220 }
23221
23222 fn definitions(
23223 &self,
23224 buffer: &Entity<Buffer>,
23225 position: text::Anchor,
23226 kind: GotoDefinitionKind,
23227 cx: &mut App,
23228 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23229 Some(self.update(cx, |project, cx| match kind {
23230 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23231 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23232 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23233 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23234 }))
23235 }
23236
23237 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23238 self.update(cx, |project, cx| {
23239 if project
23240 .active_debug_session(cx)
23241 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23242 {
23243 return true;
23244 }
23245
23246 buffer.update(cx, |buffer, cx| {
23247 project.any_language_server_supports_inlay_hints(buffer, cx)
23248 })
23249 })
23250 }
23251
23252 fn inline_values(
23253 &self,
23254 buffer_handle: Entity<Buffer>,
23255 range: Range<text::Anchor>,
23256 cx: &mut App,
23257 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23258 self.update(cx, |project, cx| {
23259 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23260
23261 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23262 })
23263 }
23264
23265 fn inlay_hints(
23266 &self,
23267 buffer_handle: Entity<Buffer>,
23268 range: Range<text::Anchor>,
23269 cx: &mut App,
23270 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23271 Some(self.update(cx, |project, cx| {
23272 project.inlay_hints(buffer_handle, range, cx)
23273 }))
23274 }
23275
23276 fn resolve_inlay_hint(
23277 &self,
23278 hint: InlayHint,
23279 buffer_handle: Entity<Buffer>,
23280 server_id: LanguageServerId,
23281 cx: &mut App,
23282 ) -> Option<Task<anyhow::Result<InlayHint>>> {
23283 Some(self.update(cx, |project, cx| {
23284 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
23285 }))
23286 }
23287
23288 fn range_for_rename(
23289 &self,
23290 buffer: &Entity<Buffer>,
23291 position: text::Anchor,
23292 cx: &mut App,
23293 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23294 Some(self.update(cx, |project, cx| {
23295 let buffer = buffer.clone();
23296 let task = project.prepare_rename(buffer.clone(), position, cx);
23297 cx.spawn(async move |_, cx| {
23298 Ok(match task.await? {
23299 PrepareRenameResponse::Success(range) => Some(range),
23300 PrepareRenameResponse::InvalidPosition => None,
23301 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23302 // Fallback on using TreeSitter info to determine identifier range
23303 buffer.read_with(cx, |buffer, _| {
23304 let snapshot = buffer.snapshot();
23305 let (range, kind) = snapshot.surrounding_word(position, None);
23306 if kind != Some(CharKind::Word) {
23307 return None;
23308 }
23309 Some(
23310 snapshot.anchor_before(range.start)
23311 ..snapshot.anchor_after(range.end),
23312 )
23313 })?
23314 }
23315 })
23316 })
23317 }))
23318 }
23319
23320 fn perform_rename(
23321 &self,
23322 buffer: &Entity<Buffer>,
23323 position: text::Anchor,
23324 new_name: String,
23325 cx: &mut App,
23326 ) -> Option<Task<Result<ProjectTransaction>>> {
23327 Some(self.update(cx, |project, cx| {
23328 project.perform_rename(buffer.clone(), position, new_name, cx)
23329 }))
23330 }
23331}
23332
23333fn inlay_hint_settings(
23334 location: Anchor,
23335 snapshot: &MultiBufferSnapshot,
23336 cx: &mut Context<Editor>,
23337) -> InlayHintSettings {
23338 let file = snapshot.file_at(location);
23339 let language = snapshot.language_at(location).map(|l| l.name());
23340 language_settings(language, file, cx).inlay_hints
23341}
23342
23343fn consume_contiguous_rows(
23344 contiguous_row_selections: &mut Vec<Selection<Point>>,
23345 selection: &Selection<Point>,
23346 display_map: &DisplaySnapshot,
23347 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23348) -> (MultiBufferRow, MultiBufferRow) {
23349 contiguous_row_selections.push(selection.clone());
23350 let start_row = starting_row(selection, display_map);
23351 let mut end_row = ending_row(selection, display_map);
23352
23353 while let Some(next_selection) = selections.peek() {
23354 if next_selection.start.row <= end_row.0 {
23355 end_row = ending_row(next_selection, display_map);
23356 contiguous_row_selections.push(selections.next().unwrap().clone());
23357 } else {
23358 break;
23359 }
23360 }
23361 (start_row, end_row)
23362}
23363
23364fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23365 if selection.start.column > 0 {
23366 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23367 } else {
23368 MultiBufferRow(selection.start.row)
23369 }
23370}
23371
23372fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23373 if next_selection.end.column > 0 || next_selection.is_empty() {
23374 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23375 } else {
23376 MultiBufferRow(next_selection.end.row)
23377 }
23378}
23379
23380impl EditorSnapshot {
23381 pub fn remote_selections_in_range<'a>(
23382 &'a self,
23383 range: &'a Range<Anchor>,
23384 collaboration_hub: &dyn CollaborationHub,
23385 cx: &'a App,
23386 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23387 let participant_names = collaboration_hub.user_names(cx);
23388 let participant_indices = collaboration_hub.user_participant_indices(cx);
23389 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23390 let collaborators_by_replica_id = collaborators_by_peer_id
23391 .values()
23392 .map(|collaborator| (collaborator.replica_id, collaborator))
23393 .collect::<HashMap<_, _>>();
23394 self.buffer_snapshot()
23395 .selections_in_range(range, false)
23396 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23397 if replica_id == AGENT_REPLICA_ID {
23398 Some(RemoteSelection {
23399 replica_id,
23400 selection,
23401 cursor_shape,
23402 line_mode,
23403 collaborator_id: CollaboratorId::Agent,
23404 user_name: Some("Agent".into()),
23405 color: cx.theme().players().agent(),
23406 })
23407 } else {
23408 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23409 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23410 let user_name = participant_names.get(&collaborator.user_id).cloned();
23411 Some(RemoteSelection {
23412 replica_id,
23413 selection,
23414 cursor_shape,
23415 line_mode,
23416 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23417 user_name,
23418 color: if let Some(index) = participant_index {
23419 cx.theme().players().color_for_participant(index.0)
23420 } else {
23421 cx.theme().players().absent()
23422 },
23423 })
23424 }
23425 })
23426 }
23427
23428 pub fn hunks_for_ranges(
23429 &self,
23430 ranges: impl IntoIterator<Item = Range<Point>>,
23431 ) -> Vec<MultiBufferDiffHunk> {
23432 let mut hunks = Vec::new();
23433 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23434 HashMap::default();
23435 for query_range in ranges {
23436 let query_rows =
23437 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23438 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23439 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23440 ) {
23441 // Include deleted hunks that are adjacent to the query range, because
23442 // otherwise they would be missed.
23443 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23444 if hunk.status().is_deleted() {
23445 intersects_range |= hunk.row_range.start == query_rows.end;
23446 intersects_range |= hunk.row_range.end == query_rows.start;
23447 }
23448 if intersects_range {
23449 if !processed_buffer_rows
23450 .entry(hunk.buffer_id)
23451 .or_default()
23452 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23453 {
23454 continue;
23455 }
23456 hunks.push(hunk);
23457 }
23458 }
23459 }
23460
23461 hunks
23462 }
23463
23464 fn display_diff_hunks_for_rows<'a>(
23465 &'a self,
23466 display_rows: Range<DisplayRow>,
23467 folded_buffers: &'a HashSet<BufferId>,
23468 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23469 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23470 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23471
23472 self.buffer_snapshot()
23473 .diff_hunks_in_range(buffer_start..buffer_end)
23474 .filter_map(|hunk| {
23475 if folded_buffers.contains(&hunk.buffer_id) {
23476 return None;
23477 }
23478
23479 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23480 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23481
23482 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23483 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23484
23485 let display_hunk = if hunk_display_start.column() != 0 {
23486 DisplayDiffHunk::Folded {
23487 display_row: hunk_display_start.row(),
23488 }
23489 } else {
23490 let mut end_row = hunk_display_end.row();
23491 if hunk_display_end.column() > 0 {
23492 end_row.0 += 1;
23493 }
23494 let is_created_file = hunk.is_created_file();
23495 DisplayDiffHunk::Unfolded {
23496 status: hunk.status(),
23497 diff_base_byte_range: hunk.diff_base_byte_range,
23498 display_row_range: hunk_display_start.row()..end_row,
23499 multi_buffer_range: Anchor::range_in_buffer(
23500 hunk.excerpt_id,
23501 hunk.buffer_id,
23502 hunk.buffer_range,
23503 ),
23504 is_created_file,
23505 }
23506 };
23507
23508 Some(display_hunk)
23509 })
23510 }
23511
23512 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23513 self.display_snapshot
23514 .buffer_snapshot()
23515 .language_at(position)
23516 }
23517
23518 pub fn is_focused(&self) -> bool {
23519 self.is_focused
23520 }
23521
23522 pub fn placeholder_text(&self) -> Option<String> {
23523 self.placeholder_display_snapshot
23524 .as_ref()
23525 .map(|display_map| display_map.text())
23526 }
23527
23528 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23529 self.scroll_anchor.scroll_position(&self.display_snapshot)
23530 }
23531
23532 fn gutter_dimensions(
23533 &self,
23534 font_id: FontId,
23535 font_size: Pixels,
23536 max_line_number_width: Pixels,
23537 cx: &App,
23538 ) -> Option<GutterDimensions> {
23539 if !self.show_gutter {
23540 return None;
23541 }
23542
23543 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23544 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23545
23546 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23547 matches!(
23548 ProjectSettings::get_global(cx).git.git_gutter,
23549 GitGutterSetting::TrackedFiles
23550 )
23551 });
23552 let gutter_settings = EditorSettings::get_global(cx).gutter;
23553 let show_line_numbers = self
23554 .show_line_numbers
23555 .unwrap_or(gutter_settings.line_numbers);
23556 let line_gutter_width = if show_line_numbers {
23557 // Avoid flicker-like gutter resizes when the line number gains another digit by
23558 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23559 let min_width_for_number_on_gutter =
23560 ch_advance * gutter_settings.min_line_number_digits as f32;
23561 max_line_number_width.max(min_width_for_number_on_gutter)
23562 } else {
23563 0.0.into()
23564 };
23565
23566 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23567 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23568
23569 let git_blame_entries_width =
23570 self.git_blame_gutter_max_author_length
23571 .map(|max_author_length| {
23572 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23573 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23574
23575 /// The number of characters to dedicate to gaps and margins.
23576 const SPACING_WIDTH: usize = 4;
23577
23578 let max_char_count = max_author_length.min(renderer.max_author_length())
23579 + ::git::SHORT_SHA_LENGTH
23580 + MAX_RELATIVE_TIMESTAMP.len()
23581 + SPACING_WIDTH;
23582
23583 ch_advance * max_char_count
23584 });
23585
23586 let is_singleton = self.buffer_snapshot().is_singleton();
23587
23588 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23589 left_padding += if !is_singleton {
23590 ch_width * 4.0
23591 } else if show_runnables || show_breakpoints {
23592 ch_width * 3.0
23593 } else if show_git_gutter && show_line_numbers {
23594 ch_width * 2.0
23595 } else if show_git_gutter || show_line_numbers {
23596 ch_width
23597 } else {
23598 px(0.)
23599 };
23600
23601 let shows_folds = is_singleton && gutter_settings.folds;
23602
23603 let right_padding = if shows_folds && show_line_numbers {
23604 ch_width * 4.0
23605 } else if shows_folds || (!is_singleton && show_line_numbers) {
23606 ch_width * 3.0
23607 } else if show_line_numbers {
23608 ch_width
23609 } else {
23610 px(0.)
23611 };
23612
23613 Some(GutterDimensions {
23614 left_padding,
23615 right_padding,
23616 width: line_gutter_width + left_padding + right_padding,
23617 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23618 git_blame_entries_width,
23619 })
23620 }
23621
23622 pub fn render_crease_toggle(
23623 &self,
23624 buffer_row: MultiBufferRow,
23625 row_contains_cursor: bool,
23626 editor: Entity<Editor>,
23627 window: &mut Window,
23628 cx: &mut App,
23629 ) -> Option<AnyElement> {
23630 let folded = self.is_line_folded(buffer_row);
23631 let mut is_foldable = false;
23632
23633 if let Some(crease) = self
23634 .crease_snapshot
23635 .query_row(buffer_row, self.buffer_snapshot())
23636 {
23637 is_foldable = true;
23638 match crease {
23639 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23640 if let Some(render_toggle) = render_toggle {
23641 let toggle_callback =
23642 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23643 if folded {
23644 editor.update(cx, |editor, cx| {
23645 editor.fold_at(buffer_row, window, cx)
23646 });
23647 } else {
23648 editor.update(cx, |editor, cx| {
23649 editor.unfold_at(buffer_row, window, cx)
23650 });
23651 }
23652 });
23653 return Some((render_toggle)(
23654 buffer_row,
23655 folded,
23656 toggle_callback,
23657 window,
23658 cx,
23659 ));
23660 }
23661 }
23662 }
23663 }
23664
23665 is_foldable |= self.starts_indent(buffer_row);
23666
23667 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23668 Some(
23669 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23670 .toggle_state(folded)
23671 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23672 if folded {
23673 this.unfold_at(buffer_row, window, cx);
23674 } else {
23675 this.fold_at(buffer_row, window, cx);
23676 }
23677 }))
23678 .into_any_element(),
23679 )
23680 } else {
23681 None
23682 }
23683 }
23684
23685 pub fn render_crease_trailer(
23686 &self,
23687 buffer_row: MultiBufferRow,
23688 window: &mut Window,
23689 cx: &mut App,
23690 ) -> Option<AnyElement> {
23691 let folded = self.is_line_folded(buffer_row);
23692 if let Crease::Inline { render_trailer, .. } = self
23693 .crease_snapshot
23694 .query_row(buffer_row, self.buffer_snapshot())?
23695 {
23696 let render_trailer = render_trailer.as_ref()?;
23697 Some(render_trailer(buffer_row, folded, window, cx))
23698 } else {
23699 None
23700 }
23701 }
23702}
23703
23704impl Deref for EditorSnapshot {
23705 type Target = DisplaySnapshot;
23706
23707 fn deref(&self) -> &Self::Target {
23708 &self.display_snapshot
23709 }
23710}
23711
23712#[derive(Clone, Debug, PartialEq, Eq)]
23713pub enum EditorEvent {
23714 InputIgnored {
23715 text: Arc<str>,
23716 },
23717 InputHandled {
23718 utf16_range_to_replace: Option<Range<isize>>,
23719 text: Arc<str>,
23720 },
23721 ExcerptsAdded {
23722 buffer: Entity<Buffer>,
23723 predecessor: ExcerptId,
23724 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23725 },
23726 ExcerptsRemoved {
23727 ids: Vec<ExcerptId>,
23728 removed_buffer_ids: Vec<BufferId>,
23729 },
23730 BufferFoldToggled {
23731 ids: Vec<ExcerptId>,
23732 folded: bool,
23733 },
23734 ExcerptsEdited {
23735 ids: Vec<ExcerptId>,
23736 },
23737 ExcerptsExpanded {
23738 ids: Vec<ExcerptId>,
23739 },
23740 BufferEdited,
23741 Edited {
23742 transaction_id: clock::Lamport,
23743 },
23744 Reparsed(BufferId),
23745 Focused,
23746 FocusedIn,
23747 Blurred,
23748 DirtyChanged,
23749 Saved,
23750 TitleChanged,
23751 SelectionsChanged {
23752 local: bool,
23753 },
23754 ScrollPositionChanged {
23755 local: bool,
23756 autoscroll: bool,
23757 },
23758 TransactionUndone {
23759 transaction_id: clock::Lamport,
23760 },
23761 TransactionBegun {
23762 transaction_id: clock::Lamport,
23763 },
23764 CursorShapeChanged,
23765 BreadcrumbsChanged,
23766 PushedToNavHistory {
23767 anchor: Anchor,
23768 is_deactivate: bool,
23769 },
23770}
23771
23772impl EventEmitter<EditorEvent> for Editor {}
23773
23774impl Focusable for Editor {
23775 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23776 self.focus_handle.clone()
23777 }
23778}
23779
23780impl Render for Editor {
23781 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23782 let settings = ThemeSettings::get_global(cx);
23783
23784 let mut text_style = match self.mode {
23785 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23786 color: cx.theme().colors().editor_foreground,
23787 font_family: settings.ui_font.family.clone(),
23788 font_features: settings.ui_font.features.clone(),
23789 font_fallbacks: settings.ui_font.fallbacks.clone(),
23790 font_size: rems(0.875).into(),
23791 font_weight: settings.ui_font.weight,
23792 line_height: relative(settings.buffer_line_height.value()),
23793 ..Default::default()
23794 },
23795 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23796 color: cx.theme().colors().editor_foreground,
23797 font_family: settings.buffer_font.family.clone(),
23798 font_features: settings.buffer_font.features.clone(),
23799 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23800 font_size: settings.buffer_font_size(cx).into(),
23801 font_weight: settings.buffer_font.weight,
23802 line_height: relative(settings.buffer_line_height.value()),
23803 ..Default::default()
23804 },
23805 };
23806 if let Some(text_style_refinement) = &self.text_style_refinement {
23807 text_style.refine(text_style_refinement)
23808 }
23809
23810 let background = match self.mode {
23811 EditorMode::SingleLine => cx.theme().system().transparent,
23812 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23813 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23814 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23815 };
23816
23817 EditorElement::new(
23818 &cx.entity(),
23819 EditorStyle {
23820 background,
23821 border: cx.theme().colors().border,
23822 local_player: cx.theme().players().local(),
23823 text: text_style,
23824 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23825 syntax: cx.theme().syntax().clone(),
23826 status: cx.theme().status().clone(),
23827 inlay_hints_style: make_inlay_hints_style(cx),
23828 edit_prediction_styles: make_suggestion_styles(cx),
23829 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23830 show_underlines: self.diagnostics_enabled(),
23831 },
23832 )
23833 }
23834}
23835
23836impl EntityInputHandler for Editor {
23837 fn text_for_range(
23838 &mut self,
23839 range_utf16: Range<usize>,
23840 adjusted_range: &mut Option<Range<usize>>,
23841 _: &mut Window,
23842 cx: &mut Context<Self>,
23843 ) -> Option<String> {
23844 let snapshot = self.buffer.read(cx).read(cx);
23845 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23846 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23847 if (start.0..end.0) != range_utf16 {
23848 adjusted_range.replace(start.0..end.0);
23849 }
23850 Some(snapshot.text_for_range(start..end).collect())
23851 }
23852
23853 fn selected_text_range(
23854 &mut self,
23855 ignore_disabled_input: bool,
23856 _: &mut Window,
23857 cx: &mut Context<Self>,
23858 ) -> Option<UTF16Selection> {
23859 // Prevent the IME menu from appearing when holding down an alphabetic key
23860 // while input is disabled.
23861 if !ignore_disabled_input && !self.input_enabled {
23862 return None;
23863 }
23864
23865 let selection = self.selections.newest::<OffsetUtf16>(cx);
23866 let range = selection.range();
23867
23868 Some(UTF16Selection {
23869 range: range.start.0..range.end.0,
23870 reversed: selection.reversed,
23871 })
23872 }
23873
23874 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23875 let snapshot = self.buffer.read(cx).read(cx);
23876 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23877 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23878 }
23879
23880 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23881 self.clear_highlights::<InputComposition>(cx);
23882 self.ime_transaction.take();
23883 }
23884
23885 fn replace_text_in_range(
23886 &mut self,
23887 range_utf16: Option<Range<usize>>,
23888 text: &str,
23889 window: &mut Window,
23890 cx: &mut Context<Self>,
23891 ) {
23892 if !self.input_enabled {
23893 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23894 return;
23895 }
23896
23897 self.transact(window, cx, |this, window, cx| {
23898 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23899 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23900 Some(this.selection_replacement_ranges(range_utf16, cx))
23901 } else {
23902 this.marked_text_ranges(cx)
23903 };
23904
23905 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23906 let newest_selection_id = this.selections.newest_anchor().id;
23907 this.selections
23908 .all::<OffsetUtf16>(cx)
23909 .iter()
23910 .zip(ranges_to_replace.iter())
23911 .find_map(|(selection, range)| {
23912 if selection.id == newest_selection_id {
23913 Some(
23914 (range.start.0 as isize - selection.head().0 as isize)
23915 ..(range.end.0 as isize - selection.head().0 as isize),
23916 )
23917 } else {
23918 None
23919 }
23920 })
23921 });
23922
23923 cx.emit(EditorEvent::InputHandled {
23924 utf16_range_to_replace: range_to_replace,
23925 text: text.into(),
23926 });
23927
23928 if let Some(new_selected_ranges) = new_selected_ranges {
23929 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23930 selections.select_ranges(new_selected_ranges)
23931 });
23932 this.backspace(&Default::default(), window, cx);
23933 }
23934
23935 this.handle_input(text, window, cx);
23936 });
23937
23938 if let Some(transaction) = self.ime_transaction {
23939 self.buffer.update(cx, |buffer, cx| {
23940 buffer.group_until_transaction(transaction, cx);
23941 });
23942 }
23943
23944 self.unmark_text(window, cx);
23945 }
23946
23947 fn replace_and_mark_text_in_range(
23948 &mut self,
23949 range_utf16: Option<Range<usize>>,
23950 text: &str,
23951 new_selected_range_utf16: Option<Range<usize>>,
23952 window: &mut Window,
23953 cx: &mut Context<Self>,
23954 ) {
23955 if !self.input_enabled {
23956 return;
23957 }
23958
23959 let transaction = self.transact(window, cx, |this, window, cx| {
23960 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23961 let snapshot = this.buffer.read(cx).read(cx);
23962 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23963 for marked_range in &mut marked_ranges {
23964 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23965 marked_range.start.0 += relative_range_utf16.start;
23966 marked_range.start =
23967 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23968 marked_range.end =
23969 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23970 }
23971 }
23972 Some(marked_ranges)
23973 } else if let Some(range_utf16) = range_utf16 {
23974 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23975 Some(this.selection_replacement_ranges(range_utf16, cx))
23976 } else {
23977 None
23978 };
23979
23980 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23981 let newest_selection_id = this.selections.newest_anchor().id;
23982 this.selections
23983 .all::<OffsetUtf16>(cx)
23984 .iter()
23985 .zip(ranges_to_replace.iter())
23986 .find_map(|(selection, range)| {
23987 if selection.id == newest_selection_id {
23988 Some(
23989 (range.start.0 as isize - selection.head().0 as isize)
23990 ..(range.end.0 as isize - selection.head().0 as isize),
23991 )
23992 } else {
23993 None
23994 }
23995 })
23996 });
23997
23998 cx.emit(EditorEvent::InputHandled {
23999 utf16_range_to_replace: range_to_replace,
24000 text: text.into(),
24001 });
24002
24003 if let Some(ranges) = ranges_to_replace {
24004 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24005 s.select_ranges(ranges)
24006 });
24007 }
24008
24009 let marked_ranges = {
24010 let snapshot = this.buffer.read(cx).read(cx);
24011 this.selections
24012 .disjoint_anchors_arc()
24013 .iter()
24014 .map(|selection| {
24015 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24016 })
24017 .collect::<Vec<_>>()
24018 };
24019
24020 if text.is_empty() {
24021 this.unmark_text(window, cx);
24022 } else {
24023 this.highlight_text::<InputComposition>(
24024 marked_ranges.clone(),
24025 HighlightStyle {
24026 underline: Some(UnderlineStyle {
24027 thickness: px(1.),
24028 color: None,
24029 wavy: false,
24030 }),
24031 ..Default::default()
24032 },
24033 cx,
24034 );
24035 }
24036
24037 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24038 let use_autoclose = this.use_autoclose;
24039 let use_auto_surround = this.use_auto_surround;
24040 this.set_use_autoclose(false);
24041 this.set_use_auto_surround(false);
24042 this.handle_input(text, window, cx);
24043 this.set_use_autoclose(use_autoclose);
24044 this.set_use_auto_surround(use_auto_surround);
24045
24046 if let Some(new_selected_range) = new_selected_range_utf16 {
24047 let snapshot = this.buffer.read(cx).read(cx);
24048 let new_selected_ranges = marked_ranges
24049 .into_iter()
24050 .map(|marked_range| {
24051 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24052 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
24053 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
24054 snapshot.clip_offset_utf16(new_start, Bias::Left)
24055 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24056 })
24057 .collect::<Vec<_>>();
24058
24059 drop(snapshot);
24060 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24061 selections.select_ranges(new_selected_ranges)
24062 });
24063 }
24064 });
24065
24066 self.ime_transaction = self.ime_transaction.or(transaction);
24067 if let Some(transaction) = self.ime_transaction {
24068 self.buffer.update(cx, |buffer, cx| {
24069 buffer.group_until_transaction(transaction, cx);
24070 });
24071 }
24072
24073 if self.text_highlights::<InputComposition>(cx).is_none() {
24074 self.ime_transaction.take();
24075 }
24076 }
24077
24078 fn bounds_for_range(
24079 &mut self,
24080 range_utf16: Range<usize>,
24081 element_bounds: gpui::Bounds<Pixels>,
24082 window: &mut Window,
24083 cx: &mut Context<Self>,
24084 ) -> Option<gpui::Bounds<Pixels>> {
24085 let text_layout_details = self.text_layout_details(window);
24086 let CharacterDimensions {
24087 em_width,
24088 em_advance,
24089 line_height,
24090 } = self.character_dimensions(window);
24091
24092 let snapshot = self.snapshot(window, cx);
24093 let scroll_position = snapshot.scroll_position();
24094 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24095
24096 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
24097 let x = Pixels::from(
24098 ScrollOffset::from(
24099 snapshot.x_for_display_point(start, &text_layout_details)
24100 + self.gutter_dimensions.full_width(),
24101 ) - scroll_left,
24102 );
24103 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24104
24105 Some(Bounds {
24106 origin: element_bounds.origin + point(x, y),
24107 size: size(em_width, line_height),
24108 })
24109 }
24110
24111 fn character_index_for_point(
24112 &mut self,
24113 point: gpui::Point<Pixels>,
24114 _window: &mut Window,
24115 _cx: &mut Context<Self>,
24116 ) -> Option<usize> {
24117 let position_map = self.last_position_map.as_ref()?;
24118 if !position_map.text_hitbox.contains(&point) {
24119 return None;
24120 }
24121 let display_point = position_map.point_for_position(point).previous_valid;
24122 let anchor = position_map
24123 .snapshot
24124 .display_point_to_anchor(display_point, Bias::Left);
24125 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24126 Some(utf16_offset.0)
24127 }
24128}
24129
24130trait SelectionExt {
24131 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24132 fn spanned_rows(
24133 &self,
24134 include_end_if_at_line_start: bool,
24135 map: &DisplaySnapshot,
24136 ) -> Range<MultiBufferRow>;
24137}
24138
24139impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24140 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24141 let start = self
24142 .start
24143 .to_point(map.buffer_snapshot())
24144 .to_display_point(map);
24145 let end = self
24146 .end
24147 .to_point(map.buffer_snapshot())
24148 .to_display_point(map);
24149 if self.reversed {
24150 end..start
24151 } else {
24152 start..end
24153 }
24154 }
24155
24156 fn spanned_rows(
24157 &self,
24158 include_end_if_at_line_start: bool,
24159 map: &DisplaySnapshot,
24160 ) -> Range<MultiBufferRow> {
24161 let start = self.start.to_point(map.buffer_snapshot());
24162 let mut end = self.end.to_point(map.buffer_snapshot());
24163 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24164 end.row -= 1;
24165 }
24166
24167 let buffer_start = map.prev_line_boundary(start).0;
24168 let buffer_end = map.next_line_boundary(end).0;
24169 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24170 }
24171}
24172
24173impl<T: InvalidationRegion> InvalidationStack<T> {
24174 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24175 where
24176 S: Clone + ToOffset,
24177 {
24178 while let Some(region) = self.last() {
24179 let all_selections_inside_invalidation_ranges =
24180 if selections.len() == region.ranges().len() {
24181 selections
24182 .iter()
24183 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24184 .all(|(selection, invalidation_range)| {
24185 let head = selection.head().to_offset(buffer);
24186 invalidation_range.start <= head && invalidation_range.end >= head
24187 })
24188 } else {
24189 false
24190 };
24191
24192 if all_selections_inside_invalidation_ranges {
24193 break;
24194 } else {
24195 self.pop();
24196 }
24197 }
24198 }
24199}
24200
24201impl<T> Default for InvalidationStack<T> {
24202 fn default() -> Self {
24203 Self(Default::default())
24204 }
24205}
24206
24207impl<T> Deref for InvalidationStack<T> {
24208 type Target = Vec<T>;
24209
24210 fn deref(&self) -> &Self::Target {
24211 &self.0
24212 }
24213}
24214
24215impl<T> DerefMut for InvalidationStack<T> {
24216 fn deref_mut(&mut self) -> &mut Self::Target {
24217 &mut self.0
24218 }
24219}
24220
24221impl InvalidationRegion for SnippetState {
24222 fn ranges(&self) -> &[Range<Anchor>] {
24223 &self.ranges[self.active_index]
24224 }
24225}
24226
24227fn edit_prediction_edit_text(
24228 current_snapshot: &BufferSnapshot,
24229 edits: &[(Range<Anchor>, String)],
24230 edit_preview: &EditPreview,
24231 include_deletions: bool,
24232 cx: &App,
24233) -> HighlightedText {
24234 let edits = edits
24235 .iter()
24236 .map(|(anchor, text)| {
24237 (
24238 anchor.start.text_anchor..anchor.end.text_anchor,
24239 text.clone(),
24240 )
24241 })
24242 .collect::<Vec<_>>();
24243
24244 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24245}
24246
24247fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
24248 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24249 // Just show the raw edit text with basic styling
24250 let mut text = String::new();
24251 let mut highlights = Vec::new();
24252
24253 let insertion_highlight_style = HighlightStyle {
24254 color: Some(cx.theme().colors().text),
24255 ..Default::default()
24256 };
24257
24258 for (_, edit_text) in edits {
24259 let start_offset = text.len();
24260 text.push_str(edit_text);
24261 let end_offset = text.len();
24262
24263 if start_offset < end_offset {
24264 highlights.push((start_offset..end_offset, insertion_highlight_style));
24265 }
24266 }
24267
24268 HighlightedText {
24269 text: text.into(),
24270 highlights,
24271 }
24272}
24273
24274pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24275 match severity {
24276 lsp::DiagnosticSeverity::ERROR => colors.error,
24277 lsp::DiagnosticSeverity::WARNING => colors.warning,
24278 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24279 lsp::DiagnosticSeverity::HINT => colors.info,
24280 _ => colors.ignored,
24281 }
24282}
24283
24284pub fn styled_runs_for_code_label<'a>(
24285 label: &'a CodeLabel,
24286 syntax_theme: &'a theme::SyntaxTheme,
24287) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24288 let fade_out = HighlightStyle {
24289 fade_out: Some(0.35),
24290 ..Default::default()
24291 };
24292
24293 let mut prev_end = label.filter_range.end;
24294 label
24295 .runs
24296 .iter()
24297 .enumerate()
24298 .flat_map(move |(ix, (range, highlight_id))| {
24299 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24300 style
24301 } else {
24302 return Default::default();
24303 };
24304 let muted_style = style.highlight(fade_out);
24305
24306 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24307 if range.start >= label.filter_range.end {
24308 if range.start > prev_end {
24309 runs.push((prev_end..range.start, fade_out));
24310 }
24311 runs.push((range.clone(), muted_style));
24312 } else if range.end <= label.filter_range.end {
24313 runs.push((range.clone(), style));
24314 } else {
24315 runs.push((range.start..label.filter_range.end, style));
24316 runs.push((label.filter_range.end..range.end, muted_style));
24317 }
24318 prev_end = cmp::max(prev_end, range.end);
24319
24320 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24321 runs.push((prev_end..label.text.len(), fade_out));
24322 }
24323
24324 runs
24325 })
24326}
24327
24328pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24329 let mut prev_index = 0;
24330 let mut prev_codepoint: Option<char> = None;
24331 text.char_indices()
24332 .chain([(text.len(), '\0')])
24333 .filter_map(move |(index, codepoint)| {
24334 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24335 let is_boundary = index == text.len()
24336 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24337 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24338 if is_boundary {
24339 let chunk = &text[prev_index..index];
24340 prev_index = index;
24341 Some(chunk)
24342 } else {
24343 None
24344 }
24345 })
24346}
24347
24348pub trait RangeToAnchorExt: Sized {
24349 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24350
24351 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24352 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24353 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24354 }
24355}
24356
24357impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24358 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24359 let start_offset = self.start.to_offset(snapshot);
24360 let end_offset = self.end.to_offset(snapshot);
24361 if start_offset == end_offset {
24362 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24363 } else {
24364 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24365 }
24366 }
24367}
24368
24369pub trait RowExt {
24370 fn as_f64(&self) -> f64;
24371
24372 fn next_row(&self) -> Self;
24373
24374 fn previous_row(&self) -> Self;
24375
24376 fn minus(&self, other: Self) -> u32;
24377}
24378
24379impl RowExt for DisplayRow {
24380 fn as_f64(&self) -> f64 {
24381 self.0 as _
24382 }
24383
24384 fn next_row(&self) -> Self {
24385 Self(self.0 + 1)
24386 }
24387
24388 fn previous_row(&self) -> Self {
24389 Self(self.0.saturating_sub(1))
24390 }
24391
24392 fn minus(&self, other: Self) -> u32 {
24393 self.0 - other.0
24394 }
24395}
24396
24397impl RowExt for MultiBufferRow {
24398 fn as_f64(&self) -> f64 {
24399 self.0 as _
24400 }
24401
24402 fn next_row(&self) -> Self {
24403 Self(self.0 + 1)
24404 }
24405
24406 fn previous_row(&self) -> Self {
24407 Self(self.0.saturating_sub(1))
24408 }
24409
24410 fn minus(&self, other: Self) -> u32 {
24411 self.0 - other.0
24412 }
24413}
24414
24415trait RowRangeExt {
24416 type Row;
24417
24418 fn len(&self) -> usize;
24419
24420 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24421}
24422
24423impl RowRangeExt for Range<MultiBufferRow> {
24424 type Row = MultiBufferRow;
24425
24426 fn len(&self) -> usize {
24427 (self.end.0 - self.start.0) as usize
24428 }
24429
24430 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24431 (self.start.0..self.end.0).map(MultiBufferRow)
24432 }
24433}
24434
24435impl RowRangeExt for Range<DisplayRow> {
24436 type Row = DisplayRow;
24437
24438 fn len(&self) -> usize {
24439 (self.end.0 - self.start.0) as usize
24440 }
24441
24442 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24443 (self.start.0..self.end.0).map(DisplayRow)
24444 }
24445}
24446
24447/// If select range has more than one line, we
24448/// just point the cursor to range.start.
24449fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24450 if range.start.row == range.end.row {
24451 range
24452 } else {
24453 range.start..range.start
24454 }
24455}
24456pub struct KillRing(ClipboardItem);
24457impl Global for KillRing {}
24458
24459const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24460
24461enum BreakpointPromptEditAction {
24462 Log,
24463 Condition,
24464 HitCondition,
24465}
24466
24467struct BreakpointPromptEditor {
24468 pub(crate) prompt: Entity<Editor>,
24469 editor: WeakEntity<Editor>,
24470 breakpoint_anchor: Anchor,
24471 breakpoint: Breakpoint,
24472 edit_action: BreakpointPromptEditAction,
24473 block_ids: HashSet<CustomBlockId>,
24474 editor_margins: Arc<Mutex<EditorMargins>>,
24475 _subscriptions: Vec<Subscription>,
24476}
24477
24478impl BreakpointPromptEditor {
24479 const MAX_LINES: u8 = 4;
24480
24481 fn new(
24482 editor: WeakEntity<Editor>,
24483 breakpoint_anchor: Anchor,
24484 breakpoint: Breakpoint,
24485 edit_action: BreakpointPromptEditAction,
24486 window: &mut Window,
24487 cx: &mut Context<Self>,
24488 ) -> Self {
24489 let base_text = match edit_action {
24490 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24491 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24492 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24493 }
24494 .map(|msg| msg.to_string())
24495 .unwrap_or_default();
24496
24497 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24498 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24499
24500 let prompt = cx.new(|cx| {
24501 let mut prompt = Editor::new(
24502 EditorMode::AutoHeight {
24503 min_lines: 1,
24504 max_lines: Some(Self::MAX_LINES as usize),
24505 },
24506 buffer,
24507 None,
24508 window,
24509 cx,
24510 );
24511 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24512 prompt.set_show_cursor_when_unfocused(false, cx);
24513 prompt.set_placeholder_text(
24514 match edit_action {
24515 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24516 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24517 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24518 },
24519 window,
24520 cx,
24521 );
24522
24523 prompt
24524 });
24525
24526 Self {
24527 prompt,
24528 editor,
24529 breakpoint_anchor,
24530 breakpoint,
24531 edit_action,
24532 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24533 block_ids: Default::default(),
24534 _subscriptions: vec![],
24535 }
24536 }
24537
24538 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24539 self.block_ids.extend(block_ids)
24540 }
24541
24542 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24543 if let Some(editor) = self.editor.upgrade() {
24544 let message = self
24545 .prompt
24546 .read(cx)
24547 .buffer
24548 .read(cx)
24549 .as_singleton()
24550 .expect("A multi buffer in breakpoint prompt isn't possible")
24551 .read(cx)
24552 .as_rope()
24553 .to_string();
24554
24555 editor.update(cx, |editor, cx| {
24556 editor.edit_breakpoint_at_anchor(
24557 self.breakpoint_anchor,
24558 self.breakpoint.clone(),
24559 match self.edit_action {
24560 BreakpointPromptEditAction::Log => {
24561 BreakpointEditAction::EditLogMessage(message.into())
24562 }
24563 BreakpointPromptEditAction::Condition => {
24564 BreakpointEditAction::EditCondition(message.into())
24565 }
24566 BreakpointPromptEditAction::HitCondition => {
24567 BreakpointEditAction::EditHitCondition(message.into())
24568 }
24569 },
24570 cx,
24571 );
24572
24573 editor.remove_blocks(self.block_ids.clone(), None, cx);
24574 cx.focus_self(window);
24575 });
24576 }
24577 }
24578
24579 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24580 self.editor
24581 .update(cx, |editor, cx| {
24582 editor.remove_blocks(self.block_ids.clone(), None, cx);
24583 window.focus(&editor.focus_handle);
24584 })
24585 .log_err();
24586 }
24587
24588 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24589 let settings = ThemeSettings::get_global(cx);
24590 let text_style = TextStyle {
24591 color: if self.prompt.read(cx).read_only(cx) {
24592 cx.theme().colors().text_disabled
24593 } else {
24594 cx.theme().colors().text
24595 },
24596 font_family: settings.buffer_font.family.clone(),
24597 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24598 font_size: settings.buffer_font_size(cx).into(),
24599 font_weight: settings.buffer_font.weight,
24600 line_height: relative(settings.buffer_line_height.value()),
24601 ..Default::default()
24602 };
24603 EditorElement::new(
24604 &self.prompt,
24605 EditorStyle {
24606 background: cx.theme().colors().editor_background,
24607 local_player: cx.theme().players().local(),
24608 text: text_style,
24609 ..Default::default()
24610 },
24611 )
24612 }
24613}
24614
24615impl Render for BreakpointPromptEditor {
24616 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24617 let editor_margins = *self.editor_margins.lock();
24618 let gutter_dimensions = editor_margins.gutter;
24619 h_flex()
24620 .key_context("Editor")
24621 .bg(cx.theme().colors().editor_background)
24622 .border_y_1()
24623 .border_color(cx.theme().status().info_border)
24624 .size_full()
24625 .py(window.line_height() / 2.5)
24626 .on_action(cx.listener(Self::confirm))
24627 .on_action(cx.listener(Self::cancel))
24628 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24629 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24630 }
24631}
24632
24633impl Focusable for BreakpointPromptEditor {
24634 fn focus_handle(&self, cx: &App) -> FocusHandle {
24635 self.prompt.focus_handle(cx)
24636 }
24637}
24638
24639fn all_edits_insertions_or_deletions(
24640 edits: &Vec<(Range<Anchor>, String)>,
24641 snapshot: &MultiBufferSnapshot,
24642) -> bool {
24643 let mut all_insertions = true;
24644 let mut all_deletions = true;
24645
24646 for (range, new_text) in edits.iter() {
24647 let range_is_empty = range.to_offset(snapshot).is_empty();
24648 let text_is_empty = new_text.is_empty();
24649
24650 if range_is_empty != text_is_empty {
24651 if range_is_empty {
24652 all_deletions = false;
24653 } else {
24654 all_insertions = false;
24655 }
24656 } else {
24657 return false;
24658 }
24659
24660 if !all_insertions && !all_deletions {
24661 return false;
24662 }
24663 }
24664 all_insertions || all_deletions
24665}
24666
24667struct MissingEditPredictionKeybindingTooltip;
24668
24669impl Render for MissingEditPredictionKeybindingTooltip {
24670 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24671 ui::tooltip_container(cx, |container, cx| {
24672 container
24673 .flex_shrink_0()
24674 .max_w_80()
24675 .min_h(rems_from_px(124.))
24676 .justify_between()
24677 .child(
24678 v_flex()
24679 .flex_1()
24680 .text_ui_sm(cx)
24681 .child(Label::new("Conflict with Accept Keybinding"))
24682 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24683 )
24684 .child(
24685 h_flex()
24686 .pb_1()
24687 .gap_1()
24688 .items_end()
24689 .w_full()
24690 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24691 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
24692 }))
24693 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24694 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24695 })),
24696 )
24697 })
24698 }
24699}
24700
24701#[derive(Debug, Clone, Copy, PartialEq)]
24702pub struct LineHighlight {
24703 pub background: Background,
24704 pub border: Option<gpui::Hsla>,
24705 pub include_gutter: bool,
24706 pub type_id: Option<TypeId>,
24707}
24708
24709struct LineManipulationResult {
24710 pub new_text: String,
24711 pub line_count_before: usize,
24712 pub line_count_after: usize,
24713}
24714
24715fn render_diff_hunk_controls(
24716 row: u32,
24717 status: &DiffHunkStatus,
24718 hunk_range: Range<Anchor>,
24719 is_created_file: bool,
24720 line_height: Pixels,
24721 editor: &Entity<Editor>,
24722 _window: &mut Window,
24723 cx: &mut App,
24724) -> AnyElement {
24725 h_flex()
24726 .h(line_height)
24727 .mr_1()
24728 .gap_1()
24729 .px_0p5()
24730 .pb_1()
24731 .border_x_1()
24732 .border_b_1()
24733 .border_color(cx.theme().colors().border_variant)
24734 .rounded_b_lg()
24735 .bg(cx.theme().colors().editor_background)
24736 .gap_1()
24737 .block_mouse_except_scroll()
24738 .shadow_md()
24739 .child(if status.has_secondary_hunk() {
24740 Button::new(("stage", row as u64), "Stage")
24741 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24742 .tooltip({
24743 let focus_handle = editor.focus_handle(cx);
24744 move |window, cx| {
24745 Tooltip::for_action_in(
24746 "Stage Hunk",
24747 &::git::ToggleStaged,
24748 &focus_handle,
24749 window,
24750 cx,
24751 )
24752 }
24753 })
24754 .on_click({
24755 let editor = editor.clone();
24756 move |_event, _window, cx| {
24757 editor.update(cx, |editor, cx| {
24758 editor.stage_or_unstage_diff_hunks(
24759 true,
24760 vec![hunk_range.start..hunk_range.start],
24761 cx,
24762 );
24763 });
24764 }
24765 })
24766 } else {
24767 Button::new(("unstage", row as u64), "Unstage")
24768 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24769 .tooltip({
24770 let focus_handle = editor.focus_handle(cx);
24771 move |window, cx| {
24772 Tooltip::for_action_in(
24773 "Unstage Hunk",
24774 &::git::ToggleStaged,
24775 &focus_handle,
24776 window,
24777 cx,
24778 )
24779 }
24780 })
24781 .on_click({
24782 let editor = editor.clone();
24783 move |_event, _window, cx| {
24784 editor.update(cx, |editor, cx| {
24785 editor.stage_or_unstage_diff_hunks(
24786 false,
24787 vec![hunk_range.start..hunk_range.start],
24788 cx,
24789 );
24790 });
24791 }
24792 })
24793 })
24794 .child(
24795 Button::new(("restore", row as u64), "Restore")
24796 .tooltip({
24797 let focus_handle = editor.focus_handle(cx);
24798 move |window, cx| {
24799 Tooltip::for_action_in(
24800 "Restore Hunk",
24801 &::git::Restore,
24802 &focus_handle,
24803 window,
24804 cx,
24805 )
24806 }
24807 })
24808 .on_click({
24809 let editor = editor.clone();
24810 move |_event, window, cx| {
24811 editor.update(cx, |editor, cx| {
24812 let snapshot = editor.snapshot(window, cx);
24813 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
24814 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24815 });
24816 }
24817 })
24818 .disabled(is_created_file),
24819 )
24820 .when(
24821 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24822 |el| {
24823 el.child(
24824 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24825 .shape(IconButtonShape::Square)
24826 .icon_size(IconSize::Small)
24827 // .disabled(!has_multiple_hunks)
24828 .tooltip({
24829 let focus_handle = editor.focus_handle(cx);
24830 move |window, cx| {
24831 Tooltip::for_action_in(
24832 "Next Hunk",
24833 &GoToHunk,
24834 &focus_handle,
24835 window,
24836 cx,
24837 )
24838 }
24839 })
24840 .on_click({
24841 let editor = editor.clone();
24842 move |_event, window, cx| {
24843 editor.update(cx, |editor, cx| {
24844 let snapshot = editor.snapshot(window, cx);
24845 let position =
24846 hunk_range.end.to_point(&snapshot.buffer_snapshot());
24847 editor.go_to_hunk_before_or_after_position(
24848 &snapshot,
24849 position,
24850 Direction::Next,
24851 window,
24852 cx,
24853 );
24854 editor.expand_selected_diff_hunks(cx);
24855 });
24856 }
24857 }),
24858 )
24859 .child(
24860 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24861 .shape(IconButtonShape::Square)
24862 .icon_size(IconSize::Small)
24863 // .disabled(!has_multiple_hunks)
24864 .tooltip({
24865 let focus_handle = editor.focus_handle(cx);
24866 move |window, cx| {
24867 Tooltip::for_action_in(
24868 "Previous Hunk",
24869 &GoToPreviousHunk,
24870 &focus_handle,
24871 window,
24872 cx,
24873 )
24874 }
24875 })
24876 .on_click({
24877 let editor = editor.clone();
24878 move |_event, window, cx| {
24879 editor.update(cx, |editor, cx| {
24880 let snapshot = editor.snapshot(window, cx);
24881 let point =
24882 hunk_range.start.to_point(&snapshot.buffer_snapshot());
24883 editor.go_to_hunk_before_or_after_position(
24884 &snapshot,
24885 point,
24886 Direction::Prev,
24887 window,
24888 cx,
24889 );
24890 editor.expand_selected_diff_hunks(cx);
24891 });
24892 }
24893 }),
24894 )
24895 },
24896 )
24897 .into_any_element()
24898}
24899
24900pub fn multibuffer_context_lines(cx: &App) -> u32 {
24901 EditorSettings::try_get(cx)
24902 .map(|settings| settings.excerpt_context_lines)
24903 .unwrap_or(2)
24904 .min(32)
24905}