1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod element;
22mod git;
23mod highlight_matching_bracket;
24mod hover_links;
25pub mod hover_popover;
26mod indent_guides;
27mod inlay_hint_cache;
28pub mod items;
29mod jsx_tag_auto_close;
30mod linked_editing_ranges;
31mod lsp_colors;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod edit_prediction_tests;
46#[cfg(test)]
47mod editor_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
54pub use edit_prediction::Direction;
55pub use editor_settings::{
56 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
57 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
58};
59pub use element::{
60 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
61};
62pub use git::blame::BlameRenderer;
63pub use hover_popover::hover_markdown_style;
64pub use items::MAX_TAB_TITLE_LEN;
65pub use lsp::CompletionContext;
66pub use lsp_ext::lsp_tasks;
67pub use multi_buffer::{
68 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
69 RowInfo, ToOffset, ToPoint,
70};
71pub use proposed_changes_editor::{
72 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
73};
74pub use text::Bias;
75
76use ::git::{
77 Restore,
78 blame::{BlameEntry, ParsedCommitMessage},
79};
80use aho_corasick::AhoCorasick;
81use anyhow::{Context as _, Result, anyhow};
82use blink_manager::BlinkManager;
83use buffer_diff::DiffHunkStatus;
84use client::{Collaborator, ParticipantIndex, parse_zed_link};
85use clock::{AGENT_REPLICA_ID, ReplicaId};
86use code_context_menus::{
87 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
88 CompletionsMenu, ContextMenuOrigin,
89};
90use collections::{BTreeMap, HashMap, HashSet, VecDeque};
91use convert_case::{Case, Casing};
92use dap::TelemetrySpawnLocation;
93use display_map::*;
94use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
95use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
96use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
97use futures::{
98 FutureExt, StreamExt as _,
99 future::{self, Shared, join},
100 stream::FuturesUnordered,
101};
102use fuzzy::{StringMatch, StringMatchCandidate};
103use git::blame::{GitBlame, GlobalBlameRenderer};
104use gpui::{
105 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
106 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
107 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
108 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
109 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
110 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
111 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
112 div, point, prelude::*, pulsating_between, px, relative, size,
113};
114use highlight_matching_bracket::refresh_matching_bracket_highlights;
115use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
116use hover_popover::{HoverState, hide_hover};
117use indent_guides::ActiveIndentGuidesState;
118use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
119use itertools::{Either, Itertools};
120use language::{
121 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
122 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
123 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
124 IndentSize, Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal,
125 TextObject, TransactionId, TreeSitterOptions, WordsQuery,
126 language_settings::{
127 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
128 all_language_settings, language_settings,
129 },
130 point_from_lsp, point_to_lsp, text_diff_with_options,
131};
132use linked_editing_ranges::refresh_linked_ranges;
133use lsp::{
134 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
135 LanguageServerId,
136};
137use lsp_colors::LspColorData;
138use markdown::Markdown;
139use mouse_context_menu::MouseContextMenu;
140use movement::TextLayoutDetails;
141use multi_buffer::{
142 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
143 ToOffsetUtf16,
144};
145use parking_lot::Mutex;
146use persistence::DB;
147use project::{
148 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
149 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint,
150 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectPath,
151 ProjectTransaction, TaskSourceKind,
152 debugger::{
153 breakpoint_store::{
154 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
155 BreakpointStore, BreakpointStoreEvent,
156 },
157 session::{Session, SessionEvent},
158 },
159 git_store::{GitStoreEvent, RepositoryEvent},
160 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
161 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
162};
163use rand::seq::SliceRandom;
164use rpc::{ErrorCode, ErrorExt, proto::PeerId};
165use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
166use selections_collection::{
167 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
168};
169use serde::{Deserialize, Serialize};
170use settings::{GitGutterSetting, Settings, SettingsLocation, SettingsStore, update_settings_file};
171use smallvec::{SmallVec, smallvec};
172use snippet::Snippet;
173use std::{
174 any::{Any, TypeId},
175 borrow::Cow,
176 cell::{OnceCell, RefCell},
177 cmp::{self, Ordering, Reverse},
178 iter::{self, Peekable},
179 mem,
180 num::NonZeroU32,
181 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
182 path::{Path, PathBuf},
183 rc::Rc,
184 sync::Arc,
185 time::{Duration, Instant},
186};
187use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
188use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
189use theme::{
190 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
191 observe_buffer_font_size_adjustment,
192};
193use ui::{
194 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
195 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
196};
197use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
198use workspace::{
199 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
200 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
201 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
202 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
203 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
204 searchable::SearchEvent,
205};
206
207use crate::{
208 code_context_menus::CompletionsMenuSource,
209 editor_settings::MultiCursorModifier,
210 hover_links::{find_url, find_url_from_range},
211 scroll::{ScrollOffset, ScrollPixelOffset},
212 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
213};
214
215pub const FILE_HEADER_HEIGHT: u32 = 2;
216pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
217const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
218const MAX_LINE_LEN: usize = 1024;
219const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
220const MAX_SELECTION_HISTORY_LEN: usize = 1024;
221pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
222#[doc(hidden)]
223pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
224pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
225
226pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
227pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
228pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
229pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
230
231pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
232pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
233pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
234
235pub type RenderDiffHunkControlsFn = Arc<
236 dyn Fn(
237 u32,
238 &DiffHunkStatus,
239 Range<Anchor>,
240 bool,
241 Pixels,
242 &Entity<Editor>,
243 &mut Window,
244 &mut App,
245 ) -> AnyElement,
246>;
247
248enum ReportEditorEvent {
249 Saved { auto_saved: bool },
250 EditorOpened,
251 Closed,
252}
253
254impl ReportEditorEvent {
255 pub fn event_type(&self) -> &'static str {
256 match self {
257 Self::Saved { .. } => "Editor Saved",
258 Self::EditorOpened => "Editor Opened",
259 Self::Closed => "Editor Closed",
260 }
261 }
262}
263
264struct InlineValueCache {
265 enabled: bool,
266 inlays: Vec<InlayId>,
267 refresh_task: Task<Option<()>>,
268}
269
270impl InlineValueCache {
271 fn new(enabled: bool) -> Self {
272 Self {
273 enabled,
274 inlays: Vec::new(),
275 refresh_task: Task::ready(None),
276 }
277 }
278}
279
280#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
281pub enum InlayId {
282 EditPrediction(u32),
283 DebuggerValue(u32),
284 // LSP
285 Hint(u32),
286 Color(u32),
287}
288
289impl InlayId {
290 fn id(&self) -> u32 {
291 match self {
292 Self::EditPrediction(id) => *id,
293 Self::DebuggerValue(id) => *id,
294 Self::Hint(id) => *id,
295 Self::Color(id) => *id,
296 }
297 }
298}
299
300pub enum ActiveDebugLine {}
301pub enum DebugStackFrameLine {}
302enum DocumentHighlightRead {}
303enum DocumentHighlightWrite {}
304enum InputComposition {}
305pub enum PendingInput {}
306enum SelectedTextHighlight {}
307
308pub enum ConflictsOuter {}
309pub enum ConflictsOurs {}
310pub enum ConflictsTheirs {}
311pub enum ConflictsOursMarker {}
312pub enum ConflictsTheirsMarker {}
313
314#[derive(Debug, Copy, Clone, PartialEq, Eq)]
315pub enum Navigated {
316 Yes,
317 No,
318}
319
320impl Navigated {
321 pub fn from_bool(yes: bool) -> Navigated {
322 if yes { Navigated::Yes } else { Navigated::No }
323 }
324}
325
326#[derive(Debug, Clone, PartialEq, Eq)]
327enum DisplayDiffHunk {
328 Folded {
329 display_row: DisplayRow,
330 },
331 Unfolded {
332 is_created_file: bool,
333 diff_base_byte_range: Range<usize>,
334 display_row_range: Range<DisplayRow>,
335 multi_buffer_range: Range<Anchor>,
336 status: DiffHunkStatus,
337 },
338}
339
340pub enum HideMouseCursorOrigin {
341 TypingAction,
342 MovementAction,
343}
344
345pub fn init_settings(cx: &mut App) {
346 EditorSettings::register(cx);
347}
348
349pub fn init(cx: &mut App) {
350 init_settings(cx);
351
352 cx.set_global(GlobalBlameRenderer(Arc::new(())));
353
354 workspace::register_project_item::<Editor>(cx);
355 workspace::FollowableViewRegistry::register::<Editor>(cx);
356 workspace::register_serializable_item::<Editor>(cx);
357
358 cx.observe_new(
359 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
360 workspace.register_action(Editor::new_file);
361 workspace.register_action(Editor::new_file_vertical);
362 workspace.register_action(Editor::new_file_horizontal);
363 workspace.register_action(Editor::cancel_language_server_work);
364 workspace.register_action(Editor::toggle_focus);
365 },
366 )
367 .detach();
368
369 cx.on_action(move |_: &workspace::NewFile, cx| {
370 let app_state = workspace::AppState::global(cx);
371 if let Some(app_state) = app_state.upgrade() {
372 workspace::open_new(
373 Default::default(),
374 app_state,
375 cx,
376 |workspace, window, cx| {
377 Editor::new_file(workspace, &Default::default(), window, cx)
378 },
379 )
380 .detach();
381 }
382 });
383 cx.on_action(move |_: &workspace::NewWindow, cx| {
384 let app_state = workspace::AppState::global(cx);
385 if let Some(app_state) = app_state.upgrade() {
386 workspace::open_new(
387 Default::default(),
388 app_state,
389 cx,
390 |workspace, window, cx| {
391 cx.activate(true);
392 Editor::new_file(workspace, &Default::default(), window, cx)
393 },
394 )
395 .detach();
396 }
397 });
398}
399
400pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
401 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
402}
403
404pub trait DiagnosticRenderer {
405 fn render_group(
406 &self,
407 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
408 buffer_id: BufferId,
409 snapshot: EditorSnapshot,
410 editor: WeakEntity<Editor>,
411 cx: &mut App,
412 ) -> Vec<BlockProperties<Anchor>>;
413
414 fn render_hover(
415 &self,
416 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
417 range: Range<Point>,
418 buffer_id: BufferId,
419 cx: &mut App,
420 ) -> Option<Entity<markdown::Markdown>>;
421
422 fn open_link(
423 &self,
424 editor: &mut Editor,
425 link: SharedString,
426 window: &mut Window,
427 cx: &mut Context<Editor>,
428 );
429}
430
431pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
432
433impl GlobalDiagnosticRenderer {
434 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
435 cx.try_global::<Self>().map(|g| g.0.clone())
436 }
437}
438
439impl gpui::Global for GlobalDiagnosticRenderer {}
440pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
441 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
442}
443
444pub struct SearchWithinRange;
445
446trait InvalidationRegion {
447 fn ranges(&self) -> &[Range<Anchor>];
448}
449
450#[derive(Clone, Debug, PartialEq)]
451pub enum SelectPhase {
452 Begin {
453 position: DisplayPoint,
454 add: bool,
455 click_count: usize,
456 },
457 BeginColumnar {
458 position: DisplayPoint,
459 reset: bool,
460 mode: ColumnarMode,
461 goal_column: u32,
462 },
463 Extend {
464 position: DisplayPoint,
465 click_count: usize,
466 },
467 Update {
468 position: DisplayPoint,
469 goal_column: u32,
470 scroll_delta: gpui::Point<f32>,
471 },
472 End,
473}
474
475#[derive(Clone, Debug, PartialEq)]
476pub enum ColumnarMode {
477 FromMouse,
478 FromSelection,
479}
480
481#[derive(Clone, Debug)]
482pub enum SelectMode {
483 Character,
484 Word(Range<Anchor>),
485 Line(Range<Anchor>),
486 All,
487}
488
489#[derive(Clone, PartialEq, Eq, Debug)]
490pub enum EditorMode {
491 SingleLine,
492 AutoHeight {
493 min_lines: usize,
494 max_lines: Option<usize>,
495 },
496 Full {
497 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
498 scale_ui_elements_with_buffer_font_size: bool,
499 /// When set to `true`, the editor will render a background for the active line.
500 show_active_line_background: bool,
501 /// When set to `true`, the editor's height will be determined by its content.
502 sized_by_content: bool,
503 },
504 Minimap {
505 parent: WeakEntity<Editor>,
506 },
507}
508
509impl EditorMode {
510 pub fn full() -> Self {
511 Self::Full {
512 scale_ui_elements_with_buffer_font_size: true,
513 show_active_line_background: true,
514 sized_by_content: false,
515 }
516 }
517
518 #[inline]
519 pub fn is_full(&self) -> bool {
520 matches!(self, Self::Full { .. })
521 }
522
523 #[inline]
524 pub fn is_single_line(&self) -> bool {
525 matches!(self, Self::SingleLine { .. })
526 }
527
528 #[inline]
529 fn is_minimap(&self) -> bool {
530 matches!(self, Self::Minimap { .. })
531 }
532}
533
534#[derive(Copy, Clone, Debug)]
535pub enum SoftWrap {
536 /// Prefer not to wrap at all.
537 ///
538 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
539 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
540 GitDiff,
541 /// Prefer a single line generally, unless an overly long line is encountered.
542 None,
543 /// Soft wrap lines that exceed the editor width.
544 EditorWidth,
545 /// Soft wrap lines at the preferred line length.
546 Column(u32),
547 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
548 Bounded(u32),
549}
550
551#[derive(Clone)]
552pub struct EditorStyle {
553 pub background: Hsla,
554 pub border: Hsla,
555 pub local_player: PlayerColor,
556 pub text: TextStyle,
557 pub scrollbar_width: Pixels,
558 pub syntax: Arc<SyntaxTheme>,
559 pub status: StatusColors,
560 pub inlay_hints_style: HighlightStyle,
561 pub edit_prediction_styles: EditPredictionStyles,
562 pub unnecessary_code_fade: f32,
563 pub show_underlines: bool,
564}
565
566impl Default for EditorStyle {
567 fn default() -> Self {
568 Self {
569 background: Hsla::default(),
570 border: Hsla::default(),
571 local_player: PlayerColor::default(),
572 text: TextStyle::default(),
573 scrollbar_width: Pixels::default(),
574 syntax: Default::default(),
575 // HACK: Status colors don't have a real default.
576 // We should look into removing the status colors from the editor
577 // style and retrieve them directly from the theme.
578 status: StatusColors::dark(),
579 inlay_hints_style: HighlightStyle::default(),
580 edit_prediction_styles: EditPredictionStyles {
581 insertion: HighlightStyle::default(),
582 whitespace: HighlightStyle::default(),
583 },
584 unnecessary_code_fade: Default::default(),
585 show_underlines: true,
586 }
587 }
588}
589
590pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
591 let show_background = language_settings::language_settings(None, None, cx)
592 .inlay_hints
593 .show_background;
594
595 let mut style = cx.theme().syntax().get("hint");
596
597 if style.color.is_none() {
598 style.color = Some(cx.theme().status().hint);
599 }
600
601 if !show_background {
602 style.background_color = None;
603 return style;
604 }
605
606 if style.background_color.is_none() {
607 style.background_color = Some(cx.theme().status().hint_background);
608 }
609
610 style
611}
612
613pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
614 EditPredictionStyles {
615 insertion: HighlightStyle {
616 color: Some(cx.theme().status().predictive),
617 ..HighlightStyle::default()
618 },
619 whitespace: HighlightStyle {
620 background_color: Some(cx.theme().status().created_background),
621 ..HighlightStyle::default()
622 },
623 }
624}
625
626type CompletionId = usize;
627
628pub(crate) enum EditDisplayMode {
629 TabAccept,
630 DiffPopover,
631 Inline,
632}
633
634enum EditPrediction {
635 Edit {
636 edits: Vec<(Range<Anchor>, String)>,
637 edit_preview: Option<EditPreview>,
638 display_mode: EditDisplayMode,
639 snapshot: BufferSnapshot,
640 },
641 /// Move to a specific location in the active editor
642 MoveWithin {
643 target: Anchor,
644 snapshot: BufferSnapshot,
645 },
646 /// Move to a specific location in a different editor (not the active one)
647 MoveOutside {
648 target: language::Anchor,
649 snapshot: BufferSnapshot,
650 },
651}
652
653struct EditPredictionState {
654 inlay_ids: Vec<InlayId>,
655 completion: EditPrediction,
656 completion_id: Option<SharedString>,
657 invalidation_range: Option<Range<Anchor>>,
658}
659
660enum EditPredictionSettings {
661 Disabled,
662 Enabled {
663 show_in_menu: bool,
664 preview_requires_modifier: bool,
665 },
666}
667
668enum EditPredictionHighlight {}
669
670#[derive(Debug, Clone)]
671struct InlineDiagnostic {
672 message: SharedString,
673 group_id: usize,
674 is_primary: bool,
675 start: Point,
676 severity: lsp::DiagnosticSeverity,
677}
678
679pub enum MenuEditPredictionsPolicy {
680 Never,
681 ByProvider,
682}
683
684pub enum EditPredictionPreview {
685 /// Modifier is not pressed
686 Inactive { released_too_fast: bool },
687 /// Modifier pressed
688 Active {
689 since: Instant,
690 previous_scroll_position: Option<ScrollAnchor>,
691 },
692}
693
694impl EditPredictionPreview {
695 pub fn released_too_fast(&self) -> bool {
696 match self {
697 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
698 EditPredictionPreview::Active { .. } => false,
699 }
700 }
701
702 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
703 if let EditPredictionPreview::Active {
704 previous_scroll_position,
705 ..
706 } = self
707 {
708 *previous_scroll_position = scroll_position;
709 }
710 }
711}
712
713pub struct ContextMenuOptions {
714 pub min_entries_visible: usize,
715 pub max_entries_visible: usize,
716 pub placement: Option<ContextMenuPlacement>,
717}
718
719#[derive(Debug, Clone, PartialEq, Eq)]
720pub enum ContextMenuPlacement {
721 Above,
722 Below,
723}
724
725#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
726struct EditorActionId(usize);
727
728impl EditorActionId {
729 pub fn post_inc(&mut self) -> Self {
730 let answer = self.0;
731
732 *self = Self(answer + 1);
733
734 Self(answer)
735 }
736}
737
738// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
739// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
740
741type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
742type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
743
744#[derive(Default)]
745struct ScrollbarMarkerState {
746 scrollbar_size: Size<Pixels>,
747 dirty: bool,
748 markers: Arc<[PaintQuad]>,
749 pending_refresh: Option<Task<Result<()>>>,
750}
751
752impl ScrollbarMarkerState {
753 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
754 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
755 }
756}
757
758#[derive(Clone, Copy, PartialEq, Eq)]
759pub enum MinimapVisibility {
760 Disabled,
761 Enabled {
762 /// The configuration currently present in the users settings.
763 setting_configuration: bool,
764 /// Whether to override the currently set visibility from the users setting.
765 toggle_override: bool,
766 },
767}
768
769impl MinimapVisibility {
770 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
771 if mode.is_full() {
772 Self::Enabled {
773 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
774 toggle_override: false,
775 }
776 } else {
777 Self::Disabled
778 }
779 }
780
781 fn hidden(&self) -> Self {
782 match *self {
783 Self::Enabled {
784 setting_configuration,
785 ..
786 } => Self::Enabled {
787 setting_configuration,
788 toggle_override: setting_configuration,
789 },
790 Self::Disabled => Self::Disabled,
791 }
792 }
793
794 fn disabled(&self) -> bool {
795 matches!(*self, Self::Disabled)
796 }
797
798 fn settings_visibility(&self) -> bool {
799 match *self {
800 Self::Enabled {
801 setting_configuration,
802 ..
803 } => setting_configuration,
804 _ => false,
805 }
806 }
807
808 fn visible(&self) -> bool {
809 match *self {
810 Self::Enabled {
811 setting_configuration,
812 toggle_override,
813 } => setting_configuration ^ toggle_override,
814 _ => false,
815 }
816 }
817
818 fn toggle_visibility(&self) -> Self {
819 match *self {
820 Self::Enabled {
821 toggle_override,
822 setting_configuration,
823 } => Self::Enabled {
824 setting_configuration,
825 toggle_override: !toggle_override,
826 },
827 Self::Disabled => Self::Disabled,
828 }
829 }
830}
831
832#[derive(Clone, Debug)]
833struct RunnableTasks {
834 templates: Vec<(TaskSourceKind, TaskTemplate)>,
835 offset: multi_buffer::Anchor,
836 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
837 column: u32,
838 // Values of all named captures, including those starting with '_'
839 extra_variables: HashMap<String, String>,
840 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
841 context_range: Range<BufferOffset>,
842}
843
844impl RunnableTasks {
845 fn resolve<'a>(
846 &'a self,
847 cx: &'a task::TaskContext,
848 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
849 self.templates.iter().filter_map(|(kind, template)| {
850 template
851 .resolve_task(&kind.to_id_base(), cx)
852 .map(|task| (kind.clone(), task))
853 })
854 }
855}
856
857#[derive(Clone)]
858pub struct ResolvedTasks {
859 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
860 position: Anchor,
861}
862
863#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
864struct BufferOffset(usize);
865
866/// Addons allow storing per-editor state in other crates (e.g. Vim)
867pub trait Addon: 'static {
868 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
869
870 fn render_buffer_header_controls(
871 &self,
872 _: &ExcerptInfo,
873 _: &Window,
874 _: &App,
875 ) -> Option<AnyElement> {
876 None
877 }
878
879 fn to_any(&self) -> &dyn std::any::Any;
880
881 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
882 None
883 }
884}
885
886struct ChangeLocation {
887 current: Option<Vec<Anchor>>,
888 original: Vec<Anchor>,
889}
890impl ChangeLocation {
891 fn locations(&self) -> &[Anchor] {
892 self.current.as_ref().unwrap_or(&self.original)
893 }
894}
895
896/// A set of caret positions, registered when the editor was edited.
897pub struct ChangeList {
898 changes: Vec<ChangeLocation>,
899 /// Currently "selected" change.
900 position: Option<usize>,
901}
902
903impl ChangeList {
904 pub fn new() -> Self {
905 Self {
906 changes: Vec::new(),
907 position: None,
908 }
909 }
910
911 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
912 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
913 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
914 if self.changes.is_empty() {
915 return None;
916 }
917
918 let prev = self.position.unwrap_or(self.changes.len());
919 let next = if direction == Direction::Prev {
920 prev.saturating_sub(count)
921 } else {
922 (prev + count).min(self.changes.len() - 1)
923 };
924 self.position = Some(next);
925 self.changes.get(next).map(|change| change.locations())
926 }
927
928 /// Adds a new change to the list, resetting the change list position.
929 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
930 self.position.take();
931 if let Some(last) = self.changes.last_mut()
932 && group
933 {
934 last.current = Some(new_positions)
935 } else {
936 self.changes.push(ChangeLocation {
937 original: new_positions,
938 current: None,
939 });
940 }
941 }
942
943 pub fn last(&self) -> Option<&[Anchor]> {
944 self.changes.last().map(|change| change.locations())
945 }
946
947 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
948 self.changes.last().map(|change| change.original.as_slice())
949 }
950
951 pub fn invert_last_group(&mut self) {
952 if let Some(last) = self.changes.last_mut()
953 && let Some(current) = last.current.as_mut()
954 {
955 mem::swap(&mut last.original, current);
956 }
957 }
958}
959
960#[derive(Clone)]
961struct InlineBlamePopoverState {
962 scroll_handle: ScrollHandle,
963 commit_message: Option<ParsedCommitMessage>,
964 markdown: Entity<Markdown>,
965}
966
967struct InlineBlamePopover {
968 position: gpui::Point<Pixels>,
969 hide_task: Option<Task<()>>,
970 popover_bounds: Option<Bounds<Pixels>>,
971 popover_state: InlineBlamePopoverState,
972 keyboard_grace: bool,
973}
974
975enum SelectionDragState {
976 /// State when no drag related activity is detected.
977 None,
978 /// State when the mouse is down on a selection that is about to be dragged.
979 ReadyToDrag {
980 selection: Selection<Anchor>,
981 click_position: gpui::Point<Pixels>,
982 mouse_down_time: Instant,
983 },
984 /// State when the mouse is dragging the selection in the editor.
985 Dragging {
986 selection: Selection<Anchor>,
987 drop_cursor: Selection<Anchor>,
988 hide_drop_cursor: bool,
989 },
990}
991
992enum ColumnarSelectionState {
993 FromMouse {
994 selection_tail: Anchor,
995 display_point: Option<DisplayPoint>,
996 },
997 FromSelection {
998 selection_tail: Anchor,
999 },
1000}
1001
1002/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1003/// a breakpoint on them.
1004#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1005struct PhantomBreakpointIndicator {
1006 display_row: DisplayRow,
1007 /// There's a small debounce between hovering over the line and showing the indicator.
1008 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1009 is_active: bool,
1010 collides_with_existing_breakpoint: bool,
1011}
1012
1013/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1014///
1015/// See the [module level documentation](self) for more information.
1016pub struct Editor {
1017 focus_handle: FocusHandle,
1018 last_focused_descendant: Option<WeakFocusHandle>,
1019 /// The text buffer being edited
1020 buffer: Entity<MultiBuffer>,
1021 /// Map of how text in the buffer should be displayed.
1022 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1023 pub display_map: Entity<DisplayMap>,
1024 placeholder_display_map: Option<Entity<DisplayMap>>,
1025 pub selections: SelectionsCollection,
1026 pub scroll_manager: ScrollManager,
1027 /// When inline assist editors are linked, they all render cursors because
1028 /// typing enters text into each of them, even the ones that aren't focused.
1029 pub(crate) show_cursor_when_unfocused: bool,
1030 columnar_selection_state: Option<ColumnarSelectionState>,
1031 add_selections_state: Option<AddSelectionsState>,
1032 select_next_state: Option<SelectNextState>,
1033 select_prev_state: Option<SelectNextState>,
1034 selection_history: SelectionHistory,
1035 defer_selection_effects: bool,
1036 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1037 autoclose_regions: Vec<AutocloseRegion>,
1038 snippet_stack: InvalidationStack<SnippetState>,
1039 select_syntax_node_history: SelectSyntaxNodeHistory,
1040 ime_transaction: Option<TransactionId>,
1041 pub diagnostics_max_severity: DiagnosticSeverity,
1042 active_diagnostics: ActiveDiagnostic,
1043 show_inline_diagnostics: bool,
1044 inline_diagnostics_update: Task<()>,
1045 inline_diagnostics_enabled: bool,
1046 diagnostics_enabled: bool,
1047 word_completions_enabled: bool,
1048 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1049 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1050 hard_wrap: Option<usize>,
1051 project: Option<Entity<Project>>,
1052 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1053 completion_provider: Option<Rc<dyn CompletionProvider>>,
1054 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1055 blink_manager: Entity<BlinkManager>,
1056 show_cursor_names: bool,
1057 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1058 pub show_local_selections: bool,
1059 mode: EditorMode,
1060 show_breadcrumbs: bool,
1061 show_gutter: bool,
1062 show_scrollbars: ScrollbarAxes,
1063 minimap_visibility: MinimapVisibility,
1064 offset_content: bool,
1065 disable_expand_excerpt_buttons: bool,
1066 show_line_numbers: Option<bool>,
1067 use_relative_line_numbers: Option<bool>,
1068 show_git_diff_gutter: Option<bool>,
1069 show_code_actions: Option<bool>,
1070 show_runnables: Option<bool>,
1071 show_breakpoints: Option<bool>,
1072 show_wrap_guides: Option<bool>,
1073 show_indent_guides: Option<bool>,
1074 highlight_order: usize,
1075 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1076 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1077 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1078 scrollbar_marker_state: ScrollbarMarkerState,
1079 active_indent_guides_state: ActiveIndentGuidesState,
1080 nav_history: Option<ItemNavHistory>,
1081 context_menu: RefCell<Option<CodeContextMenu>>,
1082 context_menu_options: Option<ContextMenuOptions>,
1083 mouse_context_menu: Option<MouseContextMenu>,
1084 completion_tasks: Vec<(CompletionId, Task<()>)>,
1085 inline_blame_popover: Option<InlineBlamePopover>,
1086 inline_blame_popover_show_task: Option<Task<()>>,
1087 signature_help_state: SignatureHelpState,
1088 auto_signature_help: Option<bool>,
1089 find_all_references_task_sources: Vec<Anchor>,
1090 next_completion_id: CompletionId,
1091 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1092 code_actions_task: Option<Task<Result<()>>>,
1093 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1094 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1095 document_highlights_task: Option<Task<()>>,
1096 linked_editing_range_task: Option<Task<Option<()>>>,
1097 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1098 pending_rename: Option<RenameState>,
1099 searchable: bool,
1100 cursor_shape: CursorShape,
1101 current_line_highlight: Option<CurrentLineHighlight>,
1102 collapse_matches: bool,
1103 autoindent_mode: Option<AutoindentMode>,
1104 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1105 input_enabled: bool,
1106 use_modal_editing: bool,
1107 read_only: bool,
1108 leader_id: Option<CollaboratorId>,
1109 remote_id: Option<ViewId>,
1110 pub hover_state: HoverState,
1111 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1112 gutter_hovered: bool,
1113 hovered_link_state: Option<HoveredLinkState>,
1114 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1115 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1116 active_edit_prediction: Option<EditPredictionState>,
1117 /// Used to prevent flickering as the user types while the menu is open
1118 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1119 edit_prediction_settings: EditPredictionSettings,
1120 edit_predictions_hidden_for_vim_mode: bool,
1121 show_edit_predictions_override: Option<bool>,
1122 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1123 edit_prediction_preview: EditPredictionPreview,
1124 edit_prediction_indent_conflict: bool,
1125 edit_prediction_requires_modifier_in_indent_conflict: bool,
1126 inlay_hint_cache: InlayHintCache,
1127 next_inlay_id: u32,
1128 next_color_inlay_id: u32,
1129 _subscriptions: Vec<Subscription>,
1130 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1131 gutter_dimensions: GutterDimensions,
1132 style: Option<EditorStyle>,
1133 text_style_refinement: Option<TextStyleRefinement>,
1134 next_editor_action_id: EditorActionId,
1135 editor_actions: Rc<
1136 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1137 >,
1138 use_autoclose: bool,
1139 use_auto_surround: bool,
1140 auto_replace_emoji_shortcode: bool,
1141 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1142 show_git_blame_gutter: bool,
1143 show_git_blame_inline: bool,
1144 show_git_blame_inline_delay_task: Option<Task<()>>,
1145 git_blame_inline_enabled: bool,
1146 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1147 serialize_dirty_buffers: bool,
1148 show_selection_menu: Option<bool>,
1149 blame: Option<Entity<GitBlame>>,
1150 blame_subscription: Option<Subscription>,
1151 custom_context_menu: Option<
1152 Box<
1153 dyn 'static
1154 + Fn(
1155 &mut Self,
1156 DisplayPoint,
1157 &mut Window,
1158 &mut Context<Self>,
1159 ) -> Option<Entity<ui::ContextMenu>>,
1160 >,
1161 >,
1162 last_bounds: Option<Bounds<Pixels>>,
1163 last_position_map: Option<Rc<PositionMap>>,
1164 expect_bounds_change: Option<Bounds<Pixels>>,
1165 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1166 tasks_update_task: Option<Task<()>>,
1167 breakpoint_store: Option<Entity<BreakpointStore>>,
1168 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1169 hovered_diff_hunk_row: Option<DisplayRow>,
1170 pull_diagnostics_task: Task<()>,
1171 in_project_search: bool,
1172 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1173 breadcrumb_header: Option<String>,
1174 focused_block: Option<FocusedBlock>,
1175 next_scroll_position: NextScrollCursorCenterTopBottom,
1176 addons: HashMap<TypeId, Box<dyn Addon>>,
1177 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1178 load_diff_task: Option<Shared<Task<()>>>,
1179 /// Whether we are temporarily displaying a diff other than git's
1180 temporary_diff_override: bool,
1181 selection_mark_mode: bool,
1182 toggle_fold_multiple_buffers: Task<()>,
1183 _scroll_cursor_center_top_bottom_task: Task<()>,
1184 serialize_selections: Task<()>,
1185 serialize_folds: Task<()>,
1186 mouse_cursor_hidden: bool,
1187 minimap: Option<Entity<Self>>,
1188 hide_mouse_mode: HideMouseMode,
1189 pub change_list: ChangeList,
1190 inline_value_cache: InlineValueCache,
1191 selection_drag_state: SelectionDragState,
1192 colors: Option<LspColorData>,
1193 post_scroll_update: Task<()>,
1194 refresh_colors_task: Task<()>,
1195 folding_newlines: Task<()>,
1196 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1197}
1198
1199#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1200enum NextScrollCursorCenterTopBottom {
1201 #[default]
1202 Center,
1203 Top,
1204 Bottom,
1205}
1206
1207impl NextScrollCursorCenterTopBottom {
1208 fn next(&self) -> Self {
1209 match self {
1210 Self::Center => Self::Top,
1211 Self::Top => Self::Bottom,
1212 Self::Bottom => Self::Center,
1213 }
1214 }
1215}
1216
1217#[derive(Clone)]
1218pub struct EditorSnapshot {
1219 pub mode: EditorMode,
1220 show_gutter: bool,
1221 show_line_numbers: Option<bool>,
1222 show_git_diff_gutter: Option<bool>,
1223 show_code_actions: Option<bool>,
1224 show_runnables: Option<bool>,
1225 show_breakpoints: Option<bool>,
1226 git_blame_gutter_max_author_length: Option<usize>,
1227 pub display_snapshot: DisplaySnapshot,
1228 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1229 is_focused: bool,
1230 scroll_anchor: ScrollAnchor,
1231 ongoing_scroll: OngoingScroll,
1232 current_line_highlight: CurrentLineHighlight,
1233 gutter_hovered: bool,
1234}
1235
1236#[derive(Default, Debug, Clone, Copy)]
1237pub struct GutterDimensions {
1238 pub left_padding: Pixels,
1239 pub right_padding: Pixels,
1240 pub width: Pixels,
1241 pub margin: Pixels,
1242 pub git_blame_entries_width: Option<Pixels>,
1243}
1244
1245impl GutterDimensions {
1246 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1247 Self {
1248 margin: Self::default_gutter_margin(font_id, font_size, cx),
1249 ..Default::default()
1250 }
1251 }
1252
1253 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1254 -cx.text_system().descent(font_id, font_size)
1255 }
1256 /// The full width of the space taken up by the gutter.
1257 pub fn full_width(&self) -> Pixels {
1258 self.margin + self.width
1259 }
1260
1261 /// The width of the space reserved for the fold indicators,
1262 /// use alongside 'justify_end' and `gutter_width` to
1263 /// right align content with the line numbers
1264 pub fn fold_area_width(&self) -> Pixels {
1265 self.margin + self.right_padding
1266 }
1267}
1268
1269struct CharacterDimensions {
1270 em_width: Pixels,
1271 em_advance: Pixels,
1272 line_height: Pixels,
1273}
1274
1275#[derive(Debug)]
1276pub struct RemoteSelection {
1277 pub replica_id: ReplicaId,
1278 pub selection: Selection<Anchor>,
1279 pub cursor_shape: CursorShape,
1280 pub collaborator_id: CollaboratorId,
1281 pub line_mode: bool,
1282 pub user_name: Option<SharedString>,
1283 pub color: PlayerColor,
1284}
1285
1286#[derive(Clone, Debug)]
1287struct SelectionHistoryEntry {
1288 selections: Arc<[Selection<Anchor>]>,
1289 select_next_state: Option<SelectNextState>,
1290 select_prev_state: Option<SelectNextState>,
1291 add_selections_state: Option<AddSelectionsState>,
1292}
1293
1294#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1295enum SelectionHistoryMode {
1296 Normal,
1297 Undoing,
1298 Redoing,
1299 Skipping,
1300}
1301
1302#[derive(Clone, PartialEq, Eq, Hash)]
1303struct HoveredCursor {
1304 replica_id: u16,
1305 selection_id: usize,
1306}
1307
1308impl Default for SelectionHistoryMode {
1309 fn default() -> Self {
1310 Self::Normal
1311 }
1312}
1313
1314#[derive(Debug)]
1315/// SelectionEffects controls the side-effects of updating the selection.
1316///
1317/// The default behaviour does "what you mostly want":
1318/// - it pushes to the nav history if the cursor moved by >10 lines
1319/// - it re-triggers completion requests
1320/// - it scrolls to fit
1321///
1322/// You might want to modify these behaviours. For example when doing a "jump"
1323/// like go to definition, we always want to add to nav history; but when scrolling
1324/// in vim mode we never do.
1325///
1326/// Similarly, you might want to disable scrolling if you don't want the viewport to
1327/// move.
1328#[derive(Clone)]
1329pub struct SelectionEffects {
1330 nav_history: Option<bool>,
1331 completions: bool,
1332 scroll: Option<Autoscroll>,
1333}
1334
1335impl Default for SelectionEffects {
1336 fn default() -> Self {
1337 Self {
1338 nav_history: None,
1339 completions: true,
1340 scroll: Some(Autoscroll::fit()),
1341 }
1342 }
1343}
1344impl SelectionEffects {
1345 pub fn scroll(scroll: Autoscroll) -> Self {
1346 Self {
1347 scroll: Some(scroll),
1348 ..Default::default()
1349 }
1350 }
1351
1352 pub fn no_scroll() -> Self {
1353 Self {
1354 scroll: None,
1355 ..Default::default()
1356 }
1357 }
1358
1359 pub fn completions(self, completions: bool) -> Self {
1360 Self {
1361 completions,
1362 ..self
1363 }
1364 }
1365
1366 pub fn nav_history(self, nav_history: bool) -> Self {
1367 Self {
1368 nav_history: Some(nav_history),
1369 ..self
1370 }
1371 }
1372}
1373
1374struct DeferredSelectionEffectsState {
1375 changed: bool,
1376 effects: SelectionEffects,
1377 old_cursor_position: Anchor,
1378 history_entry: SelectionHistoryEntry,
1379}
1380
1381#[derive(Default)]
1382struct SelectionHistory {
1383 #[allow(clippy::type_complexity)]
1384 selections_by_transaction:
1385 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1386 mode: SelectionHistoryMode,
1387 undo_stack: VecDeque<SelectionHistoryEntry>,
1388 redo_stack: VecDeque<SelectionHistoryEntry>,
1389}
1390
1391impl SelectionHistory {
1392 #[track_caller]
1393 fn insert_transaction(
1394 &mut self,
1395 transaction_id: TransactionId,
1396 selections: Arc<[Selection<Anchor>]>,
1397 ) {
1398 if selections.is_empty() {
1399 log::error!(
1400 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1401 std::panic::Location::caller()
1402 );
1403 return;
1404 }
1405 self.selections_by_transaction
1406 .insert(transaction_id, (selections, None));
1407 }
1408
1409 #[allow(clippy::type_complexity)]
1410 fn transaction(
1411 &self,
1412 transaction_id: TransactionId,
1413 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1414 self.selections_by_transaction.get(&transaction_id)
1415 }
1416
1417 #[allow(clippy::type_complexity)]
1418 fn transaction_mut(
1419 &mut self,
1420 transaction_id: TransactionId,
1421 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1422 self.selections_by_transaction.get_mut(&transaction_id)
1423 }
1424
1425 fn push(&mut self, entry: SelectionHistoryEntry) {
1426 if !entry.selections.is_empty() {
1427 match self.mode {
1428 SelectionHistoryMode::Normal => {
1429 self.push_undo(entry);
1430 self.redo_stack.clear();
1431 }
1432 SelectionHistoryMode::Undoing => self.push_redo(entry),
1433 SelectionHistoryMode::Redoing => self.push_undo(entry),
1434 SelectionHistoryMode::Skipping => {}
1435 }
1436 }
1437 }
1438
1439 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1440 if self
1441 .undo_stack
1442 .back()
1443 .is_none_or(|e| e.selections != entry.selections)
1444 {
1445 self.undo_stack.push_back(entry);
1446 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1447 self.undo_stack.pop_front();
1448 }
1449 }
1450 }
1451
1452 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1453 if self
1454 .redo_stack
1455 .back()
1456 .is_none_or(|e| e.selections != entry.selections)
1457 {
1458 self.redo_stack.push_back(entry);
1459 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1460 self.redo_stack.pop_front();
1461 }
1462 }
1463 }
1464}
1465
1466#[derive(Clone, Copy)]
1467pub struct RowHighlightOptions {
1468 pub autoscroll: bool,
1469 pub include_gutter: bool,
1470}
1471
1472impl Default for RowHighlightOptions {
1473 fn default() -> Self {
1474 Self {
1475 autoscroll: Default::default(),
1476 include_gutter: true,
1477 }
1478 }
1479}
1480
1481struct RowHighlight {
1482 index: usize,
1483 range: Range<Anchor>,
1484 color: Hsla,
1485 options: RowHighlightOptions,
1486 type_id: TypeId,
1487}
1488
1489#[derive(Clone, Debug)]
1490struct AddSelectionsState {
1491 groups: Vec<AddSelectionsGroup>,
1492}
1493
1494#[derive(Clone, Debug)]
1495struct AddSelectionsGroup {
1496 above: bool,
1497 stack: Vec<usize>,
1498}
1499
1500#[derive(Clone)]
1501struct SelectNextState {
1502 query: AhoCorasick,
1503 wordwise: bool,
1504 done: bool,
1505}
1506
1507impl std::fmt::Debug for SelectNextState {
1508 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1509 f.debug_struct(std::any::type_name::<Self>())
1510 .field("wordwise", &self.wordwise)
1511 .field("done", &self.done)
1512 .finish()
1513 }
1514}
1515
1516#[derive(Debug)]
1517struct AutocloseRegion {
1518 selection_id: usize,
1519 range: Range<Anchor>,
1520 pair: BracketPair,
1521}
1522
1523#[derive(Debug)]
1524struct SnippetState {
1525 ranges: Vec<Vec<Range<Anchor>>>,
1526 active_index: usize,
1527 choices: Vec<Option<Vec<String>>>,
1528}
1529
1530#[doc(hidden)]
1531pub struct RenameState {
1532 pub range: Range<Anchor>,
1533 pub old_name: Arc<str>,
1534 pub editor: Entity<Editor>,
1535 block_id: CustomBlockId,
1536}
1537
1538struct InvalidationStack<T>(Vec<T>);
1539
1540struct RegisteredEditPredictionProvider {
1541 provider: Arc<dyn EditPredictionProviderHandle>,
1542 _subscription: Subscription,
1543}
1544
1545#[derive(Debug, PartialEq, Eq)]
1546pub struct ActiveDiagnosticGroup {
1547 pub active_range: Range<Anchor>,
1548 pub active_message: String,
1549 pub group_id: usize,
1550 pub blocks: HashSet<CustomBlockId>,
1551}
1552
1553#[derive(Debug, PartialEq, Eq)]
1554
1555pub(crate) enum ActiveDiagnostic {
1556 None,
1557 All,
1558 Group(ActiveDiagnosticGroup),
1559}
1560
1561#[derive(Serialize, Deserialize, Clone, Debug)]
1562pub struct ClipboardSelection {
1563 /// The number of bytes in this selection.
1564 pub len: usize,
1565 /// Whether this was a full-line selection.
1566 pub is_entire_line: bool,
1567 /// The indentation of the first line when this content was originally copied.
1568 pub first_line_indent: u32,
1569}
1570
1571// selections, scroll behavior, was newest selection reversed
1572type SelectSyntaxNodeHistoryState = (
1573 Box<[Selection<usize>]>,
1574 SelectSyntaxNodeScrollBehavior,
1575 bool,
1576);
1577
1578#[derive(Default)]
1579struct SelectSyntaxNodeHistory {
1580 stack: Vec<SelectSyntaxNodeHistoryState>,
1581 // disable temporarily to allow changing selections without losing the stack
1582 pub disable_clearing: bool,
1583}
1584
1585impl SelectSyntaxNodeHistory {
1586 pub fn try_clear(&mut self) {
1587 if !self.disable_clearing {
1588 self.stack.clear();
1589 }
1590 }
1591
1592 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1593 self.stack.push(selection);
1594 }
1595
1596 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1597 self.stack.pop()
1598 }
1599}
1600
1601enum SelectSyntaxNodeScrollBehavior {
1602 CursorTop,
1603 FitSelection,
1604 CursorBottom,
1605}
1606
1607#[derive(Debug)]
1608pub(crate) struct NavigationData {
1609 cursor_anchor: Anchor,
1610 cursor_position: Point,
1611 scroll_anchor: ScrollAnchor,
1612 scroll_top_row: u32,
1613}
1614
1615#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1616pub enum GotoDefinitionKind {
1617 Symbol,
1618 Declaration,
1619 Type,
1620 Implementation,
1621}
1622
1623#[derive(Debug, Clone)]
1624enum InlayHintRefreshReason {
1625 ModifiersChanged(bool),
1626 Toggle(bool),
1627 SettingsChange(InlayHintSettings),
1628 NewLinesShown,
1629 BufferEdited(HashSet<Arc<Language>>),
1630 RefreshRequested,
1631 ExcerptsRemoved(Vec<ExcerptId>),
1632}
1633
1634impl InlayHintRefreshReason {
1635 fn description(&self) -> &'static str {
1636 match self {
1637 Self::ModifiersChanged(_) => "modifiers changed",
1638 Self::Toggle(_) => "toggle",
1639 Self::SettingsChange(_) => "settings change",
1640 Self::NewLinesShown => "new lines shown",
1641 Self::BufferEdited(_) => "buffer edited",
1642 Self::RefreshRequested => "refresh requested",
1643 Self::ExcerptsRemoved(_) => "excerpts removed",
1644 }
1645 }
1646}
1647
1648pub enum FormatTarget {
1649 Buffers(HashSet<Entity<Buffer>>),
1650 Ranges(Vec<Range<MultiBufferPoint>>),
1651}
1652
1653pub(crate) struct FocusedBlock {
1654 id: BlockId,
1655 focus_handle: WeakFocusHandle,
1656}
1657
1658#[derive(Clone)]
1659enum JumpData {
1660 MultiBufferRow {
1661 row: MultiBufferRow,
1662 line_offset_from_top: u32,
1663 },
1664 MultiBufferPoint {
1665 excerpt_id: ExcerptId,
1666 position: Point,
1667 anchor: text::Anchor,
1668 line_offset_from_top: u32,
1669 },
1670}
1671
1672pub enum MultibufferSelectionMode {
1673 First,
1674 All,
1675}
1676
1677#[derive(Clone, Copy, Debug, Default)]
1678pub struct RewrapOptions {
1679 pub override_language_settings: bool,
1680 pub preserve_existing_whitespace: bool,
1681}
1682
1683impl Editor {
1684 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1685 let buffer = cx.new(|cx| Buffer::local("", cx));
1686 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1687 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1688 }
1689
1690 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1691 let buffer = cx.new(|cx| Buffer::local("", cx));
1692 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1693 Self::new(EditorMode::full(), buffer, None, window, cx)
1694 }
1695
1696 pub fn auto_height(
1697 min_lines: usize,
1698 max_lines: usize,
1699 window: &mut Window,
1700 cx: &mut Context<Self>,
1701 ) -> Self {
1702 let buffer = cx.new(|cx| Buffer::local("", cx));
1703 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1704 Self::new(
1705 EditorMode::AutoHeight {
1706 min_lines,
1707 max_lines: Some(max_lines),
1708 },
1709 buffer,
1710 None,
1711 window,
1712 cx,
1713 )
1714 }
1715
1716 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1717 /// The editor grows as tall as needed to fit its content.
1718 pub fn auto_height_unbounded(
1719 min_lines: usize,
1720 window: &mut Window,
1721 cx: &mut Context<Self>,
1722 ) -> Self {
1723 let buffer = cx.new(|cx| Buffer::local("", cx));
1724 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1725 Self::new(
1726 EditorMode::AutoHeight {
1727 min_lines,
1728 max_lines: None,
1729 },
1730 buffer,
1731 None,
1732 window,
1733 cx,
1734 )
1735 }
1736
1737 pub fn for_buffer(
1738 buffer: Entity<Buffer>,
1739 project: Option<Entity<Project>>,
1740 window: &mut Window,
1741 cx: &mut Context<Self>,
1742 ) -> Self {
1743 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1744 Self::new(EditorMode::full(), buffer, project, window, cx)
1745 }
1746
1747 pub fn for_multibuffer(
1748 buffer: Entity<MultiBuffer>,
1749 project: Option<Entity<Project>>,
1750 window: &mut Window,
1751 cx: &mut Context<Self>,
1752 ) -> Self {
1753 Self::new(EditorMode::full(), buffer, project, window, cx)
1754 }
1755
1756 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1757 let mut clone = Self::new(
1758 self.mode.clone(),
1759 self.buffer.clone(),
1760 self.project.clone(),
1761 window,
1762 cx,
1763 );
1764 self.display_map.update(cx, |display_map, cx| {
1765 let snapshot = display_map.snapshot(cx);
1766 clone.display_map.update(cx, |display_map, cx| {
1767 display_map.set_state(&snapshot, cx);
1768 });
1769 });
1770 clone.folds_did_change(cx);
1771 clone.selections.clone_state(&self.selections);
1772 clone.scroll_manager.clone_state(&self.scroll_manager);
1773 clone.searchable = self.searchable;
1774 clone.read_only = self.read_only;
1775 clone
1776 }
1777
1778 pub fn new(
1779 mode: EditorMode,
1780 buffer: Entity<MultiBuffer>,
1781 project: Option<Entity<Project>>,
1782 window: &mut Window,
1783 cx: &mut Context<Self>,
1784 ) -> Self {
1785 Editor::new_internal(mode, buffer, project, None, window, cx)
1786 }
1787
1788 fn new_internal(
1789 mode: EditorMode,
1790 multi_buffer: Entity<MultiBuffer>,
1791 project: Option<Entity<Project>>,
1792 display_map: Option<Entity<DisplayMap>>,
1793 window: &mut Window,
1794 cx: &mut Context<Self>,
1795 ) -> Self {
1796 debug_assert!(
1797 display_map.is_none() || mode.is_minimap(),
1798 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1799 );
1800
1801 let full_mode = mode.is_full();
1802 let is_minimap = mode.is_minimap();
1803 let diagnostics_max_severity = if full_mode {
1804 EditorSettings::get_global(cx)
1805 .diagnostics_max_severity
1806 .unwrap_or(DiagnosticSeverity::Hint)
1807 } else {
1808 DiagnosticSeverity::Off
1809 };
1810 let style = window.text_style();
1811 let font_size = style.font_size.to_pixels(window.rem_size());
1812 let editor = cx.entity().downgrade();
1813 let fold_placeholder = FoldPlaceholder {
1814 constrain_width: false,
1815 render: Arc::new(move |fold_id, fold_range, cx| {
1816 let editor = editor.clone();
1817 div()
1818 .id(fold_id)
1819 .bg(cx.theme().colors().ghost_element_background)
1820 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1821 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1822 .rounded_xs()
1823 .size_full()
1824 .cursor_pointer()
1825 .child("⋯")
1826 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1827 .on_click(move |_, _window, cx| {
1828 editor
1829 .update(cx, |editor, cx| {
1830 editor.unfold_ranges(
1831 &[fold_range.start..fold_range.end],
1832 true,
1833 false,
1834 cx,
1835 );
1836 cx.stop_propagation();
1837 })
1838 .ok();
1839 })
1840 .into_any()
1841 }),
1842 merge_adjacent: true,
1843 ..FoldPlaceholder::default()
1844 };
1845 let display_map = display_map.unwrap_or_else(|| {
1846 cx.new(|cx| {
1847 DisplayMap::new(
1848 multi_buffer.clone(),
1849 style.font(),
1850 font_size,
1851 None,
1852 FILE_HEADER_HEIGHT,
1853 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1854 fold_placeholder,
1855 diagnostics_max_severity,
1856 cx,
1857 )
1858 })
1859 });
1860
1861 let selections = SelectionsCollection::new(display_map.clone(), multi_buffer.clone());
1862
1863 let blink_manager = cx.new(|cx| {
1864 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1865 if is_minimap {
1866 blink_manager.disable(cx);
1867 }
1868 blink_manager
1869 });
1870
1871 let soft_wrap_mode_override =
1872 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1873
1874 let mut project_subscriptions = Vec::new();
1875 if full_mode && let Some(project) = project.as_ref() {
1876 project_subscriptions.push(cx.subscribe_in(
1877 project,
1878 window,
1879 |editor, _, event, window, cx| match event {
1880 project::Event::RefreshCodeLens => {
1881 // we always query lens with actions, without storing them, always refreshing them
1882 }
1883 project::Event::RefreshInlayHints => {
1884 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1885 }
1886 project::Event::LanguageServerAdded(..)
1887 | project::Event::LanguageServerRemoved(..) => {
1888 if editor.tasks_update_task.is_none() {
1889 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1890 }
1891 }
1892 project::Event::SnippetEdit(id, snippet_edits) => {
1893 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1894 let focus_handle = editor.focus_handle(cx);
1895 if focus_handle.is_focused(window) {
1896 let snapshot = buffer.read(cx).snapshot();
1897 for (range, snippet) in snippet_edits {
1898 let editor_range =
1899 language::range_from_lsp(*range).to_offset(&snapshot);
1900 editor
1901 .insert_snippet(
1902 &[editor_range],
1903 snippet.clone(),
1904 window,
1905 cx,
1906 )
1907 .ok();
1908 }
1909 }
1910 }
1911 }
1912 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1913 let buffer_id = *buffer_id;
1914 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
1915 let registered = editor.register_buffer(buffer_id, cx);
1916 if registered {
1917 editor.update_lsp_data(Some(buffer_id), window, cx);
1918 editor.refresh_inlay_hints(
1919 InlayHintRefreshReason::RefreshRequested,
1920 cx,
1921 );
1922 refresh_linked_ranges(editor, window, cx);
1923 editor.refresh_code_actions(window, cx);
1924 editor.refresh_document_highlights(cx);
1925 }
1926 }
1927 }
1928
1929 project::Event::EntryRenamed(transaction) => {
1930 let Some(workspace) = editor.workspace() else {
1931 return;
1932 };
1933 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1934 else {
1935 return;
1936 };
1937 if active_editor.entity_id() == cx.entity_id() {
1938 let edited_buffers_already_open = {
1939 let other_editors: Vec<Entity<Editor>> = workspace
1940 .read(cx)
1941 .panes()
1942 .iter()
1943 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1944 .filter(|editor| editor.entity_id() != cx.entity_id())
1945 .collect();
1946
1947 transaction.0.keys().all(|buffer| {
1948 other_editors.iter().any(|editor| {
1949 let multi_buffer = editor.read(cx).buffer();
1950 multi_buffer.read(cx).is_singleton()
1951 && multi_buffer.read(cx).as_singleton().map_or(
1952 false,
1953 |singleton| {
1954 singleton.entity_id() == buffer.entity_id()
1955 },
1956 )
1957 })
1958 })
1959 };
1960
1961 if !edited_buffers_already_open {
1962 let workspace = workspace.downgrade();
1963 let transaction = transaction.clone();
1964 cx.defer_in(window, move |_, window, cx| {
1965 cx.spawn_in(window, async move |editor, cx| {
1966 Self::open_project_transaction(
1967 &editor,
1968 workspace,
1969 transaction,
1970 "Rename".to_string(),
1971 cx,
1972 )
1973 .await
1974 .ok()
1975 })
1976 .detach();
1977 });
1978 }
1979 }
1980 }
1981
1982 _ => {}
1983 },
1984 ));
1985 if let Some(task_inventory) = project
1986 .read(cx)
1987 .task_store()
1988 .read(cx)
1989 .task_inventory()
1990 .cloned()
1991 {
1992 project_subscriptions.push(cx.observe_in(
1993 &task_inventory,
1994 window,
1995 |editor, _, window, cx| {
1996 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1997 },
1998 ));
1999 };
2000
2001 project_subscriptions.push(cx.subscribe_in(
2002 &project.read(cx).breakpoint_store(),
2003 window,
2004 |editor, _, event, window, cx| match event {
2005 BreakpointStoreEvent::ClearDebugLines => {
2006 editor.clear_row_highlights::<ActiveDebugLine>();
2007 editor.refresh_inline_values(cx);
2008 }
2009 BreakpointStoreEvent::SetDebugLine => {
2010 if editor.go_to_active_debug_line(window, cx) {
2011 cx.stop_propagation();
2012 }
2013
2014 editor.refresh_inline_values(cx);
2015 }
2016 _ => {}
2017 },
2018 ));
2019 let git_store = project.read(cx).git_store().clone();
2020 let project = project.clone();
2021 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2022 if let GitStoreEvent::RepositoryUpdated(
2023 _,
2024 RepositoryEvent::Updated {
2025 new_instance: true, ..
2026 },
2027 _,
2028 ) = event
2029 {
2030 this.load_diff_task = Some(
2031 update_uncommitted_diff_for_buffer(
2032 cx.entity(),
2033 &project,
2034 this.buffer.read(cx).all_buffers(),
2035 this.buffer.clone(),
2036 cx,
2037 )
2038 .shared(),
2039 );
2040 }
2041 }));
2042 }
2043
2044 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2045
2046 let inlay_hint_settings =
2047 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2048 let focus_handle = cx.focus_handle();
2049 if !is_minimap {
2050 cx.on_focus(&focus_handle, window, Self::handle_focus)
2051 .detach();
2052 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2053 .detach();
2054 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2055 .detach();
2056 cx.on_blur(&focus_handle, window, Self::handle_blur)
2057 .detach();
2058 cx.observe_pending_input(window, Self::observe_pending_input)
2059 .detach();
2060 }
2061
2062 let show_indent_guides =
2063 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2064 Some(false)
2065 } else {
2066 None
2067 };
2068
2069 let breakpoint_store = match (&mode, project.as_ref()) {
2070 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2071 _ => None,
2072 };
2073
2074 let mut code_action_providers = Vec::new();
2075 let mut load_uncommitted_diff = None;
2076 if let Some(project) = project.clone() {
2077 load_uncommitted_diff = Some(
2078 update_uncommitted_diff_for_buffer(
2079 cx.entity(),
2080 &project,
2081 multi_buffer.read(cx).all_buffers(),
2082 multi_buffer.clone(),
2083 cx,
2084 )
2085 .shared(),
2086 );
2087 code_action_providers.push(Rc::new(project) as Rc<_>);
2088 }
2089
2090 let mut editor = Self {
2091 focus_handle,
2092 show_cursor_when_unfocused: false,
2093 last_focused_descendant: None,
2094 buffer: multi_buffer.clone(),
2095 display_map: display_map.clone(),
2096 placeholder_display_map: None,
2097 selections,
2098 scroll_manager: ScrollManager::new(cx),
2099 columnar_selection_state: None,
2100 add_selections_state: None,
2101 select_next_state: None,
2102 select_prev_state: None,
2103 selection_history: SelectionHistory::default(),
2104 defer_selection_effects: false,
2105 deferred_selection_effects_state: None,
2106 autoclose_regions: Vec::new(),
2107 snippet_stack: InvalidationStack::default(),
2108 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2109 ime_transaction: None,
2110 active_diagnostics: ActiveDiagnostic::None,
2111 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2112 inline_diagnostics_update: Task::ready(()),
2113 inline_diagnostics: Vec::new(),
2114 soft_wrap_mode_override,
2115 diagnostics_max_severity,
2116 hard_wrap: None,
2117 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2118 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2119 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2120 project,
2121 blink_manager: blink_manager.clone(),
2122 show_local_selections: true,
2123 show_scrollbars: ScrollbarAxes {
2124 horizontal: full_mode,
2125 vertical: full_mode,
2126 },
2127 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2128 offset_content: !matches!(mode, EditorMode::SingleLine),
2129 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2130 show_gutter: full_mode,
2131 show_line_numbers: (!full_mode).then_some(false),
2132 use_relative_line_numbers: None,
2133 disable_expand_excerpt_buttons: !full_mode,
2134 show_git_diff_gutter: None,
2135 show_code_actions: None,
2136 show_runnables: None,
2137 show_breakpoints: None,
2138 show_wrap_guides: None,
2139 show_indent_guides,
2140 highlight_order: 0,
2141 highlighted_rows: HashMap::default(),
2142 background_highlights: HashMap::default(),
2143 gutter_highlights: HashMap::default(),
2144 scrollbar_marker_state: ScrollbarMarkerState::default(),
2145 active_indent_guides_state: ActiveIndentGuidesState::default(),
2146 nav_history: None,
2147 context_menu: RefCell::new(None),
2148 context_menu_options: None,
2149 mouse_context_menu: None,
2150 completion_tasks: Vec::new(),
2151 inline_blame_popover: None,
2152 inline_blame_popover_show_task: None,
2153 signature_help_state: SignatureHelpState::default(),
2154 auto_signature_help: None,
2155 find_all_references_task_sources: Vec::new(),
2156 next_completion_id: 0,
2157 next_inlay_id: 0,
2158 code_action_providers,
2159 available_code_actions: None,
2160 code_actions_task: None,
2161 quick_selection_highlight_task: None,
2162 debounced_selection_highlight_task: None,
2163 document_highlights_task: None,
2164 linked_editing_range_task: None,
2165 pending_rename: None,
2166 searchable: !is_minimap,
2167 cursor_shape: EditorSettings::get_global(cx)
2168 .cursor_shape
2169 .unwrap_or_default(),
2170 current_line_highlight: None,
2171 autoindent_mode: Some(AutoindentMode::EachLine),
2172 collapse_matches: false,
2173 workspace: None,
2174 input_enabled: !is_minimap,
2175 use_modal_editing: full_mode,
2176 read_only: is_minimap,
2177 use_autoclose: true,
2178 use_auto_surround: true,
2179 auto_replace_emoji_shortcode: false,
2180 jsx_tag_auto_close_enabled_in_any_buffer: false,
2181 leader_id: None,
2182 remote_id: None,
2183 hover_state: HoverState::default(),
2184 pending_mouse_down: None,
2185 hovered_link_state: None,
2186 edit_prediction_provider: None,
2187 active_edit_prediction: None,
2188 stale_edit_prediction_in_menu: None,
2189 edit_prediction_preview: EditPredictionPreview::Inactive {
2190 released_too_fast: false,
2191 },
2192 inline_diagnostics_enabled: full_mode,
2193 diagnostics_enabled: full_mode,
2194 word_completions_enabled: full_mode,
2195 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2196 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2197 gutter_hovered: false,
2198 pixel_position_of_newest_cursor: None,
2199 last_bounds: None,
2200 last_position_map: None,
2201 expect_bounds_change: None,
2202 gutter_dimensions: GutterDimensions::default(),
2203 style: None,
2204 show_cursor_names: false,
2205 hovered_cursors: HashMap::default(),
2206 next_editor_action_id: EditorActionId::default(),
2207 editor_actions: Rc::default(),
2208 edit_predictions_hidden_for_vim_mode: false,
2209 show_edit_predictions_override: None,
2210 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2211 edit_prediction_settings: EditPredictionSettings::Disabled,
2212 edit_prediction_indent_conflict: false,
2213 edit_prediction_requires_modifier_in_indent_conflict: true,
2214 custom_context_menu: None,
2215 show_git_blame_gutter: false,
2216 show_git_blame_inline: false,
2217 show_selection_menu: None,
2218 show_git_blame_inline_delay_task: None,
2219 git_blame_inline_enabled: full_mode
2220 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2221 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2222 serialize_dirty_buffers: !is_minimap
2223 && ProjectSettings::get_global(cx)
2224 .session
2225 .restore_unsaved_buffers,
2226 blame: None,
2227 blame_subscription: None,
2228 tasks: BTreeMap::default(),
2229
2230 breakpoint_store,
2231 gutter_breakpoint_indicator: (None, None),
2232 hovered_diff_hunk_row: None,
2233 _subscriptions: (!is_minimap)
2234 .then(|| {
2235 vec![
2236 cx.observe(&multi_buffer, Self::on_buffer_changed),
2237 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2238 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2239 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2240 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2241 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2242 cx.observe_window_activation(window, |editor, window, cx| {
2243 let active = window.is_window_active();
2244 editor.blink_manager.update(cx, |blink_manager, cx| {
2245 if active {
2246 blink_manager.enable(cx);
2247 } else {
2248 blink_manager.disable(cx);
2249 }
2250 });
2251 if active {
2252 editor.show_mouse_cursor(cx);
2253 }
2254 }),
2255 ]
2256 })
2257 .unwrap_or_default(),
2258 tasks_update_task: None,
2259 pull_diagnostics_task: Task::ready(()),
2260 colors: None,
2261 refresh_colors_task: Task::ready(()),
2262 next_color_inlay_id: 0,
2263 post_scroll_update: Task::ready(()),
2264 linked_edit_ranges: Default::default(),
2265 in_project_search: false,
2266 previous_search_ranges: None,
2267 breadcrumb_header: None,
2268 focused_block: None,
2269 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2270 addons: HashMap::default(),
2271 registered_buffers: HashMap::default(),
2272 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2273 selection_mark_mode: false,
2274 toggle_fold_multiple_buffers: Task::ready(()),
2275 serialize_selections: Task::ready(()),
2276 serialize_folds: Task::ready(()),
2277 text_style_refinement: None,
2278 load_diff_task: load_uncommitted_diff,
2279 temporary_diff_override: false,
2280 mouse_cursor_hidden: false,
2281 minimap: None,
2282 hide_mouse_mode: EditorSettings::get_global(cx)
2283 .hide_mouse
2284 .unwrap_or_default(),
2285 change_list: ChangeList::new(),
2286 mode,
2287 selection_drag_state: SelectionDragState::None,
2288 folding_newlines: Task::ready(()),
2289 lookup_key: None,
2290 };
2291
2292 if is_minimap {
2293 return editor;
2294 }
2295
2296 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2297 editor
2298 ._subscriptions
2299 .push(cx.observe(breakpoints, |_, _, cx| {
2300 cx.notify();
2301 }));
2302 }
2303 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2304 editor._subscriptions.extend(project_subscriptions);
2305
2306 editor._subscriptions.push(cx.subscribe_in(
2307 &cx.entity(),
2308 window,
2309 |editor, _, e: &EditorEvent, window, cx| match e {
2310 EditorEvent::ScrollPositionChanged { local, .. } => {
2311 if *local {
2312 let new_anchor = editor.scroll_manager.anchor();
2313 let snapshot = editor.snapshot(window, cx);
2314 editor.update_restoration_data(cx, move |data| {
2315 data.scroll_position = (
2316 new_anchor.top_row(snapshot.buffer_snapshot()),
2317 new_anchor.offset,
2318 );
2319 });
2320 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2321 editor.inline_blame_popover.take();
2322 }
2323 }
2324 EditorEvent::Edited { .. } => {
2325 if !vim_enabled(cx) {
2326 let (map, selections) = editor.selections.all_adjusted_display(cx);
2327 let pop_state = editor
2328 .change_list
2329 .last()
2330 .map(|previous| {
2331 previous.len() == selections.len()
2332 && previous.iter().enumerate().all(|(ix, p)| {
2333 p.to_display_point(&map).row()
2334 == selections[ix].head().row()
2335 })
2336 })
2337 .unwrap_or(false);
2338 let new_positions = selections
2339 .into_iter()
2340 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2341 .collect();
2342 editor
2343 .change_list
2344 .push_to_change_list(pop_state, new_positions);
2345 }
2346 }
2347 _ => (),
2348 },
2349 ));
2350
2351 if let Some(dap_store) = editor
2352 .project
2353 .as_ref()
2354 .map(|project| project.read(cx).dap_store())
2355 {
2356 let weak_editor = cx.weak_entity();
2357
2358 editor
2359 ._subscriptions
2360 .push(
2361 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2362 let session_entity = cx.entity();
2363 weak_editor
2364 .update(cx, |editor, cx| {
2365 editor._subscriptions.push(
2366 cx.subscribe(&session_entity, Self::on_debug_session_event),
2367 );
2368 })
2369 .ok();
2370 }),
2371 );
2372
2373 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2374 editor
2375 ._subscriptions
2376 .push(cx.subscribe(&session, Self::on_debug_session_event));
2377 }
2378 }
2379
2380 // skip adding the initial selection to selection history
2381 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2382 editor.end_selection(window, cx);
2383 editor.selection_history.mode = SelectionHistoryMode::Normal;
2384
2385 editor.scroll_manager.show_scrollbars(window, cx);
2386 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2387
2388 if full_mode {
2389 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2390 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2391
2392 if editor.git_blame_inline_enabled {
2393 editor.start_git_blame_inline(false, window, cx);
2394 }
2395
2396 editor.go_to_active_debug_line(window, cx);
2397
2398 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2399 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2400 }
2401
2402 editor.minimap =
2403 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2404 editor.colors = Some(LspColorData::new(cx));
2405 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2406 }
2407
2408 editor
2409 }
2410
2411 pub fn deploy_mouse_context_menu(
2412 &mut self,
2413 position: gpui::Point<Pixels>,
2414 context_menu: Entity<ContextMenu>,
2415 window: &mut Window,
2416 cx: &mut Context<Self>,
2417 ) {
2418 self.mouse_context_menu = Some(MouseContextMenu::new(
2419 self,
2420 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2421 context_menu,
2422 window,
2423 cx,
2424 ));
2425 }
2426
2427 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2428 self.mouse_context_menu
2429 .as_ref()
2430 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2431 }
2432
2433 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2434 if self
2435 .selections
2436 .pending_anchor()
2437 .is_some_and(|pending_selection| {
2438 let snapshot = self.buffer().read(cx).snapshot(cx);
2439 pending_selection.range().includes(range, &snapshot)
2440 })
2441 {
2442 return true;
2443 }
2444
2445 self.selections
2446 .disjoint_in_range::<usize>(range.clone(), cx)
2447 .into_iter()
2448 .any(|selection| {
2449 // This is needed to cover a corner case, if we just check for an existing
2450 // selection in the fold range, having a cursor at the start of the fold
2451 // marks it as selected. Non-empty selections don't cause this.
2452 let length = selection.end - selection.start;
2453 length > 0
2454 })
2455 }
2456
2457 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2458 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2459 }
2460
2461 fn key_context_internal(
2462 &self,
2463 has_active_edit_prediction: bool,
2464 window: &Window,
2465 cx: &App,
2466 ) -> KeyContext {
2467 let mut key_context = KeyContext::new_with_defaults();
2468 key_context.add("Editor");
2469 let mode = match self.mode {
2470 EditorMode::SingleLine => "single_line",
2471 EditorMode::AutoHeight { .. } => "auto_height",
2472 EditorMode::Minimap { .. } => "minimap",
2473 EditorMode::Full { .. } => "full",
2474 };
2475
2476 if EditorSettings::jupyter_enabled(cx) {
2477 key_context.add("jupyter");
2478 }
2479
2480 key_context.set("mode", mode);
2481 if self.pending_rename.is_some() {
2482 key_context.add("renaming");
2483 }
2484
2485 match self.context_menu.borrow().as_ref() {
2486 Some(CodeContextMenu::Completions(menu)) => {
2487 if menu.visible() {
2488 key_context.add("menu");
2489 key_context.add("showing_completions");
2490 }
2491 }
2492 Some(CodeContextMenu::CodeActions(menu)) => {
2493 if menu.visible() {
2494 key_context.add("menu");
2495 key_context.add("showing_code_actions")
2496 }
2497 }
2498 None => {}
2499 }
2500
2501 if self.signature_help_state.has_multiple_signatures() {
2502 key_context.add("showing_signature_help");
2503 }
2504
2505 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2506 if !self.focus_handle(cx).contains_focused(window, cx)
2507 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2508 {
2509 for addon in self.addons.values() {
2510 addon.extend_key_context(&mut key_context, cx)
2511 }
2512 }
2513
2514 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2515 if let Some(extension) = singleton_buffer
2516 .read(cx)
2517 .file()
2518 .and_then(|file| file.path().extension())
2519 {
2520 key_context.set("extension", extension.to_string());
2521 }
2522 } else {
2523 key_context.add("multibuffer");
2524 }
2525
2526 if has_active_edit_prediction {
2527 if self.edit_prediction_in_conflict() {
2528 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2529 } else {
2530 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2531 key_context.add("copilot_suggestion");
2532 }
2533 }
2534
2535 if self.selection_mark_mode {
2536 key_context.add("selection_mode");
2537 }
2538
2539 key_context
2540 }
2541
2542 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2543 self.last_bounds.as_ref()
2544 }
2545
2546 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2547 if self.mouse_cursor_hidden {
2548 self.mouse_cursor_hidden = false;
2549 cx.notify();
2550 }
2551 }
2552
2553 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2554 let hide_mouse_cursor = match origin {
2555 HideMouseCursorOrigin::TypingAction => {
2556 matches!(
2557 self.hide_mouse_mode,
2558 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2559 )
2560 }
2561 HideMouseCursorOrigin::MovementAction => {
2562 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2563 }
2564 };
2565 if self.mouse_cursor_hidden != hide_mouse_cursor {
2566 self.mouse_cursor_hidden = hide_mouse_cursor;
2567 cx.notify();
2568 }
2569 }
2570
2571 pub fn edit_prediction_in_conflict(&self) -> bool {
2572 if !self.show_edit_predictions_in_menu() {
2573 return false;
2574 }
2575
2576 let showing_completions = self
2577 .context_menu
2578 .borrow()
2579 .as_ref()
2580 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2581
2582 showing_completions
2583 || self.edit_prediction_requires_modifier()
2584 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2585 // bindings to insert tab characters.
2586 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2587 }
2588
2589 pub fn accept_edit_prediction_keybind(
2590 &self,
2591 accept_partial: bool,
2592 window: &Window,
2593 cx: &App,
2594 ) -> AcceptEditPredictionBinding {
2595 let key_context = self.key_context_internal(true, window, cx);
2596 let in_conflict = self.edit_prediction_in_conflict();
2597
2598 let bindings = if accept_partial {
2599 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2600 } else {
2601 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2602 };
2603
2604 // TODO: if the binding contains multiple keystrokes, display all of them, not
2605 // just the first one.
2606 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2607 !in_conflict
2608 || binding
2609 .keystrokes()
2610 .first()
2611 .is_some_and(|keystroke| keystroke.modifiers().modified())
2612 }))
2613 }
2614
2615 pub fn new_file(
2616 workspace: &mut Workspace,
2617 _: &workspace::NewFile,
2618 window: &mut Window,
2619 cx: &mut Context<Workspace>,
2620 ) {
2621 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2622 "Failed to create buffer",
2623 window,
2624 cx,
2625 |e, _, _| match e.error_code() {
2626 ErrorCode::RemoteUpgradeRequired => Some(format!(
2627 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2628 e.error_tag("required").unwrap_or("the latest version")
2629 )),
2630 _ => None,
2631 },
2632 );
2633 }
2634
2635 pub fn new_in_workspace(
2636 workspace: &mut Workspace,
2637 window: &mut Window,
2638 cx: &mut Context<Workspace>,
2639 ) -> Task<Result<Entity<Editor>>> {
2640 let project = workspace.project().clone();
2641 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2642
2643 cx.spawn_in(window, async move |workspace, cx| {
2644 let buffer = create.await?;
2645 workspace.update_in(cx, |workspace, window, cx| {
2646 let editor =
2647 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2648 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2649 editor
2650 })
2651 })
2652 }
2653
2654 fn new_file_vertical(
2655 workspace: &mut Workspace,
2656 _: &workspace::NewFileSplitVertical,
2657 window: &mut Window,
2658 cx: &mut Context<Workspace>,
2659 ) {
2660 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2661 }
2662
2663 fn new_file_horizontal(
2664 workspace: &mut Workspace,
2665 _: &workspace::NewFileSplitHorizontal,
2666 window: &mut Window,
2667 cx: &mut Context<Workspace>,
2668 ) {
2669 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2670 }
2671
2672 fn new_file_in_direction(
2673 workspace: &mut Workspace,
2674 direction: SplitDirection,
2675 window: &mut Window,
2676 cx: &mut Context<Workspace>,
2677 ) {
2678 let project = workspace.project().clone();
2679 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2680
2681 cx.spawn_in(window, async move |workspace, cx| {
2682 let buffer = create.await?;
2683 workspace.update_in(cx, move |workspace, window, cx| {
2684 workspace.split_item(
2685 direction,
2686 Box::new(
2687 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2688 ),
2689 window,
2690 cx,
2691 )
2692 })?;
2693 anyhow::Ok(())
2694 })
2695 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2696 match e.error_code() {
2697 ErrorCode::RemoteUpgradeRequired => Some(format!(
2698 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2699 e.error_tag("required").unwrap_or("the latest version")
2700 )),
2701 _ => None,
2702 }
2703 });
2704 }
2705
2706 pub fn leader_id(&self) -> Option<CollaboratorId> {
2707 self.leader_id
2708 }
2709
2710 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2711 &self.buffer
2712 }
2713
2714 pub fn project(&self) -> Option<&Entity<Project>> {
2715 self.project.as_ref()
2716 }
2717
2718 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2719 self.workspace.as_ref()?.0.upgrade()
2720 }
2721
2722 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2723 self.buffer().read(cx).title(cx)
2724 }
2725
2726 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2727 let git_blame_gutter_max_author_length = self
2728 .render_git_blame_gutter(cx)
2729 .then(|| {
2730 if let Some(blame) = self.blame.as_ref() {
2731 let max_author_length =
2732 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2733 Some(max_author_length)
2734 } else {
2735 None
2736 }
2737 })
2738 .flatten();
2739
2740 EditorSnapshot {
2741 mode: self.mode.clone(),
2742 show_gutter: self.show_gutter,
2743 show_line_numbers: self.show_line_numbers,
2744 show_git_diff_gutter: self.show_git_diff_gutter,
2745 show_code_actions: self.show_code_actions,
2746 show_runnables: self.show_runnables,
2747 show_breakpoints: self.show_breakpoints,
2748 git_blame_gutter_max_author_length,
2749 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2750 placeholder_display_snapshot: self
2751 .placeholder_display_map
2752 .as_ref()
2753 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2754 scroll_anchor: self.scroll_manager.anchor(),
2755 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2756 is_focused: self.focus_handle.is_focused(window),
2757 current_line_highlight: self
2758 .current_line_highlight
2759 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2760 gutter_hovered: self.gutter_hovered,
2761 }
2762 }
2763
2764 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2765 self.buffer.read(cx).language_at(point, cx)
2766 }
2767
2768 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2769 self.buffer.read(cx).read(cx).file_at(point).cloned()
2770 }
2771
2772 pub fn active_excerpt(
2773 &self,
2774 cx: &App,
2775 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2776 self.buffer
2777 .read(cx)
2778 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2779 }
2780
2781 pub fn mode(&self) -> &EditorMode {
2782 &self.mode
2783 }
2784
2785 pub fn set_mode(&mut self, mode: EditorMode) {
2786 self.mode = mode;
2787 }
2788
2789 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2790 self.collaboration_hub.as_deref()
2791 }
2792
2793 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2794 self.collaboration_hub = Some(hub);
2795 }
2796
2797 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2798 self.in_project_search = in_project_search;
2799 }
2800
2801 pub fn set_custom_context_menu(
2802 &mut self,
2803 f: impl 'static
2804 + Fn(
2805 &mut Self,
2806 DisplayPoint,
2807 &mut Window,
2808 &mut Context<Self>,
2809 ) -> Option<Entity<ui::ContextMenu>>,
2810 ) {
2811 self.custom_context_menu = Some(Box::new(f))
2812 }
2813
2814 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2815 self.completion_provider = provider;
2816 }
2817
2818 #[cfg(any(test, feature = "test-support"))]
2819 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2820 self.completion_provider.clone()
2821 }
2822
2823 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2824 self.semantics_provider.clone()
2825 }
2826
2827 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2828 self.semantics_provider = provider;
2829 }
2830
2831 pub fn set_edit_prediction_provider<T>(
2832 &mut self,
2833 provider: Option<Entity<T>>,
2834 window: &mut Window,
2835 cx: &mut Context<Self>,
2836 ) where
2837 T: EditPredictionProvider,
2838 {
2839 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2840 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2841 if this.focus_handle.is_focused(window) {
2842 this.update_visible_edit_prediction(window, cx);
2843 }
2844 }),
2845 provider: Arc::new(provider),
2846 });
2847 self.update_edit_prediction_settings(cx);
2848 self.refresh_edit_prediction(false, false, window, cx);
2849 }
2850
2851 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2852 self.placeholder_display_map
2853 .as_ref()
2854 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2855 }
2856
2857 pub fn set_placeholder_text(
2858 &mut self,
2859 placeholder_text: &str,
2860 window: &mut Window,
2861 cx: &mut Context<Self>,
2862 ) {
2863 let multibuffer = cx
2864 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2865
2866 let style = window.text_style();
2867
2868 self.placeholder_display_map = Some(cx.new(|cx| {
2869 DisplayMap::new(
2870 multibuffer,
2871 style.font(),
2872 style.font_size.to_pixels(window.rem_size()),
2873 None,
2874 FILE_HEADER_HEIGHT,
2875 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2876 Default::default(),
2877 DiagnosticSeverity::Off,
2878 cx,
2879 )
2880 }));
2881 cx.notify();
2882 }
2883
2884 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2885 self.cursor_shape = cursor_shape;
2886
2887 // Disrupt blink for immediate user feedback that the cursor shape has changed
2888 self.blink_manager.update(cx, BlinkManager::show_cursor);
2889
2890 cx.notify();
2891 }
2892
2893 pub fn set_current_line_highlight(
2894 &mut self,
2895 current_line_highlight: Option<CurrentLineHighlight>,
2896 ) {
2897 self.current_line_highlight = current_line_highlight;
2898 }
2899
2900 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2901 self.collapse_matches = collapse_matches;
2902 }
2903
2904 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2905 if self.collapse_matches {
2906 return range.start..range.start;
2907 }
2908 range.clone()
2909 }
2910
2911 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2912 if self.display_map.read(cx).clip_at_line_ends != clip {
2913 self.display_map
2914 .update(cx, |map, _| map.clip_at_line_ends = clip);
2915 }
2916 }
2917
2918 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2919 self.input_enabled = input_enabled;
2920 }
2921
2922 pub fn set_edit_predictions_hidden_for_vim_mode(
2923 &mut self,
2924 hidden: bool,
2925 window: &mut Window,
2926 cx: &mut Context<Self>,
2927 ) {
2928 if hidden != self.edit_predictions_hidden_for_vim_mode {
2929 self.edit_predictions_hidden_for_vim_mode = hidden;
2930 if hidden {
2931 self.update_visible_edit_prediction(window, cx);
2932 } else {
2933 self.refresh_edit_prediction(true, false, window, cx);
2934 }
2935 }
2936 }
2937
2938 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2939 self.menu_edit_predictions_policy = value;
2940 }
2941
2942 pub fn set_autoindent(&mut self, autoindent: bool) {
2943 if autoindent {
2944 self.autoindent_mode = Some(AutoindentMode::EachLine);
2945 } else {
2946 self.autoindent_mode = None;
2947 }
2948 }
2949
2950 pub fn read_only(&self, cx: &App) -> bool {
2951 self.read_only || self.buffer.read(cx).read_only()
2952 }
2953
2954 pub fn set_read_only(&mut self, read_only: bool) {
2955 self.read_only = read_only;
2956 }
2957
2958 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2959 self.use_autoclose = autoclose;
2960 }
2961
2962 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2963 self.use_auto_surround = auto_surround;
2964 }
2965
2966 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2967 self.auto_replace_emoji_shortcode = auto_replace;
2968 }
2969
2970 pub fn toggle_edit_predictions(
2971 &mut self,
2972 _: &ToggleEditPrediction,
2973 window: &mut Window,
2974 cx: &mut Context<Self>,
2975 ) {
2976 if self.show_edit_predictions_override.is_some() {
2977 self.set_show_edit_predictions(None, window, cx);
2978 } else {
2979 let show_edit_predictions = !self.edit_predictions_enabled();
2980 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2981 }
2982 }
2983
2984 pub fn set_show_edit_predictions(
2985 &mut self,
2986 show_edit_predictions: Option<bool>,
2987 window: &mut Window,
2988 cx: &mut Context<Self>,
2989 ) {
2990 self.show_edit_predictions_override = show_edit_predictions;
2991 self.update_edit_prediction_settings(cx);
2992
2993 if let Some(false) = show_edit_predictions {
2994 self.discard_edit_prediction(false, cx);
2995 } else {
2996 self.refresh_edit_prediction(false, true, window, cx);
2997 }
2998 }
2999
3000 fn edit_predictions_disabled_in_scope(
3001 &self,
3002 buffer: &Entity<Buffer>,
3003 buffer_position: language::Anchor,
3004 cx: &App,
3005 ) -> bool {
3006 let snapshot = buffer.read(cx).snapshot();
3007 let settings = snapshot.settings_at(buffer_position, cx);
3008
3009 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3010 return false;
3011 };
3012
3013 scope.override_name().is_some_and(|scope_name| {
3014 settings
3015 .edit_predictions_disabled_in
3016 .iter()
3017 .any(|s| s == scope_name)
3018 })
3019 }
3020
3021 pub fn set_use_modal_editing(&mut self, to: bool) {
3022 self.use_modal_editing = to;
3023 }
3024
3025 pub fn use_modal_editing(&self) -> bool {
3026 self.use_modal_editing
3027 }
3028
3029 fn selections_did_change(
3030 &mut self,
3031 local: bool,
3032 old_cursor_position: &Anchor,
3033 effects: SelectionEffects,
3034 window: &mut Window,
3035 cx: &mut Context<Self>,
3036 ) {
3037 window.invalidate_character_coordinates();
3038
3039 // Copy selections to primary selection buffer
3040 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3041 if local {
3042 let selections = self.selections.all::<usize>(cx);
3043 let buffer_handle = self.buffer.read(cx).read(cx);
3044
3045 let mut text = String::new();
3046 for (index, selection) in selections.iter().enumerate() {
3047 let text_for_selection = buffer_handle
3048 .text_for_range(selection.start..selection.end)
3049 .collect::<String>();
3050
3051 text.push_str(&text_for_selection);
3052 if index != selections.len() - 1 {
3053 text.push('\n');
3054 }
3055 }
3056
3057 if !text.is_empty() {
3058 cx.write_to_primary(ClipboardItem::new_string(text));
3059 }
3060 }
3061
3062 let selection_anchors = self.selections.disjoint_anchors_arc();
3063
3064 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3065 self.buffer.update(cx, |buffer, cx| {
3066 buffer.set_active_selections(
3067 &selection_anchors,
3068 self.selections.line_mode(),
3069 self.cursor_shape,
3070 cx,
3071 )
3072 });
3073 }
3074 let display_map = self
3075 .display_map
3076 .update(cx, |display_map, cx| display_map.snapshot(cx));
3077 let buffer = display_map.buffer_snapshot();
3078 if self.selections.count() == 1 {
3079 self.add_selections_state = None;
3080 }
3081 self.select_next_state = None;
3082 self.select_prev_state = None;
3083 self.select_syntax_node_history.try_clear();
3084 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3085 self.snippet_stack.invalidate(&selection_anchors, buffer);
3086 self.take_rename(false, window, cx);
3087
3088 let newest_selection = self.selections.newest_anchor();
3089 let new_cursor_position = newest_selection.head();
3090 let selection_start = newest_selection.start;
3091
3092 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3093 self.push_to_nav_history(
3094 *old_cursor_position,
3095 Some(new_cursor_position.to_point(buffer)),
3096 false,
3097 effects.nav_history == Some(true),
3098 cx,
3099 );
3100 }
3101
3102 if local {
3103 if let Some(buffer_id) = new_cursor_position.buffer_id {
3104 self.register_buffer(buffer_id, cx);
3105 }
3106
3107 let mut context_menu = self.context_menu.borrow_mut();
3108 let completion_menu = match context_menu.as_ref() {
3109 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3110 Some(CodeContextMenu::CodeActions(_)) => {
3111 *context_menu = None;
3112 None
3113 }
3114 None => None,
3115 };
3116 let completion_position = completion_menu.map(|menu| menu.initial_position);
3117 drop(context_menu);
3118
3119 if effects.completions
3120 && let Some(completion_position) = completion_position
3121 {
3122 let start_offset = selection_start.to_offset(buffer);
3123 let position_matches = start_offset == completion_position.to_offset(buffer);
3124 let continue_showing = if position_matches {
3125 if self.snippet_stack.is_empty() {
3126 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3127 == Some(CharKind::Word)
3128 } else {
3129 // Snippet choices can be shown even when the cursor is in whitespace.
3130 // Dismissing the menu with actions like backspace is handled by
3131 // invalidation regions.
3132 true
3133 }
3134 } else {
3135 false
3136 };
3137
3138 if continue_showing {
3139 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3140 } else {
3141 self.hide_context_menu(window, cx);
3142 }
3143 }
3144
3145 hide_hover(self, cx);
3146
3147 if old_cursor_position.to_display_point(&display_map).row()
3148 != new_cursor_position.to_display_point(&display_map).row()
3149 {
3150 self.available_code_actions.take();
3151 }
3152 self.refresh_code_actions(window, cx);
3153 self.refresh_document_highlights(cx);
3154 refresh_linked_ranges(self, window, cx);
3155
3156 self.refresh_selected_text_highlights(false, window, cx);
3157 refresh_matching_bracket_highlights(self, cx);
3158 self.update_visible_edit_prediction(window, cx);
3159 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3160 self.inline_blame_popover.take();
3161 if self.git_blame_inline_enabled {
3162 self.start_inline_blame_timer(window, cx);
3163 }
3164 }
3165
3166 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3167 cx.emit(EditorEvent::SelectionsChanged { local });
3168
3169 let selections = &self.selections.disjoint_anchors_arc();
3170 if selections.len() == 1 {
3171 cx.emit(SearchEvent::ActiveMatchChanged)
3172 }
3173 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3174 let inmemory_selections = selections
3175 .iter()
3176 .map(|s| {
3177 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3178 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3179 })
3180 .collect();
3181 self.update_restoration_data(cx, |data| {
3182 data.selections = inmemory_selections;
3183 });
3184
3185 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3186 && let Some(workspace_id) =
3187 self.workspace.as_ref().and_then(|workspace| workspace.1)
3188 {
3189 let snapshot = self.buffer().read(cx).snapshot(cx);
3190 let selections = selections.clone();
3191 let background_executor = cx.background_executor().clone();
3192 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3193 self.serialize_selections = cx.background_spawn(async move {
3194 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3195 let db_selections = selections
3196 .iter()
3197 .map(|selection| {
3198 (
3199 selection.start.to_offset(&snapshot),
3200 selection.end.to_offset(&snapshot),
3201 )
3202 })
3203 .collect();
3204
3205 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3206 .await
3207 .with_context(|| {
3208 format!(
3209 "persisting editor selections for editor {editor_id}, \
3210 workspace {workspace_id:?}"
3211 )
3212 })
3213 .log_err();
3214 });
3215 }
3216 }
3217
3218 cx.notify();
3219 }
3220
3221 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3222 use text::ToOffset as _;
3223 use text::ToPoint as _;
3224
3225 if self.mode.is_minimap()
3226 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3227 {
3228 return;
3229 }
3230
3231 if !self.buffer().read(cx).is_singleton() {
3232 return;
3233 }
3234
3235 let display_snapshot = self
3236 .display_map
3237 .update(cx, |display_map, cx| display_map.snapshot(cx));
3238 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3239 return;
3240 };
3241 let inmemory_folds = display_snapshot
3242 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3243 .map(|fold| {
3244 fold.range.start.text_anchor.to_point(&snapshot)
3245 ..fold.range.end.text_anchor.to_point(&snapshot)
3246 })
3247 .collect();
3248 self.update_restoration_data(cx, |data| {
3249 data.folds = inmemory_folds;
3250 });
3251
3252 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3253 return;
3254 };
3255 let background_executor = cx.background_executor().clone();
3256 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3257 let db_folds = display_snapshot
3258 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3259 .map(|fold| {
3260 (
3261 fold.range.start.text_anchor.to_offset(&snapshot),
3262 fold.range.end.text_anchor.to_offset(&snapshot),
3263 )
3264 })
3265 .collect();
3266 self.serialize_folds = cx.background_spawn(async move {
3267 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3268 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3269 .await
3270 .with_context(|| {
3271 format!(
3272 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3273 )
3274 })
3275 .log_err();
3276 });
3277 }
3278
3279 pub fn sync_selections(
3280 &mut self,
3281 other: Entity<Editor>,
3282 cx: &mut Context<Self>,
3283 ) -> gpui::Subscription {
3284 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3285 if !other_selections.is_empty() {
3286 self.selections.change_with(cx, |selections| {
3287 selections.select_anchors(other_selections);
3288 });
3289 }
3290
3291 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3292 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3293 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3294 if other_selections.is_empty() {
3295 return;
3296 }
3297 this.selections.change_with(cx, |selections| {
3298 selections.select_anchors(other_selections);
3299 });
3300 }
3301 });
3302
3303 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3304 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3305 let these_selections = this.selections.disjoint_anchors().to_vec();
3306 if these_selections.is_empty() {
3307 return;
3308 }
3309 other.update(cx, |other_editor, cx| {
3310 other_editor.selections.change_with(cx, |selections| {
3311 selections.select_anchors(these_selections);
3312 })
3313 });
3314 }
3315 });
3316
3317 Subscription::join(other_subscription, this_subscription)
3318 }
3319
3320 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3321 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3322 /// effects of selection change occur at the end of the transaction.
3323 pub fn change_selections<R>(
3324 &mut self,
3325 effects: SelectionEffects,
3326 window: &mut Window,
3327 cx: &mut Context<Self>,
3328 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3329 ) -> R {
3330 if let Some(state) = &mut self.deferred_selection_effects_state {
3331 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3332 state.effects.completions = effects.completions;
3333 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3334 let (changed, result) = self.selections.change_with(cx, change);
3335 state.changed |= changed;
3336 return result;
3337 }
3338 let mut state = DeferredSelectionEffectsState {
3339 changed: false,
3340 effects,
3341 old_cursor_position: self.selections.newest_anchor().head(),
3342 history_entry: SelectionHistoryEntry {
3343 selections: self.selections.disjoint_anchors_arc(),
3344 select_next_state: self.select_next_state.clone(),
3345 select_prev_state: self.select_prev_state.clone(),
3346 add_selections_state: self.add_selections_state.clone(),
3347 },
3348 };
3349 let (changed, result) = self.selections.change_with(cx, change);
3350 state.changed = state.changed || changed;
3351 if self.defer_selection_effects {
3352 self.deferred_selection_effects_state = Some(state);
3353 } else {
3354 self.apply_selection_effects(state, window, cx);
3355 }
3356 result
3357 }
3358
3359 /// Defers the effects of selection change, so that the effects of multiple calls to
3360 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3361 /// to selection history and the state of popovers based on selection position aren't
3362 /// erroneously updated.
3363 pub fn with_selection_effects_deferred<R>(
3364 &mut self,
3365 window: &mut Window,
3366 cx: &mut Context<Self>,
3367 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3368 ) -> R {
3369 let already_deferred = self.defer_selection_effects;
3370 self.defer_selection_effects = true;
3371 let result = update(self, window, cx);
3372 if !already_deferred {
3373 self.defer_selection_effects = false;
3374 if let Some(state) = self.deferred_selection_effects_state.take() {
3375 self.apply_selection_effects(state, window, cx);
3376 }
3377 }
3378 result
3379 }
3380
3381 fn apply_selection_effects(
3382 &mut self,
3383 state: DeferredSelectionEffectsState,
3384 window: &mut Window,
3385 cx: &mut Context<Self>,
3386 ) {
3387 if state.changed {
3388 self.selection_history.push(state.history_entry);
3389
3390 if let Some(autoscroll) = state.effects.scroll {
3391 self.request_autoscroll(autoscroll, cx);
3392 }
3393
3394 let old_cursor_position = &state.old_cursor_position;
3395
3396 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3397
3398 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3399 self.show_signature_help(&ShowSignatureHelp, window, cx);
3400 }
3401 }
3402 }
3403
3404 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3405 where
3406 I: IntoIterator<Item = (Range<S>, T)>,
3407 S: ToOffset,
3408 T: Into<Arc<str>>,
3409 {
3410 if self.read_only(cx) {
3411 return;
3412 }
3413
3414 self.buffer
3415 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3416 }
3417
3418 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3419 where
3420 I: IntoIterator<Item = (Range<S>, T)>,
3421 S: ToOffset,
3422 T: Into<Arc<str>>,
3423 {
3424 if self.read_only(cx) {
3425 return;
3426 }
3427
3428 self.buffer.update(cx, |buffer, cx| {
3429 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3430 });
3431 }
3432
3433 pub fn edit_with_block_indent<I, S, T>(
3434 &mut self,
3435 edits: I,
3436 original_indent_columns: Vec<Option<u32>>,
3437 cx: &mut Context<Self>,
3438 ) where
3439 I: IntoIterator<Item = (Range<S>, T)>,
3440 S: ToOffset,
3441 T: Into<Arc<str>>,
3442 {
3443 if self.read_only(cx) {
3444 return;
3445 }
3446
3447 self.buffer.update(cx, |buffer, cx| {
3448 buffer.edit(
3449 edits,
3450 Some(AutoindentMode::Block {
3451 original_indent_columns,
3452 }),
3453 cx,
3454 )
3455 });
3456 }
3457
3458 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3459 self.hide_context_menu(window, cx);
3460
3461 match phase {
3462 SelectPhase::Begin {
3463 position,
3464 add,
3465 click_count,
3466 } => self.begin_selection(position, add, click_count, window, cx),
3467 SelectPhase::BeginColumnar {
3468 position,
3469 goal_column,
3470 reset,
3471 mode,
3472 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3473 SelectPhase::Extend {
3474 position,
3475 click_count,
3476 } => self.extend_selection(position, click_count, window, cx),
3477 SelectPhase::Update {
3478 position,
3479 goal_column,
3480 scroll_delta,
3481 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3482 SelectPhase::End => self.end_selection(window, cx),
3483 }
3484 }
3485
3486 fn extend_selection(
3487 &mut self,
3488 position: DisplayPoint,
3489 click_count: usize,
3490 window: &mut Window,
3491 cx: &mut Context<Self>,
3492 ) {
3493 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3494 let tail = self.selections.newest::<usize>(cx).tail();
3495 let click_count = click_count.max(match self.selections.select_mode() {
3496 SelectMode::Character => 1,
3497 SelectMode::Word(_) => 2,
3498 SelectMode::Line(_) => 3,
3499 SelectMode::All => 4,
3500 });
3501 self.begin_selection(position, false, click_count, window, cx);
3502
3503 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3504
3505 let current_selection = match self.selections.select_mode() {
3506 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3507 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3508 };
3509
3510 let mut pending_selection = self
3511 .selections
3512 .pending_anchor()
3513 .cloned()
3514 .expect("extend_selection not called with pending selection");
3515
3516 if pending_selection
3517 .start
3518 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3519 == Ordering::Greater
3520 {
3521 pending_selection.start = current_selection.start;
3522 }
3523 if pending_selection
3524 .end
3525 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3526 == Ordering::Less
3527 {
3528 pending_selection.end = current_selection.end;
3529 pending_selection.reversed = true;
3530 }
3531
3532 let mut pending_mode = self.selections.pending_mode().unwrap();
3533 match &mut pending_mode {
3534 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3535 _ => {}
3536 }
3537
3538 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3539 SelectionEffects::scroll(Autoscroll::fit())
3540 } else {
3541 SelectionEffects::no_scroll()
3542 };
3543
3544 self.change_selections(effects, window, cx, |s| {
3545 s.set_pending(pending_selection.clone(), pending_mode);
3546 s.set_is_extending(true);
3547 });
3548 }
3549
3550 fn begin_selection(
3551 &mut self,
3552 position: DisplayPoint,
3553 add: bool,
3554 click_count: usize,
3555 window: &mut Window,
3556 cx: &mut Context<Self>,
3557 ) {
3558 if !self.focus_handle.is_focused(window) {
3559 self.last_focused_descendant = None;
3560 window.focus(&self.focus_handle);
3561 }
3562
3563 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3564 let buffer = display_map.buffer_snapshot();
3565 let position = display_map.clip_point(position, Bias::Left);
3566
3567 let start;
3568 let end;
3569 let mode;
3570 let mut auto_scroll;
3571 match click_count {
3572 1 => {
3573 start = buffer.anchor_before(position.to_point(&display_map));
3574 end = start;
3575 mode = SelectMode::Character;
3576 auto_scroll = true;
3577 }
3578 2 => {
3579 let position = display_map
3580 .clip_point(position, Bias::Left)
3581 .to_offset(&display_map, Bias::Left);
3582 let (range, _) = buffer.surrounding_word(position, None);
3583 start = buffer.anchor_before(range.start);
3584 end = buffer.anchor_before(range.end);
3585 mode = SelectMode::Word(start..end);
3586 auto_scroll = true;
3587 }
3588 3 => {
3589 let position = display_map
3590 .clip_point(position, Bias::Left)
3591 .to_point(&display_map);
3592 let line_start = display_map.prev_line_boundary(position).0;
3593 let next_line_start = buffer.clip_point(
3594 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3595 Bias::Left,
3596 );
3597 start = buffer.anchor_before(line_start);
3598 end = buffer.anchor_before(next_line_start);
3599 mode = SelectMode::Line(start..end);
3600 auto_scroll = true;
3601 }
3602 _ => {
3603 start = buffer.anchor_before(0);
3604 end = buffer.anchor_before(buffer.len());
3605 mode = SelectMode::All;
3606 auto_scroll = false;
3607 }
3608 }
3609 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3610
3611 let point_to_delete: Option<usize> = {
3612 let selected_points: Vec<Selection<Point>> =
3613 self.selections.disjoint_in_range(start..end, cx);
3614
3615 if !add || click_count > 1 {
3616 None
3617 } else if !selected_points.is_empty() {
3618 Some(selected_points[0].id)
3619 } else {
3620 let clicked_point_already_selected =
3621 self.selections.disjoint_anchors().iter().find(|selection| {
3622 selection.start.to_point(buffer) == start.to_point(buffer)
3623 || selection.end.to_point(buffer) == end.to_point(buffer)
3624 });
3625
3626 clicked_point_already_selected.map(|selection| selection.id)
3627 }
3628 };
3629
3630 let selections_count = self.selections.count();
3631 let effects = if auto_scroll {
3632 SelectionEffects::default()
3633 } else {
3634 SelectionEffects::no_scroll()
3635 };
3636
3637 self.change_selections(effects, window, cx, |s| {
3638 if let Some(point_to_delete) = point_to_delete {
3639 s.delete(point_to_delete);
3640
3641 if selections_count == 1 {
3642 s.set_pending_anchor_range(start..end, mode);
3643 }
3644 } else {
3645 if !add {
3646 s.clear_disjoint();
3647 }
3648
3649 s.set_pending_anchor_range(start..end, mode);
3650 }
3651 });
3652 }
3653
3654 fn begin_columnar_selection(
3655 &mut self,
3656 position: DisplayPoint,
3657 goal_column: u32,
3658 reset: bool,
3659 mode: ColumnarMode,
3660 window: &mut Window,
3661 cx: &mut Context<Self>,
3662 ) {
3663 if !self.focus_handle.is_focused(window) {
3664 self.last_focused_descendant = None;
3665 window.focus(&self.focus_handle);
3666 }
3667
3668 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3669
3670 if reset {
3671 let pointer_position = display_map
3672 .buffer_snapshot()
3673 .anchor_before(position.to_point(&display_map));
3674
3675 self.change_selections(
3676 SelectionEffects::scroll(Autoscroll::newest()),
3677 window,
3678 cx,
3679 |s| {
3680 s.clear_disjoint();
3681 s.set_pending_anchor_range(
3682 pointer_position..pointer_position,
3683 SelectMode::Character,
3684 );
3685 },
3686 );
3687 };
3688
3689 let tail = self.selections.newest::<Point>(cx).tail();
3690 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3691 self.columnar_selection_state = match mode {
3692 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3693 selection_tail: selection_anchor,
3694 display_point: if reset {
3695 if position.column() != goal_column {
3696 Some(DisplayPoint::new(position.row(), goal_column))
3697 } else {
3698 None
3699 }
3700 } else {
3701 None
3702 },
3703 }),
3704 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3705 selection_tail: selection_anchor,
3706 }),
3707 };
3708
3709 if !reset {
3710 self.select_columns(position, goal_column, &display_map, window, cx);
3711 }
3712 }
3713
3714 fn update_selection(
3715 &mut self,
3716 position: DisplayPoint,
3717 goal_column: u32,
3718 scroll_delta: gpui::Point<f32>,
3719 window: &mut Window,
3720 cx: &mut Context<Self>,
3721 ) {
3722 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3723
3724 if self.columnar_selection_state.is_some() {
3725 self.select_columns(position, goal_column, &display_map, window, cx);
3726 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3727 let buffer = display_map.buffer_snapshot();
3728 let head;
3729 let tail;
3730 let mode = self.selections.pending_mode().unwrap();
3731 match &mode {
3732 SelectMode::Character => {
3733 head = position.to_point(&display_map);
3734 tail = pending.tail().to_point(buffer);
3735 }
3736 SelectMode::Word(original_range) => {
3737 let offset = display_map
3738 .clip_point(position, Bias::Left)
3739 .to_offset(&display_map, Bias::Left);
3740 let original_range = original_range.to_offset(buffer);
3741
3742 let head_offset = if buffer.is_inside_word(offset, None)
3743 || original_range.contains(&offset)
3744 {
3745 let (word_range, _) = buffer.surrounding_word(offset, None);
3746 if word_range.start < original_range.start {
3747 word_range.start
3748 } else {
3749 word_range.end
3750 }
3751 } else {
3752 offset
3753 };
3754
3755 head = head_offset.to_point(buffer);
3756 if head_offset <= original_range.start {
3757 tail = original_range.end.to_point(buffer);
3758 } else {
3759 tail = original_range.start.to_point(buffer);
3760 }
3761 }
3762 SelectMode::Line(original_range) => {
3763 let original_range = original_range.to_point(display_map.buffer_snapshot());
3764
3765 let position = display_map
3766 .clip_point(position, Bias::Left)
3767 .to_point(&display_map);
3768 let line_start = display_map.prev_line_boundary(position).0;
3769 let next_line_start = buffer.clip_point(
3770 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3771 Bias::Left,
3772 );
3773
3774 if line_start < original_range.start {
3775 head = line_start
3776 } else {
3777 head = next_line_start
3778 }
3779
3780 if head <= original_range.start {
3781 tail = original_range.end;
3782 } else {
3783 tail = original_range.start;
3784 }
3785 }
3786 SelectMode::All => {
3787 return;
3788 }
3789 };
3790
3791 if head < tail {
3792 pending.start = buffer.anchor_before(head);
3793 pending.end = buffer.anchor_before(tail);
3794 pending.reversed = true;
3795 } else {
3796 pending.start = buffer.anchor_before(tail);
3797 pending.end = buffer.anchor_before(head);
3798 pending.reversed = false;
3799 }
3800
3801 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3802 s.set_pending(pending.clone(), mode);
3803 });
3804 } else {
3805 log::error!("update_selection dispatched with no pending selection");
3806 return;
3807 }
3808
3809 self.apply_scroll_delta(scroll_delta, window, cx);
3810 cx.notify();
3811 }
3812
3813 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3814 self.columnar_selection_state.take();
3815 if let Some(pending_mode) = self.selections.pending_mode() {
3816 let selections = self.selections.all::<usize>(cx);
3817 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3818 s.select(selections);
3819 s.clear_pending();
3820 if s.is_extending() {
3821 s.set_is_extending(false);
3822 } else {
3823 s.set_select_mode(pending_mode);
3824 }
3825 });
3826 }
3827 }
3828
3829 fn select_columns(
3830 &mut self,
3831 head: DisplayPoint,
3832 goal_column: u32,
3833 display_map: &DisplaySnapshot,
3834 window: &mut Window,
3835 cx: &mut Context<Self>,
3836 ) {
3837 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3838 return;
3839 };
3840
3841 let tail = match columnar_state {
3842 ColumnarSelectionState::FromMouse {
3843 selection_tail,
3844 display_point,
3845 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3846 ColumnarSelectionState::FromSelection { selection_tail } => {
3847 selection_tail.to_display_point(display_map)
3848 }
3849 };
3850
3851 let start_row = cmp::min(tail.row(), head.row());
3852 let end_row = cmp::max(tail.row(), head.row());
3853 let start_column = cmp::min(tail.column(), goal_column);
3854 let end_column = cmp::max(tail.column(), goal_column);
3855 let reversed = start_column < tail.column();
3856
3857 let selection_ranges = (start_row.0..=end_row.0)
3858 .map(DisplayRow)
3859 .filter_map(|row| {
3860 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3861 || start_column <= display_map.line_len(row))
3862 && !display_map.is_block_line(row)
3863 {
3864 let start = display_map
3865 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3866 .to_point(display_map);
3867 let end = display_map
3868 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3869 .to_point(display_map);
3870 if reversed {
3871 Some(end..start)
3872 } else {
3873 Some(start..end)
3874 }
3875 } else {
3876 None
3877 }
3878 })
3879 .collect::<Vec<_>>();
3880 if selection_ranges.is_empty() {
3881 return;
3882 }
3883
3884 let ranges = match columnar_state {
3885 ColumnarSelectionState::FromMouse { .. } => {
3886 let mut non_empty_ranges = selection_ranges
3887 .iter()
3888 .filter(|selection_range| selection_range.start != selection_range.end)
3889 .peekable();
3890 if non_empty_ranges.peek().is_some() {
3891 non_empty_ranges.cloned().collect()
3892 } else {
3893 selection_ranges
3894 }
3895 }
3896 _ => selection_ranges,
3897 };
3898
3899 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3900 s.select_ranges(ranges);
3901 });
3902 cx.notify();
3903 }
3904
3905 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3906 self.selections
3907 .all_adjusted(cx)
3908 .iter()
3909 .any(|selection| !selection.is_empty())
3910 }
3911
3912 pub fn has_pending_nonempty_selection(&self) -> bool {
3913 let pending_nonempty_selection = match self.selections.pending_anchor() {
3914 Some(Selection { start, end, .. }) => start != end,
3915 None => false,
3916 };
3917
3918 pending_nonempty_selection
3919 || (self.columnar_selection_state.is_some()
3920 && self.selections.disjoint_anchors().len() > 1)
3921 }
3922
3923 pub fn has_pending_selection(&self) -> bool {
3924 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3925 }
3926
3927 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3928 self.selection_mark_mode = false;
3929 self.selection_drag_state = SelectionDragState::None;
3930
3931 if self.clear_expanded_diff_hunks(cx) {
3932 cx.notify();
3933 return;
3934 }
3935 if self.dismiss_menus_and_popups(true, window, cx) {
3936 return;
3937 }
3938
3939 if self.mode.is_full()
3940 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3941 {
3942 return;
3943 }
3944
3945 cx.propagate();
3946 }
3947
3948 pub fn dismiss_menus_and_popups(
3949 &mut self,
3950 is_user_requested: bool,
3951 window: &mut Window,
3952 cx: &mut Context<Self>,
3953 ) -> bool {
3954 if self.take_rename(false, window, cx).is_some() {
3955 return true;
3956 }
3957
3958 if hide_hover(self, cx) {
3959 return true;
3960 }
3961
3962 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3963 return true;
3964 }
3965
3966 if self.hide_context_menu(window, cx).is_some() {
3967 return true;
3968 }
3969
3970 if self.mouse_context_menu.take().is_some() {
3971 return true;
3972 }
3973
3974 if is_user_requested && self.discard_edit_prediction(true, cx) {
3975 return true;
3976 }
3977
3978 if self.snippet_stack.pop().is_some() {
3979 return true;
3980 }
3981
3982 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3983 self.dismiss_diagnostics(cx);
3984 return true;
3985 }
3986
3987 false
3988 }
3989
3990 fn linked_editing_ranges_for(
3991 &self,
3992 selection: Range<text::Anchor>,
3993 cx: &App,
3994 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3995 if self.linked_edit_ranges.is_empty() {
3996 return None;
3997 }
3998 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3999 selection.end.buffer_id.and_then(|end_buffer_id| {
4000 if selection.start.buffer_id != Some(end_buffer_id) {
4001 return None;
4002 }
4003 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4004 let snapshot = buffer.read(cx).snapshot();
4005 self.linked_edit_ranges
4006 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4007 .map(|ranges| (ranges, snapshot, buffer))
4008 })?;
4009 use text::ToOffset as TO;
4010 // find offset from the start of current range to current cursor position
4011 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4012
4013 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4014 let start_difference = start_offset - start_byte_offset;
4015 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4016 let end_difference = end_offset - start_byte_offset;
4017 // Current range has associated linked ranges.
4018 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4019 for range in linked_ranges.iter() {
4020 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4021 let end_offset = start_offset + end_difference;
4022 let start_offset = start_offset + start_difference;
4023 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4024 continue;
4025 }
4026 if self.selections.disjoint_anchor_ranges().any(|s| {
4027 if s.start.buffer_id != selection.start.buffer_id
4028 || s.end.buffer_id != selection.end.buffer_id
4029 {
4030 return false;
4031 }
4032 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4033 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4034 }) {
4035 continue;
4036 }
4037 let start = buffer_snapshot.anchor_after(start_offset);
4038 let end = buffer_snapshot.anchor_after(end_offset);
4039 linked_edits
4040 .entry(buffer.clone())
4041 .or_default()
4042 .push(start..end);
4043 }
4044 Some(linked_edits)
4045 }
4046
4047 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4048 let text: Arc<str> = text.into();
4049
4050 if self.read_only(cx) {
4051 return;
4052 }
4053
4054 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4055
4056 let selections = self.selections.all_adjusted(cx);
4057 let mut bracket_inserted = false;
4058 let mut edits = Vec::new();
4059 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4060 let mut new_selections = Vec::with_capacity(selections.len());
4061 let mut new_autoclose_regions = Vec::new();
4062 let snapshot = self.buffer.read(cx).read(cx);
4063 let mut clear_linked_edit_ranges = false;
4064
4065 for (selection, autoclose_region) in
4066 self.selections_with_autoclose_regions(selections, &snapshot)
4067 {
4068 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4069 // Determine if the inserted text matches the opening or closing
4070 // bracket of any of this language's bracket pairs.
4071 let mut bracket_pair = None;
4072 let mut is_bracket_pair_start = false;
4073 let mut is_bracket_pair_end = false;
4074 if !text.is_empty() {
4075 let mut bracket_pair_matching_end = None;
4076 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4077 // and they are removing the character that triggered IME popup.
4078 for (pair, enabled) in scope.brackets() {
4079 if !pair.close && !pair.surround {
4080 continue;
4081 }
4082
4083 if enabled && pair.start.ends_with(text.as_ref()) {
4084 let prefix_len = pair.start.len() - text.len();
4085 let preceding_text_matches_prefix = prefix_len == 0
4086 || (selection.start.column >= (prefix_len as u32)
4087 && snapshot.contains_str_at(
4088 Point::new(
4089 selection.start.row,
4090 selection.start.column - (prefix_len as u32),
4091 ),
4092 &pair.start[..prefix_len],
4093 ));
4094 if preceding_text_matches_prefix {
4095 bracket_pair = Some(pair.clone());
4096 is_bracket_pair_start = true;
4097 break;
4098 }
4099 }
4100 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4101 {
4102 // take first bracket pair matching end, but don't break in case a later bracket
4103 // pair matches start
4104 bracket_pair_matching_end = Some(pair.clone());
4105 }
4106 }
4107 if let Some(end) = bracket_pair_matching_end
4108 && bracket_pair.is_none()
4109 {
4110 bracket_pair = Some(end);
4111 is_bracket_pair_end = true;
4112 }
4113 }
4114
4115 if let Some(bracket_pair) = bracket_pair {
4116 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4117 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4118 let auto_surround =
4119 self.use_auto_surround && snapshot_settings.use_auto_surround;
4120 if selection.is_empty() {
4121 if is_bracket_pair_start {
4122 // If the inserted text is a suffix of an opening bracket and the
4123 // selection is preceded by the rest of the opening bracket, then
4124 // insert the closing bracket.
4125 let following_text_allows_autoclose = snapshot
4126 .chars_at(selection.start)
4127 .next()
4128 .is_none_or(|c| scope.should_autoclose_before(c));
4129
4130 let preceding_text_allows_autoclose = selection.start.column == 0
4131 || snapshot
4132 .reversed_chars_at(selection.start)
4133 .next()
4134 .is_none_or(|c| {
4135 bracket_pair.start != bracket_pair.end
4136 || !snapshot
4137 .char_classifier_at(selection.start)
4138 .is_word(c)
4139 });
4140
4141 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4142 && bracket_pair.start.len() == 1
4143 {
4144 let target = bracket_pair.start.chars().next().unwrap();
4145 let current_line_count = snapshot
4146 .reversed_chars_at(selection.start)
4147 .take_while(|&c| c != '\n')
4148 .filter(|&c| c == target)
4149 .count();
4150 current_line_count % 2 == 1
4151 } else {
4152 false
4153 };
4154
4155 if autoclose
4156 && bracket_pair.close
4157 && following_text_allows_autoclose
4158 && preceding_text_allows_autoclose
4159 && !is_closing_quote
4160 {
4161 let anchor = snapshot.anchor_before(selection.end);
4162 new_selections.push((selection.map(|_| anchor), text.len()));
4163 new_autoclose_regions.push((
4164 anchor,
4165 text.len(),
4166 selection.id,
4167 bracket_pair.clone(),
4168 ));
4169 edits.push((
4170 selection.range(),
4171 format!("{}{}", text, bracket_pair.end).into(),
4172 ));
4173 bracket_inserted = true;
4174 continue;
4175 }
4176 }
4177
4178 if let Some(region) = autoclose_region {
4179 // If the selection is followed by an auto-inserted closing bracket,
4180 // then don't insert that closing bracket again; just move the selection
4181 // past the closing bracket.
4182 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4183 && text.as_ref() == region.pair.end.as_str()
4184 && snapshot.contains_str_at(region.range.end, text.as_ref());
4185 if should_skip {
4186 let anchor = snapshot.anchor_after(selection.end);
4187 new_selections
4188 .push((selection.map(|_| anchor), region.pair.end.len()));
4189 continue;
4190 }
4191 }
4192
4193 let always_treat_brackets_as_autoclosed = snapshot
4194 .language_settings_at(selection.start, cx)
4195 .always_treat_brackets_as_autoclosed;
4196 if always_treat_brackets_as_autoclosed
4197 && is_bracket_pair_end
4198 && snapshot.contains_str_at(selection.end, text.as_ref())
4199 {
4200 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4201 // and the inserted text is a closing bracket and the selection is followed
4202 // by the closing bracket then move the selection past the closing bracket.
4203 let anchor = snapshot.anchor_after(selection.end);
4204 new_selections.push((selection.map(|_| anchor), text.len()));
4205 continue;
4206 }
4207 }
4208 // If an opening bracket is 1 character long and is typed while
4209 // text is selected, then surround that text with the bracket pair.
4210 else if auto_surround
4211 && bracket_pair.surround
4212 && is_bracket_pair_start
4213 && bracket_pair.start.chars().count() == 1
4214 {
4215 edits.push((selection.start..selection.start, text.clone()));
4216 edits.push((
4217 selection.end..selection.end,
4218 bracket_pair.end.as_str().into(),
4219 ));
4220 bracket_inserted = true;
4221 new_selections.push((
4222 Selection {
4223 id: selection.id,
4224 start: snapshot.anchor_after(selection.start),
4225 end: snapshot.anchor_before(selection.end),
4226 reversed: selection.reversed,
4227 goal: selection.goal,
4228 },
4229 0,
4230 ));
4231 continue;
4232 }
4233 }
4234 }
4235
4236 if self.auto_replace_emoji_shortcode
4237 && selection.is_empty()
4238 && text.as_ref().ends_with(':')
4239 && let Some(possible_emoji_short_code) =
4240 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4241 && !possible_emoji_short_code.is_empty()
4242 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4243 {
4244 let emoji_shortcode_start = Point::new(
4245 selection.start.row,
4246 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4247 );
4248
4249 // Remove shortcode from buffer
4250 edits.push((
4251 emoji_shortcode_start..selection.start,
4252 "".to_string().into(),
4253 ));
4254 new_selections.push((
4255 Selection {
4256 id: selection.id,
4257 start: snapshot.anchor_after(emoji_shortcode_start),
4258 end: snapshot.anchor_before(selection.start),
4259 reversed: selection.reversed,
4260 goal: selection.goal,
4261 },
4262 0,
4263 ));
4264
4265 // Insert emoji
4266 let selection_start_anchor = snapshot.anchor_after(selection.start);
4267 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4268 edits.push((selection.start..selection.end, emoji.to_string().into()));
4269
4270 continue;
4271 }
4272
4273 // If not handling any auto-close operation, then just replace the selected
4274 // text with the given input and move the selection to the end of the
4275 // newly inserted text.
4276 let anchor = snapshot.anchor_after(selection.end);
4277 if !self.linked_edit_ranges.is_empty() {
4278 let start_anchor = snapshot.anchor_before(selection.start);
4279
4280 let is_word_char = text.chars().next().is_none_or(|char| {
4281 let classifier = snapshot
4282 .char_classifier_at(start_anchor.to_offset(&snapshot))
4283 .scope_context(Some(CharScopeContext::LinkedEdit));
4284 classifier.is_word(char)
4285 });
4286
4287 if is_word_char {
4288 if let Some(ranges) = self
4289 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4290 {
4291 for (buffer, edits) in ranges {
4292 linked_edits
4293 .entry(buffer.clone())
4294 .or_default()
4295 .extend(edits.into_iter().map(|range| (range, text.clone())));
4296 }
4297 }
4298 } else {
4299 clear_linked_edit_ranges = true;
4300 }
4301 }
4302
4303 new_selections.push((selection.map(|_| anchor), 0));
4304 edits.push((selection.start..selection.end, text.clone()));
4305 }
4306
4307 drop(snapshot);
4308
4309 self.transact(window, cx, |this, window, cx| {
4310 if clear_linked_edit_ranges {
4311 this.linked_edit_ranges.clear();
4312 }
4313 let initial_buffer_versions =
4314 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4315
4316 this.buffer.update(cx, |buffer, cx| {
4317 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4318 });
4319 for (buffer, edits) in linked_edits {
4320 buffer.update(cx, |buffer, cx| {
4321 let snapshot = buffer.snapshot();
4322 let edits = edits
4323 .into_iter()
4324 .map(|(range, text)| {
4325 use text::ToPoint as TP;
4326 let end_point = TP::to_point(&range.end, &snapshot);
4327 let start_point = TP::to_point(&range.start, &snapshot);
4328 (start_point..end_point, text)
4329 })
4330 .sorted_by_key(|(range, _)| range.start);
4331 buffer.edit(edits, None, cx);
4332 })
4333 }
4334 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4335 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4336 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4337 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4338 .zip(new_selection_deltas)
4339 .map(|(selection, delta)| Selection {
4340 id: selection.id,
4341 start: selection.start + delta,
4342 end: selection.end + delta,
4343 reversed: selection.reversed,
4344 goal: SelectionGoal::None,
4345 })
4346 .collect::<Vec<_>>();
4347
4348 let mut i = 0;
4349 for (position, delta, selection_id, pair) in new_autoclose_regions {
4350 let position = position.to_offset(map.buffer_snapshot()) + delta;
4351 let start = map.buffer_snapshot().anchor_before(position);
4352 let end = map.buffer_snapshot().anchor_after(position);
4353 while let Some(existing_state) = this.autoclose_regions.get(i) {
4354 match existing_state
4355 .range
4356 .start
4357 .cmp(&start, map.buffer_snapshot())
4358 {
4359 Ordering::Less => i += 1,
4360 Ordering::Greater => break,
4361 Ordering::Equal => {
4362 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4363 Ordering::Less => i += 1,
4364 Ordering::Equal => break,
4365 Ordering::Greater => break,
4366 }
4367 }
4368 }
4369 }
4370 this.autoclose_regions.insert(
4371 i,
4372 AutocloseRegion {
4373 selection_id,
4374 range: start..end,
4375 pair,
4376 },
4377 );
4378 }
4379
4380 let had_active_edit_prediction = this.has_active_edit_prediction();
4381 this.change_selections(
4382 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4383 window,
4384 cx,
4385 |s| s.select(new_selections),
4386 );
4387
4388 if !bracket_inserted
4389 && let Some(on_type_format_task) =
4390 this.trigger_on_type_formatting(text.to_string(), window, cx)
4391 {
4392 on_type_format_task.detach_and_log_err(cx);
4393 }
4394
4395 let editor_settings = EditorSettings::get_global(cx);
4396 if bracket_inserted
4397 && (editor_settings.auto_signature_help
4398 || editor_settings.show_signature_help_after_edits)
4399 {
4400 this.show_signature_help(&ShowSignatureHelp, window, cx);
4401 }
4402
4403 let trigger_in_words =
4404 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4405 if this.hard_wrap.is_some() {
4406 let latest: Range<Point> = this.selections.newest(cx).range();
4407 if latest.is_empty()
4408 && this
4409 .buffer()
4410 .read(cx)
4411 .snapshot(cx)
4412 .line_len(MultiBufferRow(latest.start.row))
4413 == latest.start.column
4414 {
4415 this.rewrap_impl(
4416 RewrapOptions {
4417 override_language_settings: true,
4418 preserve_existing_whitespace: true,
4419 },
4420 cx,
4421 )
4422 }
4423 }
4424 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4425 refresh_linked_ranges(this, window, cx);
4426 this.refresh_edit_prediction(true, false, window, cx);
4427 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4428 });
4429 }
4430
4431 fn find_possible_emoji_shortcode_at_position(
4432 snapshot: &MultiBufferSnapshot,
4433 position: Point,
4434 ) -> Option<String> {
4435 let mut chars = Vec::new();
4436 let mut found_colon = false;
4437 for char in snapshot.reversed_chars_at(position).take(100) {
4438 // Found a possible emoji shortcode in the middle of the buffer
4439 if found_colon {
4440 if char.is_whitespace() {
4441 chars.reverse();
4442 return Some(chars.iter().collect());
4443 }
4444 // If the previous character is not a whitespace, we are in the middle of a word
4445 // and we only want to complete the shortcode if the word is made up of other emojis
4446 let mut containing_word = String::new();
4447 for ch in snapshot
4448 .reversed_chars_at(position)
4449 .skip(chars.len() + 1)
4450 .take(100)
4451 {
4452 if ch.is_whitespace() {
4453 break;
4454 }
4455 containing_word.push(ch);
4456 }
4457 let containing_word = containing_word.chars().rev().collect::<String>();
4458 if util::word_consists_of_emojis(containing_word.as_str()) {
4459 chars.reverse();
4460 return Some(chars.iter().collect());
4461 }
4462 }
4463
4464 if char.is_whitespace() || !char.is_ascii() {
4465 return None;
4466 }
4467 if char == ':' {
4468 found_colon = true;
4469 } else {
4470 chars.push(char);
4471 }
4472 }
4473 // Found a possible emoji shortcode at the beginning of the buffer
4474 chars.reverse();
4475 Some(chars.iter().collect())
4476 }
4477
4478 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4479 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4480 self.transact(window, cx, |this, window, cx| {
4481 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4482 let selections = this.selections.all::<usize>(cx);
4483 let multi_buffer = this.buffer.read(cx);
4484 let buffer = multi_buffer.snapshot(cx);
4485 selections
4486 .iter()
4487 .map(|selection| {
4488 let start_point = selection.start.to_point(&buffer);
4489 let mut existing_indent =
4490 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4491 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4492 let start = selection.start;
4493 let end = selection.end;
4494 let selection_is_empty = start == end;
4495 let language_scope = buffer.language_scope_at(start);
4496 let (
4497 comment_delimiter,
4498 doc_delimiter,
4499 insert_extra_newline,
4500 indent_on_newline,
4501 indent_on_extra_newline,
4502 ) = if let Some(language) = &language_scope {
4503 let mut insert_extra_newline =
4504 insert_extra_newline_brackets(&buffer, start..end, language)
4505 || insert_extra_newline_tree_sitter(&buffer, start..end);
4506
4507 // Comment extension on newline is allowed only for cursor selections
4508 let comment_delimiter = maybe!({
4509 if !selection_is_empty {
4510 return None;
4511 }
4512
4513 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4514 return None;
4515 }
4516
4517 let delimiters = language.line_comment_prefixes();
4518 let max_len_of_delimiter =
4519 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4520 let (snapshot, range) =
4521 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4522
4523 let num_of_whitespaces = snapshot
4524 .chars_for_range(range.clone())
4525 .take_while(|c| c.is_whitespace())
4526 .count();
4527 let comment_candidate = snapshot
4528 .chars_for_range(range.clone())
4529 .skip(num_of_whitespaces)
4530 .take(max_len_of_delimiter)
4531 .collect::<String>();
4532 let (delimiter, trimmed_len) = delimiters
4533 .iter()
4534 .filter_map(|delimiter| {
4535 let prefix = delimiter.trim_end();
4536 if comment_candidate.starts_with(prefix) {
4537 Some((delimiter, prefix.len()))
4538 } else {
4539 None
4540 }
4541 })
4542 .max_by_key(|(_, len)| *len)?;
4543
4544 if let Some(BlockCommentConfig {
4545 start: block_start, ..
4546 }) = language.block_comment()
4547 {
4548 let block_start_trimmed = block_start.trim_end();
4549 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4550 let line_content = snapshot
4551 .chars_for_range(range)
4552 .skip(num_of_whitespaces)
4553 .take(block_start_trimmed.len())
4554 .collect::<String>();
4555
4556 if line_content.starts_with(block_start_trimmed) {
4557 return None;
4558 }
4559 }
4560 }
4561
4562 let cursor_is_placed_after_comment_marker =
4563 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4564 if cursor_is_placed_after_comment_marker {
4565 Some(delimiter.clone())
4566 } else {
4567 None
4568 }
4569 });
4570
4571 let mut indent_on_newline = IndentSize::spaces(0);
4572 let mut indent_on_extra_newline = IndentSize::spaces(0);
4573
4574 let doc_delimiter = maybe!({
4575 if !selection_is_empty {
4576 return None;
4577 }
4578
4579 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4580 return None;
4581 }
4582
4583 let BlockCommentConfig {
4584 start: start_tag,
4585 end: end_tag,
4586 prefix: delimiter,
4587 tab_size: len,
4588 } = language.documentation_comment()?;
4589 let is_within_block_comment = buffer
4590 .language_scope_at(start_point)
4591 .is_some_and(|scope| scope.override_name() == Some("comment"));
4592 if !is_within_block_comment {
4593 return None;
4594 }
4595
4596 let (snapshot, range) =
4597 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4598
4599 let num_of_whitespaces = snapshot
4600 .chars_for_range(range.clone())
4601 .take_while(|c| c.is_whitespace())
4602 .count();
4603
4604 // 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.
4605 let column = start_point.column;
4606 let cursor_is_after_start_tag = {
4607 let start_tag_len = start_tag.len();
4608 let start_tag_line = snapshot
4609 .chars_for_range(range.clone())
4610 .skip(num_of_whitespaces)
4611 .take(start_tag_len)
4612 .collect::<String>();
4613 if start_tag_line.starts_with(start_tag.as_ref()) {
4614 num_of_whitespaces + start_tag_len <= column as usize
4615 } else {
4616 false
4617 }
4618 };
4619
4620 let cursor_is_after_delimiter = {
4621 let delimiter_trim = delimiter.trim_end();
4622 let delimiter_line = snapshot
4623 .chars_for_range(range.clone())
4624 .skip(num_of_whitespaces)
4625 .take(delimiter_trim.len())
4626 .collect::<String>();
4627 if delimiter_line.starts_with(delimiter_trim) {
4628 num_of_whitespaces + delimiter_trim.len() <= column as usize
4629 } else {
4630 false
4631 }
4632 };
4633
4634 let cursor_is_before_end_tag_if_exists = {
4635 let mut char_position = 0u32;
4636 let mut end_tag_offset = None;
4637
4638 'outer: for chunk in snapshot.text_for_range(range) {
4639 if let Some(byte_pos) = chunk.find(&**end_tag) {
4640 let chars_before_match =
4641 chunk[..byte_pos].chars().count() as u32;
4642 end_tag_offset =
4643 Some(char_position + chars_before_match);
4644 break 'outer;
4645 }
4646 char_position += chunk.chars().count() as u32;
4647 }
4648
4649 if let Some(end_tag_offset) = end_tag_offset {
4650 let cursor_is_before_end_tag = column <= end_tag_offset;
4651 if cursor_is_after_start_tag {
4652 if cursor_is_before_end_tag {
4653 insert_extra_newline = true;
4654 }
4655 let cursor_is_at_start_of_end_tag =
4656 column == end_tag_offset;
4657 if cursor_is_at_start_of_end_tag {
4658 indent_on_extra_newline.len = *len;
4659 }
4660 }
4661 cursor_is_before_end_tag
4662 } else {
4663 true
4664 }
4665 };
4666
4667 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4668 && cursor_is_before_end_tag_if_exists
4669 {
4670 if cursor_is_after_start_tag {
4671 indent_on_newline.len = *len;
4672 }
4673 Some(delimiter.clone())
4674 } else {
4675 None
4676 }
4677 });
4678
4679 (
4680 comment_delimiter,
4681 doc_delimiter,
4682 insert_extra_newline,
4683 indent_on_newline,
4684 indent_on_extra_newline,
4685 )
4686 } else {
4687 (
4688 None,
4689 None,
4690 false,
4691 IndentSize::default(),
4692 IndentSize::default(),
4693 )
4694 };
4695
4696 let prevent_auto_indent = doc_delimiter.is_some();
4697 let delimiter = comment_delimiter.or(doc_delimiter);
4698
4699 let capacity_for_delimiter =
4700 delimiter.as_deref().map(str::len).unwrap_or_default();
4701 let mut new_text = String::with_capacity(
4702 1 + capacity_for_delimiter
4703 + existing_indent.len as usize
4704 + indent_on_newline.len as usize
4705 + indent_on_extra_newline.len as usize,
4706 );
4707 new_text.push('\n');
4708 new_text.extend(existing_indent.chars());
4709 new_text.extend(indent_on_newline.chars());
4710
4711 if let Some(delimiter) = &delimiter {
4712 new_text.push_str(delimiter);
4713 }
4714
4715 if insert_extra_newline {
4716 new_text.push('\n');
4717 new_text.extend(existing_indent.chars());
4718 new_text.extend(indent_on_extra_newline.chars());
4719 }
4720
4721 let anchor = buffer.anchor_after(end);
4722 let new_selection = selection.map(|_| anchor);
4723 (
4724 ((start..end, new_text), prevent_auto_indent),
4725 (insert_extra_newline, new_selection),
4726 )
4727 })
4728 .unzip()
4729 };
4730
4731 let mut auto_indent_edits = Vec::new();
4732 let mut edits = Vec::new();
4733 for (edit, prevent_auto_indent) in edits_with_flags {
4734 if prevent_auto_indent {
4735 edits.push(edit);
4736 } else {
4737 auto_indent_edits.push(edit);
4738 }
4739 }
4740 if !edits.is_empty() {
4741 this.edit(edits, cx);
4742 }
4743 if !auto_indent_edits.is_empty() {
4744 this.edit_with_autoindent(auto_indent_edits, cx);
4745 }
4746
4747 let buffer = this.buffer.read(cx).snapshot(cx);
4748 let new_selections = selection_info
4749 .into_iter()
4750 .map(|(extra_newline_inserted, new_selection)| {
4751 let mut cursor = new_selection.end.to_point(&buffer);
4752 if extra_newline_inserted {
4753 cursor.row -= 1;
4754 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4755 }
4756 new_selection.map(|_| cursor)
4757 })
4758 .collect();
4759
4760 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4761 this.refresh_edit_prediction(true, false, window, cx);
4762 });
4763 }
4764
4765 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4766 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4767
4768 let buffer = self.buffer.read(cx);
4769 let snapshot = buffer.snapshot(cx);
4770
4771 let mut edits = Vec::new();
4772 let mut rows = Vec::new();
4773
4774 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4775 let cursor = selection.head();
4776 let row = cursor.row;
4777
4778 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4779
4780 let newline = "\n".to_string();
4781 edits.push((start_of_line..start_of_line, newline));
4782
4783 rows.push(row + rows_inserted as u32);
4784 }
4785
4786 self.transact(window, cx, |editor, window, cx| {
4787 editor.edit(edits, cx);
4788
4789 editor.change_selections(Default::default(), window, cx, |s| {
4790 let mut index = 0;
4791 s.move_cursors_with(|map, _, _| {
4792 let row = rows[index];
4793 index += 1;
4794
4795 let point = Point::new(row, 0);
4796 let boundary = map.next_line_boundary(point).1;
4797 let clipped = map.clip_point(boundary, Bias::Left);
4798
4799 (clipped, SelectionGoal::None)
4800 });
4801 });
4802
4803 let mut indent_edits = Vec::new();
4804 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4805 for row in rows {
4806 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4807 for (row, indent) in indents {
4808 if indent.len == 0 {
4809 continue;
4810 }
4811
4812 let text = match indent.kind {
4813 IndentKind::Space => " ".repeat(indent.len as usize),
4814 IndentKind::Tab => "\t".repeat(indent.len as usize),
4815 };
4816 let point = Point::new(row.0, 0);
4817 indent_edits.push((point..point, text));
4818 }
4819 }
4820 editor.edit(indent_edits, cx);
4821 });
4822 }
4823
4824 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4825 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4826
4827 let buffer = self.buffer.read(cx);
4828 let snapshot = buffer.snapshot(cx);
4829
4830 let mut edits = Vec::new();
4831 let mut rows = Vec::new();
4832 let mut rows_inserted = 0;
4833
4834 for selection in self.selections.all_adjusted(cx) {
4835 let cursor = selection.head();
4836 let row = cursor.row;
4837
4838 let point = Point::new(row + 1, 0);
4839 let start_of_line = snapshot.clip_point(point, Bias::Left);
4840
4841 let newline = "\n".to_string();
4842 edits.push((start_of_line..start_of_line, newline));
4843
4844 rows_inserted += 1;
4845 rows.push(row + rows_inserted);
4846 }
4847
4848 self.transact(window, cx, |editor, window, cx| {
4849 editor.edit(edits, cx);
4850
4851 editor.change_selections(Default::default(), window, cx, |s| {
4852 let mut index = 0;
4853 s.move_cursors_with(|map, _, _| {
4854 let row = rows[index];
4855 index += 1;
4856
4857 let point = Point::new(row, 0);
4858 let boundary = map.next_line_boundary(point).1;
4859 let clipped = map.clip_point(boundary, Bias::Left);
4860
4861 (clipped, SelectionGoal::None)
4862 });
4863 });
4864
4865 let mut indent_edits = Vec::new();
4866 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4867 for row in rows {
4868 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4869 for (row, indent) in indents {
4870 if indent.len == 0 {
4871 continue;
4872 }
4873
4874 let text = match indent.kind {
4875 IndentKind::Space => " ".repeat(indent.len as usize),
4876 IndentKind::Tab => "\t".repeat(indent.len as usize),
4877 };
4878 let point = Point::new(row.0, 0);
4879 indent_edits.push((point..point, text));
4880 }
4881 }
4882 editor.edit(indent_edits, cx);
4883 });
4884 }
4885
4886 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4887 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4888 original_indent_columns: Vec::new(),
4889 });
4890 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4891 }
4892
4893 fn insert_with_autoindent_mode(
4894 &mut self,
4895 text: &str,
4896 autoindent_mode: Option<AutoindentMode>,
4897 window: &mut Window,
4898 cx: &mut Context<Self>,
4899 ) {
4900 if self.read_only(cx) {
4901 return;
4902 }
4903
4904 let text: Arc<str> = text.into();
4905 self.transact(window, cx, |this, window, cx| {
4906 let old_selections = this.selections.all_adjusted(cx);
4907 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4908 let anchors = {
4909 let snapshot = buffer.read(cx);
4910 old_selections
4911 .iter()
4912 .map(|s| {
4913 let anchor = snapshot.anchor_after(s.head());
4914 s.map(|_| anchor)
4915 })
4916 .collect::<Vec<_>>()
4917 };
4918 buffer.edit(
4919 old_selections
4920 .iter()
4921 .map(|s| (s.start..s.end, text.clone())),
4922 autoindent_mode,
4923 cx,
4924 );
4925 anchors
4926 });
4927
4928 this.change_selections(Default::default(), window, cx, |s| {
4929 s.select_anchors(selection_anchors);
4930 });
4931
4932 cx.notify();
4933 });
4934 }
4935
4936 fn trigger_completion_on_input(
4937 &mut self,
4938 text: &str,
4939 trigger_in_words: bool,
4940 window: &mut Window,
4941 cx: &mut Context<Self>,
4942 ) {
4943 let completions_source = self
4944 .context_menu
4945 .borrow()
4946 .as_ref()
4947 .and_then(|menu| match menu {
4948 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4949 CodeContextMenu::CodeActions(_) => None,
4950 });
4951
4952 match completions_source {
4953 Some(CompletionsMenuSource::Words { .. }) => {
4954 self.open_or_update_completions_menu(
4955 Some(CompletionsMenuSource::Words {
4956 ignore_threshold: false,
4957 }),
4958 None,
4959 window,
4960 cx,
4961 );
4962 }
4963 Some(CompletionsMenuSource::Normal)
4964 | Some(CompletionsMenuSource::SnippetChoices)
4965 | None
4966 if self.is_completion_trigger(
4967 text,
4968 trigger_in_words,
4969 completions_source.is_some(),
4970 cx,
4971 ) =>
4972 {
4973 self.show_completions(
4974 &ShowCompletions {
4975 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4976 },
4977 window,
4978 cx,
4979 )
4980 }
4981 _ => {
4982 self.hide_context_menu(window, cx);
4983 }
4984 }
4985 }
4986
4987 fn is_completion_trigger(
4988 &self,
4989 text: &str,
4990 trigger_in_words: bool,
4991 menu_is_open: bool,
4992 cx: &mut Context<Self>,
4993 ) -> bool {
4994 let position = self.selections.newest_anchor().head();
4995 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4996 return false;
4997 };
4998
4999 if let Some(completion_provider) = &self.completion_provider {
5000 completion_provider.is_completion_trigger(
5001 &buffer,
5002 position.text_anchor,
5003 text,
5004 trigger_in_words,
5005 menu_is_open,
5006 cx,
5007 )
5008 } else {
5009 false
5010 }
5011 }
5012
5013 /// If any empty selections is touching the start of its innermost containing autoclose
5014 /// region, expand it to select the brackets.
5015 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5016 let selections = self.selections.all::<usize>(cx);
5017 let buffer = self.buffer.read(cx).read(cx);
5018 let new_selections = self
5019 .selections_with_autoclose_regions(selections, &buffer)
5020 .map(|(mut selection, region)| {
5021 if !selection.is_empty() {
5022 return selection;
5023 }
5024
5025 if let Some(region) = region {
5026 let mut range = region.range.to_offset(&buffer);
5027 if selection.start == range.start && range.start >= region.pair.start.len() {
5028 range.start -= region.pair.start.len();
5029 if buffer.contains_str_at(range.start, ®ion.pair.start)
5030 && buffer.contains_str_at(range.end, ®ion.pair.end)
5031 {
5032 range.end += region.pair.end.len();
5033 selection.start = range.start;
5034 selection.end = range.end;
5035
5036 return selection;
5037 }
5038 }
5039 }
5040
5041 let always_treat_brackets_as_autoclosed = buffer
5042 .language_settings_at(selection.start, cx)
5043 .always_treat_brackets_as_autoclosed;
5044
5045 if !always_treat_brackets_as_autoclosed {
5046 return selection;
5047 }
5048
5049 if let Some(scope) = buffer.language_scope_at(selection.start) {
5050 for (pair, enabled) in scope.brackets() {
5051 if !enabled || !pair.close {
5052 continue;
5053 }
5054
5055 if buffer.contains_str_at(selection.start, &pair.end) {
5056 let pair_start_len = pair.start.len();
5057 if buffer.contains_str_at(
5058 selection.start.saturating_sub(pair_start_len),
5059 &pair.start,
5060 ) {
5061 selection.start -= pair_start_len;
5062 selection.end += pair.end.len();
5063
5064 return selection;
5065 }
5066 }
5067 }
5068 }
5069
5070 selection
5071 })
5072 .collect();
5073
5074 drop(buffer);
5075 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5076 selections.select(new_selections)
5077 });
5078 }
5079
5080 /// Iterate the given selections, and for each one, find the smallest surrounding
5081 /// autoclose region. This uses the ordering of the selections and the autoclose
5082 /// regions to avoid repeated comparisons.
5083 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5084 &'a self,
5085 selections: impl IntoIterator<Item = Selection<D>>,
5086 buffer: &'a MultiBufferSnapshot,
5087 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5088 let mut i = 0;
5089 let mut regions = self.autoclose_regions.as_slice();
5090 selections.into_iter().map(move |selection| {
5091 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5092
5093 let mut enclosing = None;
5094 while let Some(pair_state) = regions.get(i) {
5095 if pair_state.range.end.to_offset(buffer) < range.start {
5096 regions = ®ions[i + 1..];
5097 i = 0;
5098 } else if pair_state.range.start.to_offset(buffer) > range.end {
5099 break;
5100 } else {
5101 if pair_state.selection_id == selection.id {
5102 enclosing = Some(pair_state);
5103 }
5104 i += 1;
5105 }
5106 }
5107
5108 (selection, enclosing)
5109 })
5110 }
5111
5112 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5113 fn invalidate_autoclose_regions(
5114 &mut self,
5115 mut selections: &[Selection<Anchor>],
5116 buffer: &MultiBufferSnapshot,
5117 ) {
5118 self.autoclose_regions.retain(|state| {
5119 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5120 return false;
5121 }
5122
5123 let mut i = 0;
5124 while let Some(selection) = selections.get(i) {
5125 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5126 selections = &selections[1..];
5127 continue;
5128 }
5129 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5130 break;
5131 }
5132 if selection.id == state.selection_id {
5133 return true;
5134 } else {
5135 i += 1;
5136 }
5137 }
5138 false
5139 });
5140 }
5141
5142 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5143 let offset = position.to_offset(buffer);
5144 let (word_range, kind) =
5145 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5146 if offset > word_range.start && kind == Some(CharKind::Word) {
5147 Some(
5148 buffer
5149 .text_for_range(word_range.start..offset)
5150 .collect::<String>(),
5151 )
5152 } else {
5153 None
5154 }
5155 }
5156
5157 pub fn toggle_inline_values(
5158 &mut self,
5159 _: &ToggleInlineValues,
5160 _: &mut Window,
5161 cx: &mut Context<Self>,
5162 ) {
5163 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5164
5165 self.refresh_inline_values(cx);
5166 }
5167
5168 pub fn toggle_inlay_hints(
5169 &mut self,
5170 _: &ToggleInlayHints,
5171 _: &mut Window,
5172 cx: &mut Context<Self>,
5173 ) {
5174 self.refresh_inlay_hints(
5175 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5176 cx,
5177 );
5178 }
5179
5180 pub fn inlay_hints_enabled(&self) -> bool {
5181 self.inlay_hint_cache.enabled
5182 }
5183
5184 pub fn inline_values_enabled(&self) -> bool {
5185 self.inline_value_cache.enabled
5186 }
5187
5188 #[cfg(any(test, feature = "test-support"))]
5189 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5190 self.display_map
5191 .read(cx)
5192 .current_inlays()
5193 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5194 .cloned()
5195 .collect()
5196 }
5197
5198 #[cfg(any(test, feature = "test-support"))]
5199 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5200 self.display_map
5201 .read(cx)
5202 .current_inlays()
5203 .cloned()
5204 .collect()
5205 }
5206
5207 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5208 if self.semantics_provider.is_none() || !self.mode.is_full() {
5209 return;
5210 }
5211
5212 let reason_description = reason.description();
5213 let ignore_debounce = matches!(
5214 reason,
5215 InlayHintRefreshReason::SettingsChange(_)
5216 | InlayHintRefreshReason::Toggle(_)
5217 | InlayHintRefreshReason::ExcerptsRemoved(_)
5218 | InlayHintRefreshReason::ModifiersChanged(_)
5219 );
5220 let (invalidate_cache, required_languages) = match reason {
5221 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5222 match self.inlay_hint_cache.modifiers_override(enabled) {
5223 Some(enabled) => {
5224 if enabled {
5225 (InvalidationStrategy::RefreshRequested, None)
5226 } else {
5227 self.clear_inlay_hints(cx);
5228 return;
5229 }
5230 }
5231 None => return,
5232 }
5233 }
5234 InlayHintRefreshReason::Toggle(enabled) => {
5235 if self.inlay_hint_cache.toggle(enabled) {
5236 if enabled {
5237 (InvalidationStrategy::RefreshRequested, None)
5238 } else {
5239 self.clear_inlay_hints(cx);
5240 return;
5241 }
5242 } else {
5243 return;
5244 }
5245 }
5246 InlayHintRefreshReason::SettingsChange(new_settings) => {
5247 match self.inlay_hint_cache.update_settings(
5248 &self.buffer,
5249 new_settings,
5250 self.visible_inlay_hints(cx).cloned().collect::<Vec<_>>(),
5251 cx,
5252 ) {
5253 ControlFlow::Break(Some(InlaySplice {
5254 to_remove,
5255 to_insert,
5256 })) => {
5257 self.splice_inlays(&to_remove, to_insert, cx);
5258 return;
5259 }
5260 ControlFlow::Break(None) => return,
5261 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5262 }
5263 }
5264 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5265 if let Some(InlaySplice {
5266 to_remove,
5267 to_insert,
5268 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5269 {
5270 self.splice_inlays(&to_remove, to_insert, cx);
5271 }
5272 self.display_map.update(cx, |display_map, _| {
5273 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5274 });
5275 return;
5276 }
5277 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5278 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5279 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5280 }
5281 InlayHintRefreshReason::RefreshRequested => {
5282 (InvalidationStrategy::RefreshRequested, None)
5283 }
5284 };
5285
5286 let mut visible_excerpts = self.visible_excerpts(required_languages.as_ref(), cx);
5287 visible_excerpts.retain(|_, (buffer, _, _)| {
5288 self.registered_buffers
5289 .contains_key(&buffer.read(cx).remote_id())
5290 });
5291
5292 if let Some(InlaySplice {
5293 to_remove,
5294 to_insert,
5295 }) = self.inlay_hint_cache.spawn_hint_refresh(
5296 reason_description,
5297 visible_excerpts,
5298 invalidate_cache,
5299 ignore_debounce,
5300 cx,
5301 ) {
5302 self.splice_inlays(&to_remove, to_insert, cx);
5303 }
5304 }
5305
5306 pub fn clear_inlay_hints(&self, cx: &mut Context<Editor>) {
5307 self.splice_inlays(
5308 &self
5309 .visible_inlay_hints(cx)
5310 .map(|inlay| inlay.id)
5311 .collect::<Vec<_>>(),
5312 Vec::new(),
5313 cx,
5314 );
5315 }
5316
5317 fn visible_inlay_hints<'a>(
5318 &'a self,
5319 cx: &'a Context<Editor>,
5320 ) -> impl Iterator<Item = &'a Inlay> {
5321 self.display_map
5322 .read(cx)
5323 .current_inlays()
5324 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5325 }
5326
5327 pub fn visible_excerpts(
5328 &self,
5329 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5330 cx: &mut Context<Editor>,
5331 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5332 let Some(project) = self.project() else {
5333 return HashMap::default();
5334 };
5335 let project = project.read(cx);
5336 let multi_buffer = self.buffer().read(cx);
5337 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5338 let multi_buffer_visible_start = self
5339 .scroll_manager
5340 .anchor()
5341 .anchor
5342 .to_point(&multi_buffer_snapshot);
5343 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5344 multi_buffer_visible_start
5345 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5346 Bias::Left,
5347 );
5348 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5349 multi_buffer_snapshot
5350 .range_to_buffer_ranges(multi_buffer_visible_range)
5351 .into_iter()
5352 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5353 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5354 let buffer_file = project::File::from_dyn(buffer.file())?;
5355 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5356 let worktree_entry = buffer_worktree
5357 .read(cx)
5358 .entry_for_id(buffer_file.project_entry_id()?)?;
5359 if worktree_entry.is_ignored {
5360 return None;
5361 }
5362
5363 let language = buffer.language()?;
5364 if let Some(restrict_to_languages) = restrict_to_languages
5365 && !restrict_to_languages.contains(language)
5366 {
5367 return None;
5368 }
5369 Some((
5370 excerpt_id,
5371 (
5372 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5373 buffer.version().clone(),
5374 excerpt_visible_range,
5375 ),
5376 ))
5377 })
5378 .collect()
5379 }
5380
5381 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5382 TextLayoutDetails {
5383 text_system: window.text_system().clone(),
5384 editor_style: self.style.clone().unwrap(),
5385 rem_size: window.rem_size(),
5386 scroll_anchor: self.scroll_manager.anchor(),
5387 visible_rows: self.visible_line_count(),
5388 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5389 }
5390 }
5391
5392 pub fn splice_inlays(
5393 &self,
5394 to_remove: &[InlayId],
5395 to_insert: Vec<Inlay>,
5396 cx: &mut Context<Self>,
5397 ) {
5398 self.display_map.update(cx, |display_map, cx| {
5399 display_map.splice_inlays(to_remove, to_insert, cx)
5400 });
5401 cx.notify();
5402 }
5403
5404 fn trigger_on_type_formatting(
5405 &self,
5406 input: String,
5407 window: &mut Window,
5408 cx: &mut Context<Self>,
5409 ) -> Option<Task<Result<()>>> {
5410 if input.len() != 1 {
5411 return None;
5412 }
5413
5414 let project = self.project()?;
5415 let position = self.selections.newest_anchor().head();
5416 let (buffer, buffer_position) = self
5417 .buffer
5418 .read(cx)
5419 .text_anchor_for_position(position, cx)?;
5420
5421 let settings = language_settings::language_settings(
5422 buffer
5423 .read(cx)
5424 .language_at(buffer_position)
5425 .map(|l| l.name()),
5426 buffer.read(cx).file(),
5427 cx,
5428 );
5429 if !settings.use_on_type_format {
5430 return None;
5431 }
5432
5433 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5434 // hence we do LSP request & edit on host side only — add formats to host's history.
5435 let push_to_lsp_host_history = true;
5436 // If this is not the host, append its history with new edits.
5437 let push_to_client_history = project.read(cx).is_via_collab();
5438
5439 let on_type_formatting = project.update(cx, |project, cx| {
5440 project.on_type_format(
5441 buffer.clone(),
5442 buffer_position,
5443 input,
5444 push_to_lsp_host_history,
5445 cx,
5446 )
5447 });
5448 Some(cx.spawn_in(window, async move |editor, cx| {
5449 if let Some(transaction) = on_type_formatting.await? {
5450 if push_to_client_history {
5451 buffer
5452 .update(cx, |buffer, _| {
5453 buffer.push_transaction(transaction, Instant::now());
5454 buffer.finalize_last_transaction();
5455 })
5456 .ok();
5457 }
5458 editor.update(cx, |editor, cx| {
5459 editor.refresh_document_highlights(cx);
5460 })?;
5461 }
5462 Ok(())
5463 }))
5464 }
5465
5466 pub fn show_word_completions(
5467 &mut self,
5468 _: &ShowWordCompletions,
5469 window: &mut Window,
5470 cx: &mut Context<Self>,
5471 ) {
5472 self.open_or_update_completions_menu(
5473 Some(CompletionsMenuSource::Words {
5474 ignore_threshold: true,
5475 }),
5476 None,
5477 window,
5478 cx,
5479 );
5480 }
5481
5482 pub fn show_completions(
5483 &mut self,
5484 options: &ShowCompletions,
5485 window: &mut Window,
5486 cx: &mut Context<Self>,
5487 ) {
5488 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5489 }
5490
5491 fn open_or_update_completions_menu(
5492 &mut self,
5493 requested_source: Option<CompletionsMenuSource>,
5494 trigger: Option<&str>,
5495 window: &mut Window,
5496 cx: &mut Context<Self>,
5497 ) {
5498 if self.pending_rename.is_some() {
5499 return;
5500 }
5501
5502 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5503
5504 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5505 // inserted and selected. To handle that case, the start of the selection is used so that
5506 // the menu starts with all choices.
5507 let position = self
5508 .selections
5509 .newest_anchor()
5510 .start
5511 .bias_right(&multibuffer_snapshot);
5512 if position.diff_base_anchor.is_some() {
5513 return;
5514 }
5515 let buffer_position = multibuffer_snapshot.anchor_before(position);
5516 let Some(buffer) = buffer_position
5517 .buffer_id
5518 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5519 else {
5520 return;
5521 };
5522 let buffer_snapshot = buffer.read(cx).snapshot();
5523
5524 let query: Option<Arc<String>> =
5525 Self::completion_query(&multibuffer_snapshot, buffer_position)
5526 .map(|query| query.into());
5527
5528 drop(multibuffer_snapshot);
5529
5530 // Hide the current completions menu when query is empty. Without this, cached
5531 // completions from before the trigger char may be reused (#32774).
5532 if query.is_none() {
5533 let menu_is_open = matches!(
5534 self.context_menu.borrow().as_ref(),
5535 Some(CodeContextMenu::Completions(_))
5536 );
5537 if menu_is_open {
5538 self.hide_context_menu(window, cx);
5539 }
5540 }
5541
5542 let mut ignore_word_threshold = false;
5543 let provider = match requested_source {
5544 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5545 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5546 ignore_word_threshold = ignore_threshold;
5547 None
5548 }
5549 Some(CompletionsMenuSource::SnippetChoices) => {
5550 log::error!("bug: SnippetChoices requested_source is not handled");
5551 None
5552 }
5553 };
5554
5555 let sort_completions = provider
5556 .as_ref()
5557 .is_some_and(|provider| provider.sort_completions());
5558
5559 let filter_completions = provider
5560 .as_ref()
5561 .is_none_or(|provider| provider.filter_completions());
5562
5563 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5564 if filter_completions {
5565 menu.filter(query.clone(), provider.clone(), window, cx);
5566 }
5567 // When `is_incomplete` is false, no need to re-query completions when the current query
5568 // is a suffix of the initial query.
5569 if !menu.is_incomplete {
5570 // If the new query is a suffix of the old query (typing more characters) and
5571 // the previous result was complete, the existing completions can be filtered.
5572 //
5573 // Note that this is always true for snippet completions.
5574 let query_matches = match (&menu.initial_query, &query) {
5575 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5576 (None, _) => true,
5577 _ => false,
5578 };
5579 if query_matches {
5580 let position_matches = if menu.initial_position == position {
5581 true
5582 } else {
5583 let snapshot = self.buffer.read(cx).read(cx);
5584 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5585 };
5586 if position_matches {
5587 return;
5588 }
5589 }
5590 }
5591 };
5592
5593 let trigger_kind = match trigger {
5594 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5595 CompletionTriggerKind::TRIGGER_CHARACTER
5596 }
5597 _ => CompletionTriggerKind::INVOKED,
5598 };
5599 let completion_context = CompletionContext {
5600 trigger_character: trigger.and_then(|trigger| {
5601 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5602 Some(String::from(trigger))
5603 } else {
5604 None
5605 }
5606 }),
5607 trigger_kind,
5608 };
5609
5610 let Anchor {
5611 excerpt_id: buffer_excerpt_id,
5612 text_anchor: buffer_position,
5613 ..
5614 } = buffer_position;
5615
5616 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5617 buffer_snapshot.surrounding_word(buffer_position, None)
5618 {
5619 let word_to_exclude = buffer_snapshot
5620 .text_for_range(word_range.clone())
5621 .collect::<String>();
5622 (
5623 buffer_snapshot.anchor_before(word_range.start)
5624 ..buffer_snapshot.anchor_after(buffer_position),
5625 Some(word_to_exclude),
5626 )
5627 } else {
5628 (buffer_position..buffer_position, None)
5629 };
5630
5631 let language = buffer_snapshot
5632 .language_at(buffer_position)
5633 .map(|language| language.name());
5634
5635 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5636 .completions
5637 .clone();
5638
5639 let show_completion_documentation = buffer_snapshot
5640 .settings_at(buffer_position, cx)
5641 .show_completion_documentation;
5642
5643 // The document can be large, so stay in reasonable bounds when searching for words,
5644 // otherwise completion pop-up might be slow to appear.
5645 const WORD_LOOKUP_ROWS: u32 = 5_000;
5646 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5647 let min_word_search = buffer_snapshot.clip_point(
5648 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5649 Bias::Left,
5650 );
5651 let max_word_search = buffer_snapshot.clip_point(
5652 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5653 Bias::Right,
5654 );
5655 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5656 ..buffer_snapshot.point_to_offset(max_word_search);
5657
5658 let skip_digits = query
5659 .as_ref()
5660 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5661
5662 let omit_word_completions = !self.word_completions_enabled
5663 || (!ignore_word_threshold
5664 && match &query {
5665 Some(query) => query.chars().count() < completion_settings.words_min_length,
5666 None => completion_settings.words_min_length != 0,
5667 });
5668
5669 let (mut words, provider_responses) = match &provider {
5670 Some(provider) => {
5671 let provider_responses = provider.completions(
5672 buffer_excerpt_id,
5673 &buffer,
5674 buffer_position,
5675 completion_context,
5676 window,
5677 cx,
5678 );
5679
5680 let words = match (omit_word_completions, completion_settings.words) {
5681 (true, _) | (_, WordsCompletionMode::Disabled) => {
5682 Task::ready(BTreeMap::default())
5683 }
5684 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5685 .background_spawn(async move {
5686 buffer_snapshot.words_in_range(WordsQuery {
5687 fuzzy_contents: None,
5688 range: word_search_range,
5689 skip_digits,
5690 })
5691 }),
5692 };
5693
5694 (words, provider_responses)
5695 }
5696 None => {
5697 let words = if omit_word_completions {
5698 Task::ready(BTreeMap::default())
5699 } else {
5700 cx.background_spawn(async move {
5701 buffer_snapshot.words_in_range(WordsQuery {
5702 fuzzy_contents: None,
5703 range: word_search_range,
5704 skip_digits,
5705 })
5706 })
5707 };
5708 (words, Task::ready(Ok(Vec::new())))
5709 }
5710 };
5711
5712 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5713
5714 let id = post_inc(&mut self.next_completion_id);
5715 let task = cx.spawn_in(window, async move |editor, cx| {
5716 let Ok(()) = editor.update(cx, |this, _| {
5717 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5718 }) else {
5719 return;
5720 };
5721
5722 // TODO: Ideally completions from different sources would be selectively re-queried, so
5723 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5724 let mut completions = Vec::new();
5725 let mut is_incomplete = false;
5726 let mut display_options: Option<CompletionDisplayOptions> = None;
5727 if let Some(provider_responses) = provider_responses.await.log_err()
5728 && !provider_responses.is_empty()
5729 {
5730 for response in provider_responses {
5731 completions.extend(response.completions);
5732 is_incomplete = is_incomplete || response.is_incomplete;
5733 match display_options.as_mut() {
5734 None => {
5735 display_options = Some(response.display_options);
5736 }
5737 Some(options) => options.merge(&response.display_options),
5738 }
5739 }
5740 if completion_settings.words == WordsCompletionMode::Fallback {
5741 words = Task::ready(BTreeMap::default());
5742 }
5743 }
5744 let display_options = display_options.unwrap_or_default();
5745
5746 let mut words = words.await;
5747 if let Some(word_to_exclude) = &word_to_exclude {
5748 words.remove(word_to_exclude);
5749 }
5750 for lsp_completion in &completions {
5751 words.remove(&lsp_completion.new_text);
5752 }
5753 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5754 replace_range: word_replace_range.clone(),
5755 new_text: word.clone(),
5756 label: CodeLabel::plain(word, None),
5757 icon_path: None,
5758 documentation: None,
5759 source: CompletionSource::BufferWord {
5760 word_range,
5761 resolved: false,
5762 },
5763 insert_text_mode: Some(InsertTextMode::AS_IS),
5764 confirm: None,
5765 }));
5766
5767 let menu = if completions.is_empty() {
5768 None
5769 } else {
5770 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5771 let languages = editor
5772 .workspace
5773 .as_ref()
5774 .and_then(|(workspace, _)| workspace.upgrade())
5775 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5776 let menu = CompletionsMenu::new(
5777 id,
5778 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5779 sort_completions,
5780 show_completion_documentation,
5781 position,
5782 query.clone(),
5783 is_incomplete,
5784 buffer.clone(),
5785 completions.into(),
5786 display_options,
5787 snippet_sort_order,
5788 languages,
5789 language,
5790 cx,
5791 );
5792
5793 let query = if filter_completions { query } else { None };
5794 let matches_task = if let Some(query) = query {
5795 menu.do_async_filtering(query, cx)
5796 } else {
5797 Task::ready(menu.unfiltered_matches())
5798 };
5799 (menu, matches_task)
5800 }) else {
5801 return;
5802 };
5803
5804 let matches = matches_task.await;
5805
5806 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5807 // Newer menu already set, so exit.
5808 if let Some(CodeContextMenu::Completions(prev_menu)) =
5809 editor.context_menu.borrow().as_ref()
5810 && prev_menu.id > id
5811 {
5812 return;
5813 };
5814
5815 // Only valid to take prev_menu because it the new menu is immediately set
5816 // below, or the menu is hidden.
5817 if let Some(CodeContextMenu::Completions(prev_menu)) =
5818 editor.context_menu.borrow_mut().take()
5819 {
5820 let position_matches =
5821 if prev_menu.initial_position == menu.initial_position {
5822 true
5823 } else {
5824 let snapshot = editor.buffer.read(cx).read(cx);
5825 prev_menu.initial_position.to_offset(&snapshot)
5826 == menu.initial_position.to_offset(&snapshot)
5827 };
5828 if position_matches {
5829 // Preserve markdown cache before `set_filter_results` because it will
5830 // try to populate the documentation cache.
5831 menu.preserve_markdown_cache(prev_menu);
5832 }
5833 };
5834
5835 menu.set_filter_results(matches, provider, window, cx);
5836 }) else {
5837 return;
5838 };
5839
5840 menu.visible().then_some(menu)
5841 };
5842
5843 editor
5844 .update_in(cx, |editor, window, cx| {
5845 if editor.focus_handle.is_focused(window)
5846 && let Some(menu) = menu
5847 {
5848 *editor.context_menu.borrow_mut() =
5849 Some(CodeContextMenu::Completions(menu));
5850
5851 crate::hover_popover::hide_hover(editor, cx);
5852 if editor.show_edit_predictions_in_menu() {
5853 editor.update_visible_edit_prediction(window, cx);
5854 } else {
5855 editor.discard_edit_prediction(false, cx);
5856 }
5857
5858 cx.notify();
5859 return;
5860 }
5861
5862 if editor.completion_tasks.len() <= 1 {
5863 // If there are no more completion tasks and the last menu was empty, we should hide it.
5864 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5865 // If it was already hidden and we don't show edit predictions in the menu,
5866 // we should also show the edit prediction when available.
5867 if was_hidden && editor.show_edit_predictions_in_menu() {
5868 editor.update_visible_edit_prediction(window, cx);
5869 }
5870 }
5871 })
5872 .ok();
5873 });
5874
5875 self.completion_tasks.push((id, task));
5876 }
5877
5878 #[cfg(feature = "test-support")]
5879 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5880 let menu = self.context_menu.borrow();
5881 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5882 let completions = menu.completions.borrow();
5883 Some(completions.to_vec())
5884 } else {
5885 None
5886 }
5887 }
5888
5889 pub fn with_completions_menu_matching_id<R>(
5890 &self,
5891 id: CompletionId,
5892 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5893 ) -> R {
5894 let mut context_menu = self.context_menu.borrow_mut();
5895 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5896 return f(None);
5897 };
5898 if completions_menu.id != id {
5899 return f(None);
5900 }
5901 f(Some(completions_menu))
5902 }
5903
5904 pub fn confirm_completion(
5905 &mut self,
5906 action: &ConfirmCompletion,
5907 window: &mut Window,
5908 cx: &mut Context<Self>,
5909 ) -> Option<Task<Result<()>>> {
5910 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5911 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5912 }
5913
5914 pub fn confirm_completion_insert(
5915 &mut self,
5916 _: &ConfirmCompletionInsert,
5917 window: &mut Window,
5918 cx: &mut Context<Self>,
5919 ) -> Option<Task<Result<()>>> {
5920 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5921 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5922 }
5923
5924 pub fn confirm_completion_replace(
5925 &mut self,
5926 _: &ConfirmCompletionReplace,
5927 window: &mut Window,
5928 cx: &mut Context<Self>,
5929 ) -> Option<Task<Result<()>>> {
5930 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5931 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5932 }
5933
5934 pub fn compose_completion(
5935 &mut self,
5936 action: &ComposeCompletion,
5937 window: &mut Window,
5938 cx: &mut Context<Self>,
5939 ) -> Option<Task<Result<()>>> {
5940 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5941 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5942 }
5943
5944 fn do_completion(
5945 &mut self,
5946 item_ix: Option<usize>,
5947 intent: CompletionIntent,
5948 window: &mut Window,
5949 cx: &mut Context<Editor>,
5950 ) -> Option<Task<Result<()>>> {
5951 use language::ToOffset as _;
5952
5953 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5954 else {
5955 return None;
5956 };
5957
5958 let candidate_id = {
5959 let entries = completions_menu.entries.borrow();
5960 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5961 if self.show_edit_predictions_in_menu() {
5962 self.discard_edit_prediction(true, cx);
5963 }
5964 mat.candidate_id
5965 };
5966
5967 let completion = completions_menu
5968 .completions
5969 .borrow()
5970 .get(candidate_id)?
5971 .clone();
5972 cx.stop_propagation();
5973
5974 let buffer_handle = completions_menu.buffer.clone();
5975
5976 let CompletionEdit {
5977 new_text,
5978 snippet,
5979 replace_range,
5980 } = process_completion_for_edit(
5981 &completion,
5982 intent,
5983 &buffer_handle,
5984 &completions_menu.initial_position.text_anchor,
5985 cx,
5986 );
5987
5988 let buffer = buffer_handle.read(cx);
5989 let snapshot = self.buffer.read(cx).snapshot(cx);
5990 let newest_anchor = self.selections.newest_anchor();
5991 let replace_range_multibuffer = {
5992 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5993 excerpt.map_range_from_buffer(replace_range.clone())
5994 };
5995 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5996 return None;
5997 }
5998
5999 let old_text = buffer
6000 .text_for_range(replace_range.clone())
6001 .collect::<String>();
6002 let lookbehind = newest_anchor
6003 .start
6004 .text_anchor
6005 .to_offset(buffer)
6006 .saturating_sub(replace_range.start);
6007 let lookahead = replace_range
6008 .end
6009 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6010 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6011 let suffix = &old_text[lookbehind.min(old_text.len())..];
6012
6013 let selections = self.selections.all::<usize>(cx);
6014 let mut ranges = Vec::new();
6015 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6016
6017 for selection in &selections {
6018 let range = if selection.id == newest_anchor.id {
6019 replace_range_multibuffer.clone()
6020 } else {
6021 let mut range = selection.range();
6022
6023 // if prefix is present, don't duplicate it
6024 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
6025 range.start = range.start.saturating_sub(lookbehind);
6026
6027 // if suffix is also present, mimic the newest cursor and replace it
6028 if selection.id != newest_anchor.id
6029 && snapshot.contains_str_at(range.end, suffix)
6030 {
6031 range.end += lookahead;
6032 }
6033 }
6034 range
6035 };
6036
6037 ranges.push(range.clone());
6038
6039 if !self.linked_edit_ranges.is_empty() {
6040 let start_anchor = snapshot.anchor_before(range.start);
6041 let end_anchor = snapshot.anchor_after(range.end);
6042 if let Some(ranges) = self
6043 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6044 {
6045 for (buffer, edits) in ranges {
6046 linked_edits
6047 .entry(buffer.clone())
6048 .or_default()
6049 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6050 }
6051 }
6052 }
6053 }
6054
6055 let common_prefix_len = old_text
6056 .chars()
6057 .zip(new_text.chars())
6058 .take_while(|(a, b)| a == b)
6059 .map(|(a, _)| a.len_utf8())
6060 .sum::<usize>();
6061
6062 cx.emit(EditorEvent::InputHandled {
6063 utf16_range_to_replace: None,
6064 text: new_text[common_prefix_len..].into(),
6065 });
6066
6067 self.transact(window, cx, |editor, window, cx| {
6068 if let Some(mut snippet) = snippet {
6069 snippet.text = new_text.to_string();
6070 editor
6071 .insert_snippet(&ranges, snippet, window, cx)
6072 .log_err();
6073 } else {
6074 editor.buffer.update(cx, |multi_buffer, cx| {
6075 let auto_indent = match completion.insert_text_mode {
6076 Some(InsertTextMode::AS_IS) => None,
6077 _ => editor.autoindent_mode.clone(),
6078 };
6079 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6080 multi_buffer.edit(edits, auto_indent, cx);
6081 });
6082 }
6083 for (buffer, edits) in linked_edits {
6084 buffer.update(cx, |buffer, cx| {
6085 let snapshot = buffer.snapshot();
6086 let edits = edits
6087 .into_iter()
6088 .map(|(range, text)| {
6089 use text::ToPoint as TP;
6090 let end_point = TP::to_point(&range.end, &snapshot);
6091 let start_point = TP::to_point(&range.start, &snapshot);
6092 (start_point..end_point, text)
6093 })
6094 .sorted_by_key(|(range, _)| range.start);
6095 buffer.edit(edits, None, cx);
6096 })
6097 }
6098
6099 editor.refresh_edit_prediction(true, false, window, cx);
6100 });
6101 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6102
6103 let show_new_completions_on_confirm = completion
6104 .confirm
6105 .as_ref()
6106 .is_some_and(|confirm| confirm(intent, window, cx));
6107 if show_new_completions_on_confirm {
6108 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6109 }
6110
6111 let provider = self.completion_provider.as_ref()?;
6112 drop(completion);
6113 let apply_edits = provider.apply_additional_edits_for_completion(
6114 buffer_handle,
6115 completions_menu.completions.clone(),
6116 candidate_id,
6117 true,
6118 cx,
6119 );
6120
6121 let editor_settings = EditorSettings::get_global(cx);
6122 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6123 // After the code completion is finished, users often want to know what signatures are needed.
6124 // so we should automatically call signature_help
6125 self.show_signature_help(&ShowSignatureHelp, window, cx);
6126 }
6127
6128 Some(cx.foreground_executor().spawn(async move {
6129 apply_edits.await?;
6130 Ok(())
6131 }))
6132 }
6133
6134 pub fn toggle_code_actions(
6135 &mut self,
6136 action: &ToggleCodeActions,
6137 window: &mut Window,
6138 cx: &mut Context<Self>,
6139 ) {
6140 let quick_launch = action.quick_launch;
6141 let mut context_menu = self.context_menu.borrow_mut();
6142 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6143 if code_actions.deployed_from == action.deployed_from {
6144 // Toggle if we're selecting the same one
6145 *context_menu = None;
6146 cx.notify();
6147 return;
6148 } else {
6149 // Otherwise, clear it and start a new one
6150 *context_menu = None;
6151 cx.notify();
6152 }
6153 }
6154 drop(context_menu);
6155 let snapshot = self.snapshot(window, cx);
6156 let deployed_from = action.deployed_from.clone();
6157 let action = action.clone();
6158 self.completion_tasks.clear();
6159 self.discard_edit_prediction(false, cx);
6160
6161 let multibuffer_point = match &action.deployed_from {
6162 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6163 DisplayPoint::new(*row, 0).to_point(&snapshot)
6164 }
6165 _ => self.selections.newest::<Point>(cx).head(),
6166 };
6167 let Some((buffer, buffer_row)) = snapshot
6168 .buffer_snapshot()
6169 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6170 .and_then(|(buffer_snapshot, range)| {
6171 self.buffer()
6172 .read(cx)
6173 .buffer(buffer_snapshot.remote_id())
6174 .map(|buffer| (buffer, range.start.row))
6175 })
6176 else {
6177 return;
6178 };
6179 let buffer_id = buffer.read(cx).remote_id();
6180 let tasks = self
6181 .tasks
6182 .get(&(buffer_id, buffer_row))
6183 .map(|t| Arc::new(t.to_owned()));
6184
6185 if !self.focus_handle.is_focused(window) {
6186 return;
6187 }
6188 let project = self.project.clone();
6189
6190 let code_actions_task = match deployed_from {
6191 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6192 _ => self.code_actions(buffer_row, window, cx),
6193 };
6194
6195 let runnable_task = match deployed_from {
6196 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6197 _ => {
6198 let mut task_context_task = Task::ready(None);
6199 if let Some(tasks) = &tasks
6200 && let Some(project) = project
6201 {
6202 task_context_task =
6203 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6204 }
6205
6206 cx.spawn_in(window, {
6207 let buffer = buffer.clone();
6208 async move |editor, cx| {
6209 let task_context = task_context_task.await;
6210
6211 let resolved_tasks =
6212 tasks
6213 .zip(task_context.clone())
6214 .map(|(tasks, task_context)| ResolvedTasks {
6215 templates: tasks.resolve(&task_context).collect(),
6216 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6217 multibuffer_point.row,
6218 tasks.column,
6219 )),
6220 });
6221 let debug_scenarios = editor
6222 .update(cx, |editor, cx| {
6223 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6224 })?
6225 .await;
6226 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6227 }
6228 })
6229 }
6230 };
6231
6232 cx.spawn_in(window, async move |editor, cx| {
6233 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6234 let code_actions = code_actions_task.await;
6235 let spawn_straight_away = quick_launch
6236 && resolved_tasks
6237 .as_ref()
6238 .is_some_and(|tasks| tasks.templates.len() == 1)
6239 && code_actions
6240 .as_ref()
6241 .is_none_or(|actions| actions.is_empty())
6242 && debug_scenarios.is_empty();
6243
6244 editor.update_in(cx, |editor, window, cx| {
6245 crate::hover_popover::hide_hover(editor, cx);
6246 let actions = CodeActionContents::new(
6247 resolved_tasks,
6248 code_actions,
6249 debug_scenarios,
6250 task_context.unwrap_or_default(),
6251 );
6252
6253 // Don't show the menu if there are no actions available
6254 if actions.is_empty() {
6255 cx.notify();
6256 return Task::ready(Ok(()));
6257 }
6258
6259 *editor.context_menu.borrow_mut() =
6260 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6261 buffer,
6262 actions,
6263 selected_item: Default::default(),
6264 scroll_handle: UniformListScrollHandle::default(),
6265 deployed_from,
6266 }));
6267 cx.notify();
6268 if spawn_straight_away
6269 && let Some(task) = editor.confirm_code_action(
6270 &ConfirmCodeAction { item_ix: Some(0) },
6271 window,
6272 cx,
6273 )
6274 {
6275 return task;
6276 }
6277
6278 Task::ready(Ok(()))
6279 })
6280 })
6281 .detach_and_log_err(cx);
6282 }
6283
6284 fn debug_scenarios(
6285 &mut self,
6286 resolved_tasks: &Option<ResolvedTasks>,
6287 buffer: &Entity<Buffer>,
6288 cx: &mut App,
6289 ) -> Task<Vec<task::DebugScenario>> {
6290 maybe!({
6291 let project = self.project()?;
6292 let dap_store = project.read(cx).dap_store();
6293 let mut scenarios = vec![];
6294 let resolved_tasks = resolved_tasks.as_ref()?;
6295 let buffer = buffer.read(cx);
6296 let language = buffer.language()?;
6297 let file = buffer.file();
6298 let debug_adapter = language_settings(language.name().into(), file, cx)
6299 .debuggers
6300 .first()
6301 .map(SharedString::from)
6302 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6303
6304 dap_store.update(cx, |dap_store, cx| {
6305 for (_, task) in &resolved_tasks.templates {
6306 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6307 task.original_task().clone(),
6308 debug_adapter.clone().into(),
6309 task.display_label().to_owned().into(),
6310 cx,
6311 );
6312 scenarios.push(maybe_scenario);
6313 }
6314 });
6315 Some(cx.background_spawn(async move {
6316 futures::future::join_all(scenarios)
6317 .await
6318 .into_iter()
6319 .flatten()
6320 .collect::<Vec<_>>()
6321 }))
6322 })
6323 .unwrap_or_else(|| Task::ready(vec![]))
6324 }
6325
6326 fn code_actions(
6327 &mut self,
6328 buffer_row: u32,
6329 window: &mut Window,
6330 cx: &mut Context<Self>,
6331 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6332 let mut task = self.code_actions_task.take();
6333 cx.spawn_in(window, async move |editor, cx| {
6334 while let Some(prev_task) = task {
6335 prev_task.await.log_err();
6336 task = editor
6337 .update(cx, |this, _| this.code_actions_task.take())
6338 .ok()?;
6339 }
6340
6341 editor
6342 .update(cx, |editor, cx| {
6343 editor
6344 .available_code_actions
6345 .clone()
6346 .and_then(|(location, code_actions)| {
6347 let snapshot = location.buffer.read(cx).snapshot();
6348 let point_range = location.range.to_point(&snapshot);
6349 let point_range = point_range.start.row..=point_range.end.row;
6350 if point_range.contains(&buffer_row) {
6351 Some(code_actions)
6352 } else {
6353 None
6354 }
6355 })
6356 })
6357 .ok()
6358 .flatten()
6359 })
6360 }
6361
6362 pub fn confirm_code_action(
6363 &mut self,
6364 action: &ConfirmCodeAction,
6365 window: &mut Window,
6366 cx: &mut Context<Self>,
6367 ) -> Option<Task<Result<()>>> {
6368 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6369
6370 let actions_menu =
6371 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6372 menu
6373 } else {
6374 return None;
6375 };
6376
6377 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6378 let action = actions_menu.actions.get(action_ix)?;
6379 let title = action.label();
6380 let buffer = actions_menu.buffer;
6381 let workspace = self.workspace()?;
6382
6383 match action {
6384 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6385 workspace.update(cx, |workspace, cx| {
6386 workspace.schedule_resolved_task(
6387 task_source_kind,
6388 resolved_task,
6389 false,
6390 window,
6391 cx,
6392 );
6393
6394 Some(Task::ready(Ok(())))
6395 })
6396 }
6397 CodeActionsItem::CodeAction {
6398 excerpt_id,
6399 action,
6400 provider,
6401 } => {
6402 let apply_code_action =
6403 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6404 let workspace = workspace.downgrade();
6405 Some(cx.spawn_in(window, async move |editor, cx| {
6406 let project_transaction = apply_code_action.await?;
6407 Self::open_project_transaction(
6408 &editor,
6409 workspace,
6410 project_transaction,
6411 title,
6412 cx,
6413 )
6414 .await
6415 }))
6416 }
6417 CodeActionsItem::DebugScenario(scenario) => {
6418 let context = actions_menu.actions.context;
6419
6420 workspace.update(cx, |workspace, cx| {
6421 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6422 workspace.start_debug_session(
6423 scenario,
6424 context,
6425 Some(buffer),
6426 None,
6427 window,
6428 cx,
6429 );
6430 });
6431 Some(Task::ready(Ok(())))
6432 }
6433 }
6434 }
6435
6436 pub async fn open_project_transaction(
6437 editor: &WeakEntity<Editor>,
6438 workspace: WeakEntity<Workspace>,
6439 transaction: ProjectTransaction,
6440 title: String,
6441 cx: &mut AsyncWindowContext,
6442 ) -> Result<()> {
6443 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6444 cx.update(|_, cx| {
6445 entries.sort_unstable_by_key(|(buffer, _)| {
6446 buffer.read(cx).file().map(|f| f.path().clone())
6447 });
6448 })?;
6449 if entries.is_empty() {
6450 return Ok(());
6451 }
6452
6453 // If the project transaction's edits are all contained within this editor, then
6454 // avoid opening a new editor to display them.
6455
6456 if let [(buffer, transaction)] = &*entries {
6457 let excerpt = editor.update(cx, |editor, cx| {
6458 editor
6459 .buffer()
6460 .read(cx)
6461 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6462 })?;
6463 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6464 && excerpted_buffer == *buffer
6465 {
6466 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6467 let excerpt_range = excerpt_range.to_offset(buffer);
6468 buffer
6469 .edited_ranges_for_transaction::<usize>(transaction)
6470 .all(|range| {
6471 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6472 })
6473 })?;
6474
6475 if all_edits_within_excerpt {
6476 return Ok(());
6477 }
6478 }
6479 }
6480
6481 let mut ranges_to_highlight = Vec::new();
6482 let excerpt_buffer = cx.new(|cx| {
6483 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6484 for (buffer_handle, transaction) in &entries {
6485 let edited_ranges = buffer_handle
6486 .read(cx)
6487 .edited_ranges_for_transaction::<Point>(transaction)
6488 .collect::<Vec<_>>();
6489 let (ranges, _) = multibuffer.set_excerpts_for_path(
6490 PathKey::for_buffer(buffer_handle, cx),
6491 buffer_handle.clone(),
6492 edited_ranges,
6493 multibuffer_context_lines(cx),
6494 cx,
6495 );
6496
6497 ranges_to_highlight.extend(ranges);
6498 }
6499 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6500 multibuffer
6501 })?;
6502
6503 workspace.update_in(cx, |workspace, window, cx| {
6504 let project = workspace.project().clone();
6505 let editor =
6506 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6507 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6508 editor.update(cx, |editor, cx| {
6509 editor.highlight_background::<Self>(
6510 &ranges_to_highlight,
6511 |theme| theme.colors().editor_highlighted_line_background,
6512 cx,
6513 );
6514 });
6515 })?;
6516
6517 Ok(())
6518 }
6519
6520 pub fn clear_code_action_providers(&mut self) {
6521 self.code_action_providers.clear();
6522 self.available_code_actions.take();
6523 }
6524
6525 pub fn add_code_action_provider(
6526 &mut self,
6527 provider: Rc<dyn CodeActionProvider>,
6528 window: &mut Window,
6529 cx: &mut Context<Self>,
6530 ) {
6531 if self
6532 .code_action_providers
6533 .iter()
6534 .any(|existing_provider| existing_provider.id() == provider.id())
6535 {
6536 return;
6537 }
6538
6539 self.code_action_providers.push(provider);
6540 self.refresh_code_actions(window, cx);
6541 }
6542
6543 pub fn remove_code_action_provider(
6544 &mut self,
6545 id: Arc<str>,
6546 window: &mut Window,
6547 cx: &mut Context<Self>,
6548 ) {
6549 self.code_action_providers
6550 .retain(|provider| provider.id() != id);
6551 self.refresh_code_actions(window, cx);
6552 }
6553
6554 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6555 !self.code_action_providers.is_empty()
6556 && EditorSettings::get_global(cx).toolbar.code_actions
6557 }
6558
6559 pub fn has_available_code_actions(&self) -> bool {
6560 self.available_code_actions
6561 .as_ref()
6562 .is_some_and(|(_, actions)| !actions.is_empty())
6563 }
6564
6565 fn render_inline_code_actions(
6566 &self,
6567 icon_size: ui::IconSize,
6568 display_row: DisplayRow,
6569 is_active: bool,
6570 cx: &mut Context<Self>,
6571 ) -> AnyElement {
6572 let show_tooltip = !self.context_menu_visible();
6573 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6574 .icon_size(icon_size)
6575 .shape(ui::IconButtonShape::Square)
6576 .icon_color(ui::Color::Hidden)
6577 .toggle_state(is_active)
6578 .when(show_tooltip, |this| {
6579 this.tooltip({
6580 let focus_handle = self.focus_handle.clone();
6581 move |window, cx| {
6582 Tooltip::for_action_in(
6583 "Toggle Code Actions",
6584 &ToggleCodeActions {
6585 deployed_from: None,
6586 quick_launch: false,
6587 },
6588 &focus_handle,
6589 window,
6590 cx,
6591 )
6592 }
6593 })
6594 })
6595 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6596 window.focus(&editor.focus_handle(cx));
6597 editor.toggle_code_actions(
6598 &crate::actions::ToggleCodeActions {
6599 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6600 display_row,
6601 )),
6602 quick_launch: false,
6603 },
6604 window,
6605 cx,
6606 );
6607 }))
6608 .into_any_element()
6609 }
6610
6611 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6612 &self.context_menu
6613 }
6614
6615 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6616 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6617 cx.background_executor()
6618 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6619 .await;
6620
6621 let (start_buffer, start, _, end, newest_selection) = this
6622 .update(cx, |this, cx| {
6623 let newest_selection = this.selections.newest_anchor().clone();
6624 if newest_selection.head().diff_base_anchor.is_some() {
6625 return None;
6626 }
6627 let newest_selection_adjusted = this.selections.newest_adjusted(cx);
6628 let buffer = this.buffer.read(cx);
6629
6630 let (start_buffer, start) =
6631 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6632 let (end_buffer, end) =
6633 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6634
6635 Some((start_buffer, start, end_buffer, end, newest_selection))
6636 })?
6637 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6638 .context(
6639 "Expected selection to lie in a single buffer when refreshing code actions",
6640 )?;
6641 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6642 let providers = this.code_action_providers.clone();
6643 let tasks = this
6644 .code_action_providers
6645 .iter()
6646 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6647 .collect::<Vec<_>>();
6648 (providers, tasks)
6649 })?;
6650
6651 let mut actions = Vec::new();
6652 for (provider, provider_actions) in
6653 providers.into_iter().zip(future::join_all(tasks).await)
6654 {
6655 if let Some(provider_actions) = provider_actions.log_err() {
6656 actions.extend(provider_actions.into_iter().map(|action| {
6657 AvailableCodeAction {
6658 excerpt_id: newest_selection.start.excerpt_id,
6659 action,
6660 provider: provider.clone(),
6661 }
6662 }));
6663 }
6664 }
6665
6666 this.update(cx, |this, cx| {
6667 this.available_code_actions = if actions.is_empty() {
6668 None
6669 } else {
6670 Some((
6671 Location {
6672 buffer: start_buffer,
6673 range: start..end,
6674 },
6675 actions.into(),
6676 ))
6677 };
6678 cx.notify();
6679 })
6680 }));
6681 }
6682
6683 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6684 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6685 self.show_git_blame_inline = false;
6686
6687 self.show_git_blame_inline_delay_task =
6688 Some(cx.spawn_in(window, async move |this, cx| {
6689 cx.background_executor().timer(delay).await;
6690
6691 this.update(cx, |this, cx| {
6692 this.show_git_blame_inline = true;
6693 cx.notify();
6694 })
6695 .log_err();
6696 }));
6697 }
6698 }
6699
6700 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6701 let snapshot = self.snapshot(window, cx);
6702 let cursor = self.selections.newest::<Point>(cx).head();
6703 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6704 else {
6705 return;
6706 };
6707
6708 let Some(blame) = self.blame.as_ref() else {
6709 return;
6710 };
6711
6712 let row_info = RowInfo {
6713 buffer_id: Some(buffer.remote_id()),
6714 buffer_row: Some(point.row),
6715 ..Default::default()
6716 };
6717 let Some((buffer, blame_entry)) = blame
6718 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6719 .flatten()
6720 else {
6721 return;
6722 };
6723
6724 let anchor = self.selections.newest_anchor().head();
6725 let position = self.to_pixel_point(anchor, &snapshot, window);
6726 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6727 self.show_blame_popover(
6728 buffer,
6729 &blame_entry,
6730 position + last_bounds.origin,
6731 true,
6732 cx,
6733 );
6734 };
6735 }
6736
6737 fn show_blame_popover(
6738 &mut self,
6739 buffer: BufferId,
6740 blame_entry: &BlameEntry,
6741 position: gpui::Point<Pixels>,
6742 ignore_timeout: bool,
6743 cx: &mut Context<Self>,
6744 ) {
6745 if let Some(state) = &mut self.inline_blame_popover {
6746 state.hide_task.take();
6747 } else {
6748 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6749 let blame_entry = blame_entry.clone();
6750 let show_task = cx.spawn(async move |editor, cx| {
6751 if !ignore_timeout {
6752 cx.background_executor()
6753 .timer(std::time::Duration::from_millis(blame_popover_delay))
6754 .await;
6755 }
6756 editor
6757 .update(cx, |editor, cx| {
6758 editor.inline_blame_popover_show_task.take();
6759 let Some(blame) = editor.blame.as_ref() else {
6760 return;
6761 };
6762 let blame = blame.read(cx);
6763 let details = blame.details_for_entry(buffer, &blame_entry);
6764 let markdown = cx.new(|cx| {
6765 Markdown::new(
6766 details
6767 .as_ref()
6768 .map(|message| message.message.clone())
6769 .unwrap_or_default(),
6770 None,
6771 None,
6772 cx,
6773 )
6774 });
6775 editor.inline_blame_popover = Some(InlineBlamePopover {
6776 position,
6777 hide_task: None,
6778 popover_bounds: None,
6779 popover_state: InlineBlamePopoverState {
6780 scroll_handle: ScrollHandle::new(),
6781 commit_message: details,
6782 markdown,
6783 },
6784 keyboard_grace: ignore_timeout,
6785 });
6786 cx.notify();
6787 })
6788 .ok();
6789 });
6790 self.inline_blame_popover_show_task = Some(show_task);
6791 }
6792 }
6793
6794 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6795 self.inline_blame_popover_show_task.take();
6796 if let Some(state) = &mut self.inline_blame_popover {
6797 let hide_task = cx.spawn(async move |editor, cx| {
6798 cx.background_executor()
6799 .timer(std::time::Duration::from_millis(100))
6800 .await;
6801 editor
6802 .update(cx, |editor, cx| {
6803 editor.inline_blame_popover.take();
6804 cx.notify();
6805 })
6806 .ok();
6807 });
6808 state.hide_task = Some(hide_task);
6809 }
6810 }
6811
6812 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6813 if self.pending_rename.is_some() {
6814 return None;
6815 }
6816
6817 let provider = self.semantics_provider.clone()?;
6818 let buffer = self.buffer.read(cx);
6819 let newest_selection = self.selections.newest_anchor().clone();
6820 let cursor_position = newest_selection.head();
6821 let (cursor_buffer, cursor_buffer_position) =
6822 buffer.text_anchor_for_position(cursor_position, cx)?;
6823 let (tail_buffer, tail_buffer_position) =
6824 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6825 if cursor_buffer != tail_buffer {
6826 return None;
6827 }
6828
6829 let snapshot = cursor_buffer.read(cx).snapshot();
6830 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6831 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6832 if start_word_range != end_word_range {
6833 self.document_highlights_task.take();
6834 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6835 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6836 return None;
6837 }
6838
6839 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6840 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6841 cx.background_executor()
6842 .timer(Duration::from_millis(debounce))
6843 .await;
6844
6845 let highlights = if let Some(highlights) = cx
6846 .update(|cx| {
6847 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6848 })
6849 .ok()
6850 .flatten()
6851 {
6852 highlights.await.log_err()
6853 } else {
6854 None
6855 };
6856
6857 if let Some(highlights) = highlights {
6858 this.update(cx, |this, cx| {
6859 if this.pending_rename.is_some() {
6860 return;
6861 }
6862
6863 let buffer = this.buffer.read(cx);
6864 if buffer
6865 .text_anchor_for_position(cursor_position, cx)
6866 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6867 {
6868 return;
6869 }
6870
6871 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6872 let mut write_ranges = Vec::new();
6873 let mut read_ranges = Vec::new();
6874 for highlight in highlights {
6875 let buffer_id = cursor_buffer.read(cx).remote_id();
6876 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6877 {
6878 let start = highlight
6879 .range
6880 .start
6881 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6882 let end = highlight
6883 .range
6884 .end
6885 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6886 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6887 continue;
6888 }
6889
6890 let range = Anchor::range_in_buffer(excerpt_id, buffer_id, start..end);
6891 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6892 write_ranges.push(range);
6893 } else {
6894 read_ranges.push(range);
6895 }
6896 }
6897 }
6898
6899 this.highlight_background::<DocumentHighlightRead>(
6900 &read_ranges,
6901 |theme| theme.colors().editor_document_highlight_read_background,
6902 cx,
6903 );
6904 this.highlight_background::<DocumentHighlightWrite>(
6905 &write_ranges,
6906 |theme| theme.colors().editor_document_highlight_write_background,
6907 cx,
6908 );
6909 cx.notify();
6910 })
6911 .log_err();
6912 }
6913 }));
6914 None
6915 }
6916
6917 fn prepare_highlight_query_from_selection(
6918 &mut self,
6919 cx: &mut Context<Editor>,
6920 ) -> Option<(String, Range<Anchor>)> {
6921 if matches!(self.mode, EditorMode::SingleLine) {
6922 return None;
6923 }
6924 if !EditorSettings::get_global(cx).selection_highlight {
6925 return None;
6926 }
6927 if self.selections.count() != 1 || self.selections.line_mode() {
6928 return None;
6929 }
6930 let selection = self.selections.newest_anchor();
6931 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6932 let selection_point_range = selection.start.to_point(&multi_buffer_snapshot)
6933 ..selection.end.to_point(&multi_buffer_snapshot);
6934 // If the selection spans multiple rows OR it is empty
6935 if selection_point_range.start.row != selection_point_range.end.row
6936 || selection_point_range.start.column == selection_point_range.end.column
6937 {
6938 return None;
6939 }
6940
6941 let query = multi_buffer_snapshot
6942 .text_for_range(selection.range())
6943 .collect::<String>();
6944 if query.trim().is_empty() {
6945 return None;
6946 }
6947 Some((query, selection.range()))
6948 }
6949
6950 fn update_selection_occurrence_highlights(
6951 &mut self,
6952 query_text: String,
6953 query_range: Range<Anchor>,
6954 multi_buffer_range_to_query: Range<Point>,
6955 use_debounce: bool,
6956 window: &mut Window,
6957 cx: &mut Context<Editor>,
6958 ) -> Task<()> {
6959 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6960 cx.spawn_in(window, async move |editor, cx| {
6961 if use_debounce {
6962 cx.background_executor()
6963 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6964 .await;
6965 }
6966 let match_task = cx.background_spawn(async move {
6967 let buffer_ranges = multi_buffer_snapshot
6968 .range_to_buffer_ranges(multi_buffer_range_to_query)
6969 .into_iter()
6970 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6971 let mut match_ranges = Vec::new();
6972 let Ok(regex) = project::search::SearchQuery::text(
6973 query_text.clone(),
6974 false,
6975 false,
6976 false,
6977 Default::default(),
6978 Default::default(),
6979 false,
6980 None,
6981 ) else {
6982 return Vec::default();
6983 };
6984 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
6985 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6986 match_ranges.extend(
6987 regex
6988 .search(buffer_snapshot, Some(search_range.clone()))
6989 .await
6990 .into_iter()
6991 .filter_map(|match_range| {
6992 let match_start = buffer_snapshot
6993 .anchor_after(search_range.start + match_range.start);
6994 let match_end = buffer_snapshot
6995 .anchor_before(search_range.start + match_range.end);
6996 let match_anchor_range = Anchor::range_in_buffer(
6997 excerpt_id,
6998 buffer_snapshot.remote_id(),
6999 match_start..match_end,
7000 );
7001 (match_anchor_range != query_range).then_some(match_anchor_range)
7002 }),
7003 );
7004 }
7005 match_ranges
7006 });
7007 let match_ranges = match_task.await;
7008 editor
7009 .update_in(cx, |editor, _, cx| {
7010 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7011 if !match_ranges.is_empty() {
7012 editor.highlight_background::<SelectedTextHighlight>(
7013 &match_ranges,
7014 |theme| theme.colors().editor_document_highlight_bracket_background,
7015 cx,
7016 )
7017 }
7018 })
7019 .log_err();
7020 })
7021 }
7022
7023 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7024 struct NewlineFold;
7025 let type_id = std::any::TypeId::of::<NewlineFold>();
7026 if !self.mode.is_single_line() {
7027 return;
7028 }
7029 let snapshot = self.snapshot(window, cx);
7030 if snapshot.buffer_snapshot().max_point().row == 0 {
7031 return;
7032 }
7033 let task = cx.background_spawn(async move {
7034 let new_newlines = snapshot
7035 .buffer_chars_at(0)
7036 .filter_map(|(c, i)| {
7037 if c == '\n' {
7038 Some(
7039 snapshot.buffer_snapshot().anchor_after(i)
7040 ..snapshot.buffer_snapshot().anchor_before(i + 1),
7041 )
7042 } else {
7043 None
7044 }
7045 })
7046 .collect::<Vec<_>>();
7047 let existing_newlines = snapshot
7048 .folds_in_range(0..snapshot.buffer_snapshot().len())
7049 .filter_map(|fold| {
7050 if fold.placeholder.type_tag == Some(type_id) {
7051 Some(fold.range.start..fold.range.end)
7052 } else {
7053 None
7054 }
7055 })
7056 .collect::<Vec<_>>();
7057
7058 (new_newlines, existing_newlines)
7059 });
7060 self.folding_newlines = cx.spawn(async move |this, cx| {
7061 let (new_newlines, existing_newlines) = task.await;
7062 if new_newlines == existing_newlines {
7063 return;
7064 }
7065 let placeholder = FoldPlaceholder {
7066 render: Arc::new(move |_, _, cx| {
7067 div()
7068 .bg(cx.theme().status().hint_background)
7069 .border_b_1()
7070 .size_full()
7071 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7072 .border_color(cx.theme().status().hint)
7073 .child("\\n")
7074 .into_any()
7075 }),
7076 constrain_width: false,
7077 merge_adjacent: false,
7078 type_tag: Some(type_id),
7079 };
7080 let creases = new_newlines
7081 .into_iter()
7082 .map(|range| Crease::simple(range, placeholder.clone()))
7083 .collect();
7084 this.update(cx, |this, cx| {
7085 this.display_map.update(cx, |display_map, cx| {
7086 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7087 display_map.fold(creases, cx);
7088 });
7089 })
7090 .ok();
7091 });
7092 }
7093
7094 fn refresh_selected_text_highlights(
7095 &mut self,
7096 on_buffer_edit: bool,
7097 window: &mut Window,
7098 cx: &mut Context<Editor>,
7099 ) {
7100 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
7101 else {
7102 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7103 self.quick_selection_highlight_task.take();
7104 self.debounced_selection_highlight_task.take();
7105 return;
7106 };
7107 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7108 if on_buffer_edit
7109 || self
7110 .quick_selection_highlight_task
7111 .as_ref()
7112 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7113 {
7114 let multi_buffer_visible_start = self
7115 .scroll_manager
7116 .anchor()
7117 .anchor
7118 .to_point(&multi_buffer_snapshot);
7119 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7120 multi_buffer_visible_start
7121 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7122 Bias::Left,
7123 );
7124 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7125 self.quick_selection_highlight_task = Some((
7126 query_range.clone(),
7127 self.update_selection_occurrence_highlights(
7128 query_text.clone(),
7129 query_range.clone(),
7130 multi_buffer_visible_range,
7131 false,
7132 window,
7133 cx,
7134 ),
7135 ));
7136 }
7137 if on_buffer_edit
7138 || self
7139 .debounced_selection_highlight_task
7140 .as_ref()
7141 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7142 {
7143 let multi_buffer_start = multi_buffer_snapshot
7144 .anchor_before(0)
7145 .to_point(&multi_buffer_snapshot);
7146 let multi_buffer_end = multi_buffer_snapshot
7147 .anchor_after(multi_buffer_snapshot.len())
7148 .to_point(&multi_buffer_snapshot);
7149 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7150 self.debounced_selection_highlight_task = Some((
7151 query_range.clone(),
7152 self.update_selection_occurrence_highlights(
7153 query_text,
7154 query_range,
7155 multi_buffer_full_range,
7156 true,
7157 window,
7158 cx,
7159 ),
7160 ));
7161 }
7162 }
7163
7164 pub fn refresh_edit_prediction(
7165 &mut self,
7166 debounce: bool,
7167 user_requested: bool,
7168 window: &mut Window,
7169 cx: &mut Context<Self>,
7170 ) -> Option<()> {
7171 if DisableAiSettings::get_global(cx).disable_ai {
7172 return None;
7173 }
7174
7175 let provider = self.edit_prediction_provider()?;
7176 let cursor = self.selections.newest_anchor().head();
7177 let (buffer, cursor_buffer_position) =
7178 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7179
7180 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7181 self.discard_edit_prediction(false, cx);
7182 return None;
7183 }
7184
7185 self.update_visible_edit_prediction(window, cx);
7186
7187 if !user_requested
7188 && (!self.should_show_edit_predictions()
7189 || !self.is_focused(window)
7190 || buffer.read(cx).is_empty())
7191 {
7192 self.discard_edit_prediction(false, cx);
7193 return None;
7194 }
7195
7196 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7197 Some(())
7198 }
7199
7200 fn show_edit_predictions_in_menu(&self) -> bool {
7201 match self.edit_prediction_settings {
7202 EditPredictionSettings::Disabled => false,
7203 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7204 }
7205 }
7206
7207 pub fn edit_predictions_enabled(&self) -> bool {
7208 match self.edit_prediction_settings {
7209 EditPredictionSettings::Disabled => false,
7210 EditPredictionSettings::Enabled { .. } => true,
7211 }
7212 }
7213
7214 fn edit_prediction_requires_modifier(&self) -> bool {
7215 match self.edit_prediction_settings {
7216 EditPredictionSettings::Disabled => false,
7217 EditPredictionSettings::Enabled {
7218 preview_requires_modifier,
7219 ..
7220 } => preview_requires_modifier,
7221 }
7222 }
7223
7224 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7225 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7226 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7227 self.discard_edit_prediction(false, cx);
7228 } else {
7229 let selection = self.selections.newest_anchor();
7230 let cursor = selection.head();
7231
7232 if let Some((buffer, cursor_buffer_position)) =
7233 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7234 {
7235 self.edit_prediction_settings =
7236 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7237 }
7238 }
7239 }
7240
7241 fn edit_prediction_settings_at_position(
7242 &self,
7243 buffer: &Entity<Buffer>,
7244 buffer_position: language::Anchor,
7245 cx: &App,
7246 ) -> EditPredictionSettings {
7247 if !self.mode.is_full()
7248 || !self.show_edit_predictions_override.unwrap_or(true)
7249 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7250 {
7251 return EditPredictionSettings::Disabled;
7252 }
7253
7254 let buffer = buffer.read(cx);
7255
7256 let file = buffer.file();
7257
7258 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7259 return EditPredictionSettings::Disabled;
7260 };
7261
7262 let by_provider = matches!(
7263 self.menu_edit_predictions_policy,
7264 MenuEditPredictionsPolicy::ByProvider
7265 );
7266
7267 let show_in_menu = by_provider
7268 && self
7269 .edit_prediction_provider
7270 .as_ref()
7271 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7272
7273 let preview_requires_modifier =
7274 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7275
7276 EditPredictionSettings::Enabled {
7277 show_in_menu,
7278 preview_requires_modifier,
7279 }
7280 }
7281
7282 fn should_show_edit_predictions(&self) -> bool {
7283 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7284 }
7285
7286 pub fn edit_prediction_preview_is_active(&self) -> bool {
7287 matches!(
7288 self.edit_prediction_preview,
7289 EditPredictionPreview::Active { .. }
7290 )
7291 }
7292
7293 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7294 let cursor = self.selections.newest_anchor().head();
7295 if let Some((buffer, cursor_position)) =
7296 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7297 {
7298 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7299 } else {
7300 false
7301 }
7302 }
7303
7304 pub fn supports_minimap(&self, cx: &App) -> bool {
7305 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7306 }
7307
7308 fn edit_predictions_enabled_in_buffer(
7309 &self,
7310 buffer: &Entity<Buffer>,
7311 buffer_position: language::Anchor,
7312 cx: &App,
7313 ) -> bool {
7314 maybe!({
7315 if self.read_only(cx) {
7316 return Some(false);
7317 }
7318 let provider = self.edit_prediction_provider()?;
7319 if !provider.is_enabled(buffer, buffer_position, cx) {
7320 return Some(false);
7321 }
7322 let buffer = buffer.read(cx);
7323 let Some(file) = buffer.file() else {
7324 return Some(true);
7325 };
7326 let settings = all_language_settings(Some(file), cx);
7327 Some(settings.edit_predictions_enabled_for_file(file, cx))
7328 })
7329 .unwrap_or(false)
7330 }
7331
7332 fn cycle_edit_prediction(
7333 &mut self,
7334 direction: Direction,
7335 window: &mut Window,
7336 cx: &mut Context<Self>,
7337 ) -> Option<()> {
7338 let provider = self.edit_prediction_provider()?;
7339 let cursor = self.selections.newest_anchor().head();
7340 let (buffer, cursor_buffer_position) =
7341 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7342 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7343 return None;
7344 }
7345
7346 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7347 self.update_visible_edit_prediction(window, cx);
7348
7349 Some(())
7350 }
7351
7352 pub fn show_edit_prediction(
7353 &mut self,
7354 _: &ShowEditPrediction,
7355 window: &mut Window,
7356 cx: &mut Context<Self>,
7357 ) {
7358 if !self.has_active_edit_prediction() {
7359 self.refresh_edit_prediction(false, true, window, cx);
7360 return;
7361 }
7362
7363 self.update_visible_edit_prediction(window, cx);
7364 }
7365
7366 pub fn display_cursor_names(
7367 &mut self,
7368 _: &DisplayCursorNames,
7369 window: &mut Window,
7370 cx: &mut Context<Self>,
7371 ) {
7372 self.show_cursor_names(window, cx);
7373 }
7374
7375 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7376 self.show_cursor_names = true;
7377 cx.notify();
7378 cx.spawn_in(window, async move |this, cx| {
7379 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7380 this.update(cx, |this, cx| {
7381 this.show_cursor_names = false;
7382 cx.notify()
7383 })
7384 .ok()
7385 })
7386 .detach();
7387 }
7388
7389 pub fn next_edit_prediction(
7390 &mut self,
7391 _: &NextEditPrediction,
7392 window: &mut Window,
7393 cx: &mut Context<Self>,
7394 ) {
7395 if self.has_active_edit_prediction() {
7396 self.cycle_edit_prediction(Direction::Next, window, cx);
7397 } else {
7398 let is_copilot_disabled = self
7399 .refresh_edit_prediction(false, true, window, cx)
7400 .is_none();
7401 if is_copilot_disabled {
7402 cx.propagate();
7403 }
7404 }
7405 }
7406
7407 pub fn previous_edit_prediction(
7408 &mut self,
7409 _: &PreviousEditPrediction,
7410 window: &mut Window,
7411 cx: &mut Context<Self>,
7412 ) {
7413 if self.has_active_edit_prediction() {
7414 self.cycle_edit_prediction(Direction::Prev, window, cx);
7415 } else {
7416 let is_copilot_disabled = self
7417 .refresh_edit_prediction(false, true, window, cx)
7418 .is_none();
7419 if is_copilot_disabled {
7420 cx.propagate();
7421 }
7422 }
7423 }
7424
7425 pub fn accept_edit_prediction(
7426 &mut self,
7427 _: &AcceptEditPrediction,
7428 window: &mut Window,
7429 cx: &mut Context<Self>,
7430 ) {
7431 if self.show_edit_predictions_in_menu() {
7432 self.hide_context_menu(window, cx);
7433 }
7434
7435 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7436 return;
7437 };
7438
7439 match &active_edit_prediction.completion {
7440 EditPrediction::MoveWithin { target, .. } => {
7441 let target = *target;
7442
7443 if let Some(position_map) = &self.last_position_map {
7444 if position_map
7445 .visible_row_range
7446 .contains(&target.to_display_point(&position_map.snapshot).row())
7447 || !self.edit_prediction_requires_modifier()
7448 {
7449 self.unfold_ranges(&[target..target], true, false, cx);
7450 // Note that this is also done in vim's handler of the Tab action.
7451 self.change_selections(
7452 SelectionEffects::scroll(Autoscroll::newest()),
7453 window,
7454 cx,
7455 |selections| {
7456 selections.select_anchor_ranges([target..target]);
7457 },
7458 );
7459 self.clear_row_highlights::<EditPredictionPreview>();
7460
7461 self.edit_prediction_preview
7462 .set_previous_scroll_position(None);
7463 } else {
7464 self.edit_prediction_preview
7465 .set_previous_scroll_position(Some(
7466 position_map.snapshot.scroll_anchor,
7467 ));
7468
7469 self.highlight_rows::<EditPredictionPreview>(
7470 target..target,
7471 cx.theme().colors().editor_highlighted_line_background,
7472 RowHighlightOptions {
7473 autoscroll: true,
7474 ..Default::default()
7475 },
7476 cx,
7477 );
7478 self.request_autoscroll(Autoscroll::fit(), cx);
7479 }
7480 }
7481 }
7482 EditPrediction::MoveOutside { snapshot, target } => {
7483 if let Some(workspace) = self.workspace() {
7484 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7485 .detach_and_log_err(cx);
7486 }
7487 }
7488 EditPrediction::Edit { edits, .. } => {
7489 self.report_edit_prediction_event(
7490 active_edit_prediction.completion_id.clone(),
7491 true,
7492 cx,
7493 );
7494
7495 if let Some(provider) = self.edit_prediction_provider() {
7496 provider.accept(cx);
7497 }
7498
7499 // Store the transaction ID and selections before applying the edit
7500 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7501
7502 let snapshot = self.buffer.read(cx).snapshot(cx);
7503 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7504
7505 self.buffer.update(cx, |buffer, cx| {
7506 buffer.edit(edits.iter().cloned(), None, cx)
7507 });
7508
7509 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7510 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7511 });
7512
7513 let selections = self.selections.disjoint_anchors_arc();
7514 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7515 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7516 if has_new_transaction {
7517 self.selection_history
7518 .insert_transaction(transaction_id_now, selections);
7519 }
7520 }
7521
7522 self.update_visible_edit_prediction(window, cx);
7523 if self.active_edit_prediction.is_none() {
7524 self.refresh_edit_prediction(true, true, window, cx);
7525 }
7526
7527 cx.notify();
7528 }
7529 }
7530
7531 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7532 }
7533
7534 pub fn accept_partial_edit_prediction(
7535 &mut self,
7536 _: &AcceptPartialEditPrediction,
7537 window: &mut Window,
7538 cx: &mut Context<Self>,
7539 ) {
7540 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7541 return;
7542 };
7543 if self.selections.count() != 1 {
7544 return;
7545 }
7546
7547 match &active_edit_prediction.completion {
7548 EditPrediction::MoveWithin { target, .. } => {
7549 let target = *target;
7550 self.change_selections(
7551 SelectionEffects::scroll(Autoscroll::newest()),
7552 window,
7553 cx,
7554 |selections| {
7555 selections.select_anchor_ranges([target..target]);
7556 },
7557 );
7558 }
7559 EditPrediction::MoveOutside { snapshot, target } => {
7560 if let Some(workspace) = self.workspace() {
7561 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7562 .detach_and_log_err(cx);
7563 }
7564 }
7565 EditPrediction::Edit { edits, .. } => {
7566 self.report_edit_prediction_event(
7567 active_edit_prediction.completion_id.clone(),
7568 true,
7569 cx,
7570 );
7571
7572 // Find an insertion that starts at the cursor position.
7573 let snapshot = self.buffer.read(cx).snapshot(cx);
7574 let cursor_offset = self.selections.newest::<usize>(cx).head();
7575 let insertion = edits.iter().find_map(|(range, text)| {
7576 let range = range.to_offset(&snapshot);
7577 if range.is_empty() && range.start == cursor_offset {
7578 Some(text)
7579 } else {
7580 None
7581 }
7582 });
7583
7584 if let Some(text) = insertion {
7585 let mut partial_completion = text
7586 .chars()
7587 .by_ref()
7588 .take_while(|c| c.is_alphabetic())
7589 .collect::<String>();
7590 if partial_completion.is_empty() {
7591 partial_completion = text
7592 .chars()
7593 .by_ref()
7594 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7595 .collect::<String>();
7596 }
7597
7598 cx.emit(EditorEvent::InputHandled {
7599 utf16_range_to_replace: None,
7600 text: partial_completion.clone().into(),
7601 });
7602
7603 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7604
7605 self.refresh_edit_prediction(true, true, window, cx);
7606 cx.notify();
7607 } else {
7608 self.accept_edit_prediction(&Default::default(), window, cx);
7609 }
7610 }
7611 }
7612 }
7613
7614 fn discard_edit_prediction(
7615 &mut self,
7616 should_report_edit_prediction_event: bool,
7617 cx: &mut Context<Self>,
7618 ) -> bool {
7619 if should_report_edit_prediction_event {
7620 let completion_id = self
7621 .active_edit_prediction
7622 .as_ref()
7623 .and_then(|active_completion| active_completion.completion_id.clone());
7624
7625 self.report_edit_prediction_event(completion_id, false, cx);
7626 }
7627
7628 if let Some(provider) = self.edit_prediction_provider() {
7629 provider.discard(cx);
7630 }
7631
7632 self.take_active_edit_prediction(cx)
7633 }
7634
7635 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7636 let Some(provider) = self.edit_prediction_provider() else {
7637 return;
7638 };
7639
7640 let Some((_, buffer, _)) = self
7641 .buffer
7642 .read(cx)
7643 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7644 else {
7645 return;
7646 };
7647
7648 let extension = buffer
7649 .read(cx)
7650 .file()
7651 .and_then(|file| Some(file.path().extension()?.to_string()));
7652
7653 let event_type = match accepted {
7654 true => "Edit Prediction Accepted",
7655 false => "Edit Prediction Discarded",
7656 };
7657 telemetry::event!(
7658 event_type,
7659 provider = provider.name(),
7660 prediction_id = id,
7661 suggestion_accepted = accepted,
7662 file_extension = extension,
7663 );
7664 }
7665
7666 fn open_editor_at_anchor(
7667 snapshot: &language::BufferSnapshot,
7668 target: language::Anchor,
7669 workspace: &Entity<Workspace>,
7670 window: &mut Window,
7671 cx: &mut App,
7672 ) -> Task<Result<()>> {
7673 workspace.update(cx, |workspace, cx| {
7674 let path = snapshot.file().map(|file| file.full_path(cx));
7675 let Some(path) =
7676 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7677 else {
7678 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7679 };
7680 let target = text::ToPoint::to_point(&target, snapshot);
7681 let item = workspace.open_path(path, None, true, window, cx);
7682 window.spawn(cx, async move |cx| {
7683 let Some(editor) = item.await?.downcast::<Editor>() else {
7684 return Ok(());
7685 };
7686 editor
7687 .update_in(cx, |editor, window, cx| {
7688 editor.go_to_singleton_buffer_point(target, window, cx);
7689 })
7690 .ok();
7691 anyhow::Ok(())
7692 })
7693 })
7694 }
7695
7696 pub fn has_active_edit_prediction(&self) -> bool {
7697 self.active_edit_prediction.is_some()
7698 }
7699
7700 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7701 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7702 return false;
7703 };
7704
7705 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7706 self.clear_highlights::<EditPredictionHighlight>(cx);
7707 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7708 true
7709 }
7710
7711 /// Returns true when we're displaying the edit prediction popover below the cursor
7712 /// like we are not previewing and the LSP autocomplete menu is visible
7713 /// or we are in `when_holding_modifier` mode.
7714 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7715 if self.edit_prediction_preview_is_active()
7716 || !self.show_edit_predictions_in_menu()
7717 || !self.edit_predictions_enabled()
7718 {
7719 return false;
7720 }
7721
7722 if self.has_visible_completions_menu() {
7723 return true;
7724 }
7725
7726 has_completion && self.edit_prediction_requires_modifier()
7727 }
7728
7729 fn handle_modifiers_changed(
7730 &mut self,
7731 modifiers: Modifiers,
7732 position_map: &PositionMap,
7733 window: &mut Window,
7734 cx: &mut Context<Self>,
7735 ) {
7736 if self.show_edit_predictions_in_menu() {
7737 self.update_edit_prediction_preview(&modifiers, window, cx);
7738 }
7739
7740 self.update_selection_mode(&modifiers, position_map, window, cx);
7741
7742 let mouse_position = window.mouse_position();
7743 if !position_map.text_hitbox.is_hovered(window) {
7744 return;
7745 }
7746
7747 self.update_hovered_link(
7748 position_map.point_for_position(mouse_position),
7749 &position_map.snapshot,
7750 modifiers,
7751 window,
7752 cx,
7753 )
7754 }
7755
7756 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7757 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7758 if invert {
7759 match multi_cursor_setting {
7760 MultiCursorModifier::Alt => modifiers.alt,
7761 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7762 }
7763 } else {
7764 match multi_cursor_setting {
7765 MultiCursorModifier::Alt => modifiers.secondary(),
7766 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7767 }
7768 }
7769 }
7770
7771 fn columnar_selection_mode(
7772 modifiers: &Modifiers,
7773 cx: &mut Context<Self>,
7774 ) -> Option<ColumnarMode> {
7775 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7776 if Self::multi_cursor_modifier(false, modifiers, cx) {
7777 Some(ColumnarMode::FromMouse)
7778 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7779 Some(ColumnarMode::FromSelection)
7780 } else {
7781 None
7782 }
7783 } else {
7784 None
7785 }
7786 }
7787
7788 fn update_selection_mode(
7789 &mut self,
7790 modifiers: &Modifiers,
7791 position_map: &PositionMap,
7792 window: &mut Window,
7793 cx: &mut Context<Self>,
7794 ) {
7795 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7796 return;
7797 };
7798 if self.selections.pending_anchor().is_none() {
7799 return;
7800 }
7801
7802 let mouse_position = window.mouse_position();
7803 let point_for_position = position_map.point_for_position(mouse_position);
7804 let position = point_for_position.previous_valid;
7805
7806 self.select(
7807 SelectPhase::BeginColumnar {
7808 position,
7809 reset: false,
7810 mode,
7811 goal_column: point_for_position.exact_unclipped.column(),
7812 },
7813 window,
7814 cx,
7815 );
7816 }
7817
7818 fn update_edit_prediction_preview(
7819 &mut self,
7820 modifiers: &Modifiers,
7821 window: &mut Window,
7822 cx: &mut Context<Self>,
7823 ) {
7824 let mut modifiers_held = false;
7825 if let Some(accept_keystroke) = self
7826 .accept_edit_prediction_keybind(false, window, cx)
7827 .keystroke()
7828 {
7829 modifiers_held = modifiers_held
7830 || (accept_keystroke.modifiers() == modifiers
7831 && accept_keystroke.modifiers().modified());
7832 };
7833 if let Some(accept_partial_keystroke) = self
7834 .accept_edit_prediction_keybind(true, window, cx)
7835 .keystroke()
7836 {
7837 modifiers_held = modifiers_held
7838 || (accept_partial_keystroke.modifiers() == modifiers
7839 && accept_partial_keystroke.modifiers().modified());
7840 }
7841
7842 if modifiers_held {
7843 if matches!(
7844 self.edit_prediction_preview,
7845 EditPredictionPreview::Inactive { .. }
7846 ) {
7847 self.edit_prediction_preview = EditPredictionPreview::Active {
7848 previous_scroll_position: None,
7849 since: Instant::now(),
7850 };
7851
7852 self.update_visible_edit_prediction(window, cx);
7853 cx.notify();
7854 }
7855 } else if let EditPredictionPreview::Active {
7856 previous_scroll_position,
7857 since,
7858 } = self.edit_prediction_preview
7859 {
7860 if let (Some(previous_scroll_position), Some(position_map)) =
7861 (previous_scroll_position, self.last_position_map.as_ref())
7862 {
7863 self.set_scroll_position(
7864 previous_scroll_position
7865 .scroll_position(&position_map.snapshot.display_snapshot),
7866 window,
7867 cx,
7868 );
7869 }
7870
7871 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7872 released_too_fast: since.elapsed() < Duration::from_millis(200),
7873 };
7874 self.clear_row_highlights::<EditPredictionPreview>();
7875 self.update_visible_edit_prediction(window, cx);
7876 cx.notify();
7877 }
7878 }
7879
7880 fn update_visible_edit_prediction(
7881 &mut self,
7882 _window: &mut Window,
7883 cx: &mut Context<Self>,
7884 ) -> Option<()> {
7885 if DisableAiSettings::get_global(cx).disable_ai {
7886 return None;
7887 }
7888
7889 if self.ime_transaction.is_some() {
7890 self.discard_edit_prediction(false, cx);
7891 return None;
7892 }
7893
7894 let selection = self.selections.newest_anchor();
7895 let cursor = selection.head();
7896 let multibuffer = self.buffer.read(cx).snapshot(cx);
7897 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7898 let excerpt_id = cursor.excerpt_id;
7899
7900 let show_in_menu = self.show_edit_predictions_in_menu();
7901 let completions_menu_has_precedence = !show_in_menu
7902 && (self.context_menu.borrow().is_some()
7903 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7904
7905 if completions_menu_has_precedence
7906 || !offset_selection.is_empty()
7907 || self
7908 .active_edit_prediction
7909 .as_ref()
7910 .is_some_and(|completion| {
7911 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7912 return false;
7913 };
7914 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7915 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7916 !invalidation_range.contains(&offset_selection.head())
7917 })
7918 {
7919 self.discard_edit_prediction(false, cx);
7920 return None;
7921 }
7922
7923 self.take_active_edit_prediction(cx);
7924 let Some(provider) = self.edit_prediction_provider() else {
7925 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7926 return None;
7927 };
7928
7929 let (buffer, cursor_buffer_position) =
7930 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7931
7932 self.edit_prediction_settings =
7933 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7934
7935 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7936
7937 if self.edit_prediction_indent_conflict {
7938 let cursor_point = cursor.to_point(&multibuffer);
7939
7940 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7941
7942 if let Some((_, indent)) = indents.iter().next()
7943 && indent.len == cursor_point.column
7944 {
7945 self.edit_prediction_indent_conflict = false;
7946 }
7947 }
7948
7949 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7950
7951 let (completion_id, edits, edit_preview) = match edit_prediction {
7952 edit_prediction::EditPrediction::Local {
7953 id,
7954 edits,
7955 edit_preview,
7956 } => (id, edits, edit_preview),
7957 edit_prediction::EditPrediction::Jump {
7958 id,
7959 snapshot,
7960 target,
7961 } => {
7962 self.stale_edit_prediction_in_menu = None;
7963 self.active_edit_prediction = Some(EditPredictionState {
7964 inlay_ids: vec![],
7965 completion: EditPrediction::MoveOutside { snapshot, target },
7966 completion_id: id,
7967 invalidation_range: None,
7968 });
7969 cx.notify();
7970 return Some(());
7971 }
7972 };
7973
7974 let edits = edits
7975 .into_iter()
7976 .flat_map(|(range, new_text)| {
7977 Some((
7978 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
7979 new_text,
7980 ))
7981 })
7982 .collect::<Vec<_>>();
7983 if edits.is_empty() {
7984 return None;
7985 }
7986
7987 let first_edit_start = edits.first().unwrap().0.start;
7988 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7989 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7990
7991 let last_edit_end = edits.last().unwrap().0.end;
7992 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7993 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7994
7995 let cursor_row = cursor.to_point(&multibuffer).row;
7996
7997 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7998
7999 let mut inlay_ids = Vec::new();
8000 let invalidation_row_range;
8001 let move_invalidation_row_range = if cursor_row < edit_start_row {
8002 Some(cursor_row..edit_end_row)
8003 } else if cursor_row > edit_end_row {
8004 Some(edit_start_row..cursor_row)
8005 } else {
8006 None
8007 };
8008 let supports_jump = self
8009 .edit_prediction_provider
8010 .as_ref()
8011 .map(|provider| provider.provider.supports_jump_to_edit())
8012 .unwrap_or(true);
8013
8014 let is_move = supports_jump
8015 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8016 let completion = if is_move {
8017 invalidation_row_range =
8018 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8019 let target = first_edit_start;
8020 EditPrediction::MoveWithin { target, snapshot }
8021 } else {
8022 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8023 && !self.edit_predictions_hidden_for_vim_mode;
8024
8025 if show_completions_in_buffer {
8026 if edits
8027 .iter()
8028 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8029 {
8030 let mut inlays = Vec::new();
8031 for (range, new_text) in &edits {
8032 let inlay = Inlay::edit_prediction(
8033 post_inc(&mut self.next_inlay_id),
8034 range.start,
8035 new_text.as_str(),
8036 );
8037 inlay_ids.push(inlay.id);
8038 inlays.push(inlay);
8039 }
8040
8041 self.splice_inlays(&[], inlays, cx);
8042 } else {
8043 let background_color = cx.theme().status().deleted_background;
8044 self.highlight_text::<EditPredictionHighlight>(
8045 edits.iter().map(|(range, _)| range.clone()).collect(),
8046 HighlightStyle {
8047 background_color: Some(background_color),
8048 ..Default::default()
8049 },
8050 cx,
8051 );
8052 }
8053 }
8054
8055 invalidation_row_range = edit_start_row..edit_end_row;
8056
8057 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8058 if provider.show_tab_accept_marker() {
8059 EditDisplayMode::TabAccept
8060 } else {
8061 EditDisplayMode::Inline
8062 }
8063 } else {
8064 EditDisplayMode::DiffPopover
8065 };
8066
8067 EditPrediction::Edit {
8068 edits,
8069 edit_preview,
8070 display_mode,
8071 snapshot,
8072 }
8073 };
8074
8075 let invalidation_range = multibuffer
8076 .anchor_before(Point::new(invalidation_row_range.start, 0))
8077 ..multibuffer.anchor_after(Point::new(
8078 invalidation_row_range.end,
8079 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8080 ));
8081
8082 self.stale_edit_prediction_in_menu = None;
8083 self.active_edit_prediction = Some(EditPredictionState {
8084 inlay_ids,
8085 completion,
8086 completion_id,
8087 invalidation_range: Some(invalidation_range),
8088 });
8089
8090 cx.notify();
8091
8092 Some(())
8093 }
8094
8095 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8096 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8097 }
8098
8099 fn clear_tasks(&mut self) {
8100 self.tasks.clear()
8101 }
8102
8103 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8104 if self.tasks.insert(key, value).is_some() {
8105 // This case should hopefully be rare, but just in case...
8106 log::error!(
8107 "multiple different run targets found on a single line, only the last target will be rendered"
8108 )
8109 }
8110 }
8111
8112 /// Get all display points of breakpoints that will be rendered within editor
8113 ///
8114 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8115 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8116 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8117 fn active_breakpoints(
8118 &self,
8119 range: Range<DisplayRow>,
8120 window: &mut Window,
8121 cx: &mut Context<Self>,
8122 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8123 let mut breakpoint_display_points = HashMap::default();
8124
8125 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8126 return breakpoint_display_points;
8127 };
8128
8129 let snapshot = self.snapshot(window, cx);
8130
8131 let multi_buffer_snapshot = snapshot.display_snapshot.buffer_snapshot();
8132 let Some(project) = self.project() else {
8133 return breakpoint_display_points;
8134 };
8135
8136 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8137 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8138
8139 for (buffer_snapshot, range, excerpt_id) in
8140 multi_buffer_snapshot.range_to_buffer_ranges(range)
8141 {
8142 let Some(buffer) = project
8143 .read(cx)
8144 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8145 else {
8146 continue;
8147 };
8148 let breakpoints = breakpoint_store.read(cx).breakpoints(
8149 &buffer,
8150 Some(
8151 buffer_snapshot.anchor_before(range.start)
8152 ..buffer_snapshot.anchor_after(range.end),
8153 ),
8154 buffer_snapshot,
8155 cx,
8156 );
8157 for (breakpoint, state) in breakpoints {
8158 let multi_buffer_anchor =
8159 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8160 let position = multi_buffer_anchor
8161 .to_point(multi_buffer_snapshot)
8162 .to_display_point(&snapshot);
8163
8164 breakpoint_display_points.insert(
8165 position.row(),
8166 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8167 );
8168 }
8169 }
8170
8171 breakpoint_display_points
8172 }
8173
8174 fn breakpoint_context_menu(
8175 &self,
8176 anchor: Anchor,
8177 window: &mut Window,
8178 cx: &mut Context<Self>,
8179 ) -> Entity<ui::ContextMenu> {
8180 let weak_editor = cx.weak_entity();
8181 let focus_handle = self.focus_handle(cx);
8182
8183 let row = self
8184 .buffer
8185 .read(cx)
8186 .snapshot(cx)
8187 .summary_for_anchor::<Point>(&anchor)
8188 .row;
8189
8190 let breakpoint = self
8191 .breakpoint_at_row(row, window, cx)
8192 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8193
8194 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8195 "Edit Log Breakpoint"
8196 } else {
8197 "Set Log Breakpoint"
8198 };
8199
8200 let condition_breakpoint_msg = if breakpoint
8201 .as_ref()
8202 .is_some_and(|bp| bp.1.condition.is_some())
8203 {
8204 "Edit Condition Breakpoint"
8205 } else {
8206 "Set Condition Breakpoint"
8207 };
8208
8209 let hit_condition_breakpoint_msg = if breakpoint
8210 .as_ref()
8211 .is_some_and(|bp| bp.1.hit_condition.is_some())
8212 {
8213 "Edit Hit Condition Breakpoint"
8214 } else {
8215 "Set Hit Condition Breakpoint"
8216 };
8217
8218 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8219 "Unset Breakpoint"
8220 } else {
8221 "Set Breakpoint"
8222 };
8223
8224 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8225
8226 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8227 BreakpointState::Enabled => Some("Disable"),
8228 BreakpointState::Disabled => Some("Enable"),
8229 });
8230
8231 let (anchor, breakpoint) =
8232 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8233
8234 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8235 menu.on_blur_subscription(Subscription::new(|| {}))
8236 .context(focus_handle)
8237 .when(run_to_cursor, |this| {
8238 let weak_editor = weak_editor.clone();
8239 this.entry("Run to cursor", None, move |window, cx| {
8240 weak_editor
8241 .update(cx, |editor, cx| {
8242 editor.change_selections(
8243 SelectionEffects::no_scroll(),
8244 window,
8245 cx,
8246 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8247 );
8248 })
8249 .ok();
8250
8251 window.dispatch_action(Box::new(RunToCursor), cx);
8252 })
8253 .separator()
8254 })
8255 .when_some(toggle_state_msg, |this, msg| {
8256 this.entry(msg, None, {
8257 let weak_editor = weak_editor.clone();
8258 let breakpoint = breakpoint.clone();
8259 move |_window, cx| {
8260 weak_editor
8261 .update(cx, |this, cx| {
8262 this.edit_breakpoint_at_anchor(
8263 anchor,
8264 breakpoint.as_ref().clone(),
8265 BreakpointEditAction::InvertState,
8266 cx,
8267 );
8268 })
8269 .log_err();
8270 }
8271 })
8272 })
8273 .entry(set_breakpoint_msg, None, {
8274 let weak_editor = weak_editor.clone();
8275 let breakpoint = breakpoint.clone();
8276 move |_window, cx| {
8277 weak_editor
8278 .update(cx, |this, cx| {
8279 this.edit_breakpoint_at_anchor(
8280 anchor,
8281 breakpoint.as_ref().clone(),
8282 BreakpointEditAction::Toggle,
8283 cx,
8284 );
8285 })
8286 .log_err();
8287 }
8288 })
8289 .entry(log_breakpoint_msg, None, {
8290 let breakpoint = breakpoint.clone();
8291 let weak_editor = weak_editor.clone();
8292 move |window, cx| {
8293 weak_editor
8294 .update(cx, |this, cx| {
8295 this.add_edit_breakpoint_block(
8296 anchor,
8297 breakpoint.as_ref(),
8298 BreakpointPromptEditAction::Log,
8299 window,
8300 cx,
8301 );
8302 })
8303 .log_err();
8304 }
8305 })
8306 .entry(condition_breakpoint_msg, None, {
8307 let breakpoint = breakpoint.clone();
8308 let weak_editor = weak_editor.clone();
8309 move |window, cx| {
8310 weak_editor
8311 .update(cx, |this, cx| {
8312 this.add_edit_breakpoint_block(
8313 anchor,
8314 breakpoint.as_ref(),
8315 BreakpointPromptEditAction::Condition,
8316 window,
8317 cx,
8318 );
8319 })
8320 .log_err();
8321 }
8322 })
8323 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8324 weak_editor
8325 .update(cx, |this, cx| {
8326 this.add_edit_breakpoint_block(
8327 anchor,
8328 breakpoint.as_ref(),
8329 BreakpointPromptEditAction::HitCondition,
8330 window,
8331 cx,
8332 );
8333 })
8334 .log_err();
8335 })
8336 })
8337 }
8338
8339 fn render_breakpoint(
8340 &self,
8341 position: Anchor,
8342 row: DisplayRow,
8343 breakpoint: &Breakpoint,
8344 state: Option<BreakpointSessionState>,
8345 cx: &mut Context<Self>,
8346 ) -> IconButton {
8347 let is_rejected = state.is_some_and(|s| !s.verified);
8348 // Is it a breakpoint that shows up when hovering over gutter?
8349 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8350 (false, false),
8351 |PhantomBreakpointIndicator {
8352 is_active,
8353 display_row,
8354 collides_with_existing_breakpoint,
8355 }| {
8356 (
8357 is_active && display_row == row,
8358 collides_with_existing_breakpoint,
8359 )
8360 },
8361 );
8362
8363 let (color, icon) = {
8364 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8365 (false, false) => ui::IconName::DebugBreakpoint,
8366 (true, false) => ui::IconName::DebugLogBreakpoint,
8367 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8368 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8369 };
8370
8371 let color = if is_phantom {
8372 Color::Hint
8373 } else if is_rejected {
8374 Color::Disabled
8375 } else {
8376 Color::Debugger
8377 };
8378
8379 (color, icon)
8380 };
8381
8382 let breakpoint = Arc::from(breakpoint.clone());
8383
8384 let alt_as_text = gpui::Keystroke {
8385 modifiers: Modifiers::secondary_key(),
8386 ..Default::default()
8387 };
8388 let primary_action_text = if breakpoint.is_disabled() {
8389 "Enable breakpoint"
8390 } else if is_phantom && !collides_with_existing {
8391 "Set breakpoint"
8392 } else {
8393 "Unset breakpoint"
8394 };
8395 let focus_handle = self.focus_handle.clone();
8396
8397 let meta = if is_rejected {
8398 SharedString::from("No executable code is associated with this line.")
8399 } else if collides_with_existing && !breakpoint.is_disabled() {
8400 SharedString::from(format!(
8401 "{alt_as_text}-click to disable,\nright-click for more options."
8402 ))
8403 } else {
8404 SharedString::from("Right-click for more options.")
8405 };
8406 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8407 .icon_size(IconSize::XSmall)
8408 .size(ui::ButtonSize::None)
8409 .when(is_rejected, |this| {
8410 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8411 })
8412 .icon_color(color)
8413 .style(ButtonStyle::Transparent)
8414 .on_click(cx.listener({
8415 move |editor, event: &ClickEvent, window, cx| {
8416 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8417 BreakpointEditAction::InvertState
8418 } else {
8419 BreakpointEditAction::Toggle
8420 };
8421
8422 window.focus(&editor.focus_handle(cx));
8423 editor.edit_breakpoint_at_anchor(
8424 position,
8425 breakpoint.as_ref().clone(),
8426 edit_action,
8427 cx,
8428 );
8429 }
8430 }))
8431 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8432 editor.set_breakpoint_context_menu(
8433 row,
8434 Some(position),
8435 event.position(),
8436 window,
8437 cx,
8438 );
8439 }))
8440 .tooltip(move |window, cx| {
8441 Tooltip::with_meta_in(
8442 primary_action_text,
8443 Some(&ToggleBreakpoint),
8444 meta.clone(),
8445 &focus_handle,
8446 window,
8447 cx,
8448 )
8449 })
8450 }
8451
8452 fn build_tasks_context(
8453 project: &Entity<Project>,
8454 buffer: &Entity<Buffer>,
8455 buffer_row: u32,
8456 tasks: &Arc<RunnableTasks>,
8457 cx: &mut Context<Self>,
8458 ) -> Task<Option<task::TaskContext>> {
8459 let position = Point::new(buffer_row, tasks.column);
8460 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8461 let location = Location {
8462 buffer: buffer.clone(),
8463 range: range_start..range_start,
8464 };
8465 // Fill in the environmental variables from the tree-sitter captures
8466 let mut captured_task_variables = TaskVariables::default();
8467 for (capture_name, value) in tasks.extra_variables.clone() {
8468 captured_task_variables.insert(
8469 task::VariableName::Custom(capture_name.into()),
8470 value.clone(),
8471 );
8472 }
8473 project.update(cx, |project, cx| {
8474 project.task_store().update(cx, |task_store, cx| {
8475 task_store.task_context_for_location(captured_task_variables, location, cx)
8476 })
8477 })
8478 }
8479
8480 pub fn spawn_nearest_task(
8481 &mut self,
8482 action: &SpawnNearestTask,
8483 window: &mut Window,
8484 cx: &mut Context<Self>,
8485 ) {
8486 let Some((workspace, _)) = self.workspace.clone() else {
8487 return;
8488 };
8489 let Some(project) = self.project.clone() else {
8490 return;
8491 };
8492
8493 // Try to find a closest, enclosing node using tree-sitter that has a task
8494 let Some((buffer, buffer_row, tasks)) = self
8495 .find_enclosing_node_task(cx)
8496 // Or find the task that's closest in row-distance.
8497 .or_else(|| self.find_closest_task(cx))
8498 else {
8499 return;
8500 };
8501
8502 let reveal_strategy = action.reveal;
8503 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8504 cx.spawn_in(window, async move |_, cx| {
8505 let context = task_context.await?;
8506 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8507
8508 let resolved = &mut resolved_task.resolved;
8509 resolved.reveal = reveal_strategy;
8510
8511 workspace
8512 .update_in(cx, |workspace, window, cx| {
8513 workspace.schedule_resolved_task(
8514 task_source_kind,
8515 resolved_task,
8516 false,
8517 window,
8518 cx,
8519 );
8520 })
8521 .ok()
8522 })
8523 .detach();
8524 }
8525
8526 fn find_closest_task(
8527 &mut self,
8528 cx: &mut Context<Self>,
8529 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8530 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8531
8532 let ((buffer_id, row), tasks) = self
8533 .tasks
8534 .iter()
8535 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8536
8537 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8538 let tasks = Arc::new(tasks.to_owned());
8539 Some((buffer, *row, tasks))
8540 }
8541
8542 fn find_enclosing_node_task(
8543 &mut self,
8544 cx: &mut Context<Self>,
8545 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8546 let snapshot = self.buffer.read(cx).snapshot(cx);
8547 let offset = self.selections.newest::<usize>(cx).head();
8548 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8549 let buffer_id = excerpt.buffer().remote_id();
8550
8551 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8552 let mut cursor = layer.node().walk();
8553
8554 while cursor.goto_first_child_for_byte(offset).is_some() {
8555 if cursor.node().end_byte() == offset {
8556 cursor.goto_next_sibling();
8557 }
8558 }
8559
8560 // Ascend to the smallest ancestor that contains the range and has a task.
8561 loop {
8562 let node = cursor.node();
8563 let node_range = node.byte_range();
8564 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8565
8566 // Check if this node contains our offset
8567 if node_range.start <= offset && node_range.end >= offset {
8568 // If it contains offset, check for task
8569 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8570 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8571 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8572 }
8573 }
8574
8575 if !cursor.goto_parent() {
8576 break;
8577 }
8578 }
8579 None
8580 }
8581
8582 fn render_run_indicator(
8583 &self,
8584 _style: &EditorStyle,
8585 is_active: bool,
8586 row: DisplayRow,
8587 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8588 cx: &mut Context<Self>,
8589 ) -> IconButton {
8590 let color = Color::Muted;
8591 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8592
8593 IconButton::new(
8594 ("run_indicator", row.0 as usize),
8595 ui::IconName::PlayOutlined,
8596 )
8597 .shape(ui::IconButtonShape::Square)
8598 .icon_size(IconSize::XSmall)
8599 .icon_color(color)
8600 .toggle_state(is_active)
8601 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8602 let quick_launch = match e {
8603 ClickEvent::Keyboard(_) => true,
8604 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8605 };
8606
8607 window.focus(&editor.focus_handle(cx));
8608 editor.toggle_code_actions(
8609 &ToggleCodeActions {
8610 deployed_from: Some(CodeActionSource::RunMenu(row)),
8611 quick_launch,
8612 },
8613 window,
8614 cx,
8615 );
8616 }))
8617 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8618 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8619 }))
8620 }
8621
8622 pub fn context_menu_visible(&self) -> bool {
8623 !self.edit_prediction_preview_is_active()
8624 && self
8625 .context_menu
8626 .borrow()
8627 .as_ref()
8628 .is_some_and(|menu| menu.visible())
8629 }
8630
8631 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8632 self.context_menu
8633 .borrow()
8634 .as_ref()
8635 .map(|menu| menu.origin())
8636 }
8637
8638 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8639 self.context_menu_options = Some(options);
8640 }
8641
8642 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8643 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8644
8645 fn render_edit_prediction_popover(
8646 &mut self,
8647 text_bounds: &Bounds<Pixels>,
8648 content_origin: gpui::Point<Pixels>,
8649 right_margin: Pixels,
8650 editor_snapshot: &EditorSnapshot,
8651 visible_row_range: Range<DisplayRow>,
8652 scroll_top: ScrollOffset,
8653 scroll_bottom: ScrollOffset,
8654 line_layouts: &[LineWithInvisibles],
8655 line_height: Pixels,
8656 scroll_position: gpui::Point<ScrollOffset>,
8657 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8658 newest_selection_head: Option<DisplayPoint>,
8659 editor_width: Pixels,
8660 style: &EditorStyle,
8661 window: &mut Window,
8662 cx: &mut App,
8663 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8664 if self.mode().is_minimap() {
8665 return None;
8666 }
8667 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8668
8669 if self.edit_prediction_visible_in_cursor_popover(true) {
8670 return None;
8671 }
8672
8673 match &active_edit_prediction.completion {
8674 EditPrediction::MoveWithin { target, .. } => {
8675 let target_display_point = target.to_display_point(editor_snapshot);
8676
8677 if self.edit_prediction_requires_modifier() {
8678 if !self.edit_prediction_preview_is_active() {
8679 return None;
8680 }
8681
8682 self.render_edit_prediction_modifier_jump_popover(
8683 text_bounds,
8684 content_origin,
8685 visible_row_range,
8686 line_layouts,
8687 line_height,
8688 scroll_pixel_position,
8689 newest_selection_head,
8690 target_display_point,
8691 window,
8692 cx,
8693 )
8694 } else {
8695 self.render_edit_prediction_eager_jump_popover(
8696 text_bounds,
8697 content_origin,
8698 editor_snapshot,
8699 visible_row_range,
8700 scroll_top,
8701 scroll_bottom,
8702 line_height,
8703 scroll_pixel_position,
8704 target_display_point,
8705 editor_width,
8706 window,
8707 cx,
8708 )
8709 }
8710 }
8711 EditPrediction::Edit {
8712 display_mode: EditDisplayMode::Inline,
8713 ..
8714 } => None,
8715 EditPrediction::Edit {
8716 display_mode: EditDisplayMode::TabAccept,
8717 edits,
8718 ..
8719 } => {
8720 let range = &edits.first()?.0;
8721 let target_display_point = range.end.to_display_point(editor_snapshot);
8722
8723 self.render_edit_prediction_end_of_line_popover(
8724 "Accept",
8725 editor_snapshot,
8726 visible_row_range,
8727 target_display_point,
8728 line_height,
8729 scroll_pixel_position,
8730 content_origin,
8731 editor_width,
8732 window,
8733 cx,
8734 )
8735 }
8736 EditPrediction::Edit {
8737 edits,
8738 edit_preview,
8739 display_mode: EditDisplayMode::DiffPopover,
8740 snapshot,
8741 } => self.render_edit_prediction_diff_popover(
8742 text_bounds,
8743 content_origin,
8744 right_margin,
8745 editor_snapshot,
8746 visible_row_range,
8747 line_layouts,
8748 line_height,
8749 scroll_position,
8750 scroll_pixel_position,
8751 newest_selection_head,
8752 editor_width,
8753 style,
8754 edits,
8755 edit_preview,
8756 snapshot,
8757 window,
8758 cx,
8759 ),
8760 EditPrediction::MoveOutside { snapshot, .. } => {
8761 let file_name = snapshot
8762 .file()
8763 .map(|file| file.file_name(cx))
8764 .unwrap_or("untitled");
8765 let mut element = self
8766 .render_edit_prediction_line_popover(
8767 format!("Jump to {file_name}"),
8768 Some(IconName::ZedPredict),
8769 window,
8770 cx,
8771 )
8772 .into_any();
8773
8774 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8775 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8776 let origin_y = text_bounds.size.height - size.height - px(30.);
8777 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8778 element.prepaint_at(origin, window, cx);
8779
8780 Some((element, origin))
8781 }
8782 }
8783 }
8784
8785 fn render_edit_prediction_modifier_jump_popover(
8786 &mut self,
8787 text_bounds: &Bounds<Pixels>,
8788 content_origin: gpui::Point<Pixels>,
8789 visible_row_range: Range<DisplayRow>,
8790 line_layouts: &[LineWithInvisibles],
8791 line_height: Pixels,
8792 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8793 newest_selection_head: Option<DisplayPoint>,
8794 target_display_point: DisplayPoint,
8795 window: &mut Window,
8796 cx: &mut App,
8797 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8798 let scrolled_content_origin =
8799 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8800
8801 const SCROLL_PADDING_Y: Pixels = px(12.);
8802
8803 if target_display_point.row() < visible_row_range.start {
8804 return self.render_edit_prediction_scroll_popover(
8805 |_| SCROLL_PADDING_Y,
8806 IconName::ArrowUp,
8807 visible_row_range,
8808 line_layouts,
8809 newest_selection_head,
8810 scrolled_content_origin,
8811 window,
8812 cx,
8813 );
8814 } else if target_display_point.row() >= visible_row_range.end {
8815 return self.render_edit_prediction_scroll_popover(
8816 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8817 IconName::ArrowDown,
8818 visible_row_range,
8819 line_layouts,
8820 newest_selection_head,
8821 scrolled_content_origin,
8822 window,
8823 cx,
8824 );
8825 }
8826
8827 const POLE_WIDTH: Pixels = px(2.);
8828
8829 let line_layout =
8830 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8831 let target_column = target_display_point.column() as usize;
8832
8833 let target_x = line_layout.x_for_index(target_column);
8834 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8835 - scroll_pixel_position.y;
8836
8837 let flag_on_right = target_x < text_bounds.size.width / 2.;
8838
8839 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8840 border_color.l += 0.001;
8841
8842 let mut element = v_flex()
8843 .items_end()
8844 .when(flag_on_right, |el| el.items_start())
8845 .child(if flag_on_right {
8846 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8847 .rounded_bl(px(0.))
8848 .rounded_tl(px(0.))
8849 .border_l_2()
8850 .border_color(border_color)
8851 } else {
8852 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8853 .rounded_br(px(0.))
8854 .rounded_tr(px(0.))
8855 .border_r_2()
8856 .border_color(border_color)
8857 })
8858 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8859 .into_any();
8860
8861 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8862
8863 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8864 - point(
8865 if flag_on_right {
8866 POLE_WIDTH
8867 } else {
8868 size.width - POLE_WIDTH
8869 },
8870 size.height - line_height,
8871 );
8872
8873 origin.x = origin.x.max(content_origin.x);
8874
8875 element.prepaint_at(origin, window, cx);
8876
8877 Some((element, origin))
8878 }
8879
8880 fn render_edit_prediction_scroll_popover(
8881 &mut self,
8882 to_y: impl Fn(Size<Pixels>) -> Pixels,
8883 scroll_icon: IconName,
8884 visible_row_range: Range<DisplayRow>,
8885 line_layouts: &[LineWithInvisibles],
8886 newest_selection_head: Option<DisplayPoint>,
8887 scrolled_content_origin: gpui::Point<Pixels>,
8888 window: &mut Window,
8889 cx: &mut App,
8890 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8891 let mut element = self
8892 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8893 .into_any();
8894
8895 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8896
8897 let cursor = newest_selection_head?;
8898 let cursor_row_layout =
8899 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8900 let cursor_column = cursor.column() as usize;
8901
8902 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8903
8904 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8905
8906 element.prepaint_at(origin, window, cx);
8907 Some((element, origin))
8908 }
8909
8910 fn render_edit_prediction_eager_jump_popover(
8911 &mut self,
8912 text_bounds: &Bounds<Pixels>,
8913 content_origin: gpui::Point<Pixels>,
8914 editor_snapshot: &EditorSnapshot,
8915 visible_row_range: Range<DisplayRow>,
8916 scroll_top: ScrollOffset,
8917 scroll_bottom: ScrollOffset,
8918 line_height: Pixels,
8919 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8920 target_display_point: DisplayPoint,
8921 editor_width: Pixels,
8922 window: &mut Window,
8923 cx: &mut App,
8924 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8925 if target_display_point.row().as_f64() < scroll_top {
8926 let mut element = self
8927 .render_edit_prediction_line_popover(
8928 "Jump to Edit",
8929 Some(IconName::ArrowUp),
8930 window,
8931 cx,
8932 )
8933 .into_any();
8934
8935 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8936 let offset = point(
8937 (text_bounds.size.width - size.width) / 2.,
8938 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8939 );
8940
8941 let origin = text_bounds.origin + offset;
8942 element.prepaint_at(origin, window, cx);
8943 Some((element, origin))
8944 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8945 let mut element = self
8946 .render_edit_prediction_line_popover(
8947 "Jump to Edit",
8948 Some(IconName::ArrowDown),
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 text_bounds.size.height - size.height - 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 {
8964 self.render_edit_prediction_end_of_line_popover(
8965 "Jump to Edit",
8966 editor_snapshot,
8967 visible_row_range,
8968 target_display_point,
8969 line_height,
8970 scroll_pixel_position,
8971 content_origin,
8972 editor_width,
8973 window,
8974 cx,
8975 )
8976 }
8977 }
8978
8979 fn render_edit_prediction_end_of_line_popover(
8980 self: &mut Editor,
8981 label: &'static str,
8982 editor_snapshot: &EditorSnapshot,
8983 visible_row_range: Range<DisplayRow>,
8984 target_display_point: DisplayPoint,
8985 line_height: Pixels,
8986 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8987 content_origin: gpui::Point<Pixels>,
8988 editor_width: Pixels,
8989 window: &mut Window,
8990 cx: &mut App,
8991 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8992 let target_line_end = DisplayPoint::new(
8993 target_display_point.row(),
8994 editor_snapshot.line_len(target_display_point.row()),
8995 );
8996
8997 let mut element = self
8998 .render_edit_prediction_line_popover(label, None, window, cx)
8999 .into_any();
9000
9001 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9002
9003 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
9004
9005 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9006 let mut origin = start_point
9007 + line_origin
9008 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9009 origin.x = origin.x.max(content_origin.x);
9010
9011 let max_x = content_origin.x + editor_width - size.width;
9012
9013 if origin.x > max_x {
9014 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9015
9016 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9017 origin.y += offset;
9018 IconName::ArrowUp
9019 } else {
9020 origin.y -= offset;
9021 IconName::ArrowDown
9022 };
9023
9024 element = self
9025 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9026 .into_any();
9027
9028 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9029
9030 origin.x = content_origin.x + editor_width - size.width - px(2.);
9031 }
9032
9033 element.prepaint_at(origin, window, cx);
9034 Some((element, origin))
9035 }
9036
9037 fn render_edit_prediction_diff_popover(
9038 self: &Editor,
9039 text_bounds: &Bounds<Pixels>,
9040 content_origin: gpui::Point<Pixels>,
9041 right_margin: Pixels,
9042 editor_snapshot: &EditorSnapshot,
9043 visible_row_range: Range<DisplayRow>,
9044 line_layouts: &[LineWithInvisibles],
9045 line_height: Pixels,
9046 scroll_position: gpui::Point<ScrollOffset>,
9047 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9048 newest_selection_head: Option<DisplayPoint>,
9049 editor_width: Pixels,
9050 style: &EditorStyle,
9051 edits: &Vec<(Range<Anchor>, String)>,
9052 edit_preview: &Option<language::EditPreview>,
9053 snapshot: &language::BufferSnapshot,
9054 window: &mut Window,
9055 cx: &mut App,
9056 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9057 let edit_start = edits
9058 .first()
9059 .unwrap()
9060 .0
9061 .start
9062 .to_display_point(editor_snapshot);
9063 let edit_end = edits
9064 .last()
9065 .unwrap()
9066 .0
9067 .end
9068 .to_display_point(editor_snapshot);
9069
9070 let is_visible = visible_row_range.contains(&edit_start.row())
9071 || visible_row_range.contains(&edit_end.row());
9072 if !is_visible {
9073 return None;
9074 }
9075
9076 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9077 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9078 } else {
9079 // Fallback for providers without edit_preview
9080 crate::edit_prediction_fallback_text(edits, cx)
9081 };
9082
9083 let styled_text = highlighted_edits.to_styled_text(&style.text);
9084 let line_count = highlighted_edits.text.lines().count();
9085
9086 const BORDER_WIDTH: Pixels = px(1.);
9087
9088 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9089 let has_keybind = keybind.is_some();
9090
9091 let mut element = h_flex()
9092 .items_start()
9093 .child(
9094 h_flex()
9095 .bg(cx.theme().colors().editor_background)
9096 .border(BORDER_WIDTH)
9097 .shadow_xs()
9098 .border_color(cx.theme().colors().border)
9099 .rounded_l_lg()
9100 .when(line_count > 1, |el| el.rounded_br_lg())
9101 .pr_1()
9102 .child(styled_text),
9103 )
9104 .child(
9105 h_flex()
9106 .h(line_height + BORDER_WIDTH * 2.)
9107 .px_1p5()
9108 .gap_1()
9109 // Workaround: For some reason, there's a gap if we don't do this
9110 .ml(-BORDER_WIDTH)
9111 .shadow(vec![gpui::BoxShadow {
9112 color: gpui::black().opacity(0.05),
9113 offset: point(px(1.), px(1.)),
9114 blur_radius: px(2.),
9115 spread_radius: px(0.),
9116 }])
9117 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9118 .border(BORDER_WIDTH)
9119 .border_color(cx.theme().colors().border)
9120 .rounded_r_lg()
9121 .id("edit_prediction_diff_popover_keybind")
9122 .when(!has_keybind, |el| {
9123 let status_colors = cx.theme().status();
9124
9125 el.bg(status_colors.error_background)
9126 .border_color(status_colors.error.opacity(0.6))
9127 .child(Icon::new(IconName::Info).color(Color::Error))
9128 .cursor_default()
9129 .hoverable_tooltip(move |_window, cx| {
9130 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9131 })
9132 })
9133 .children(keybind),
9134 )
9135 .into_any();
9136
9137 let longest_row =
9138 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9139 let longest_line_width = if visible_row_range.contains(&longest_row) {
9140 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9141 } else {
9142 layout_line(
9143 longest_row,
9144 editor_snapshot,
9145 style,
9146 editor_width,
9147 |_| false,
9148 window,
9149 cx,
9150 )
9151 .width
9152 };
9153
9154 let viewport_bounds =
9155 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9156 right: -right_margin,
9157 ..Default::default()
9158 });
9159
9160 let x_after_longest = Pixels::from(
9161 ScrollPixelOffset::from(
9162 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9163 ) - scroll_pixel_position.x,
9164 );
9165
9166 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9167
9168 // Fully visible if it can be displayed within the window (allow overlapping other
9169 // panes). However, this is only allowed if the popover starts within text_bounds.
9170 let can_position_to_the_right = x_after_longest < text_bounds.right()
9171 && x_after_longest + element_bounds.width < viewport_bounds.right();
9172
9173 let mut origin = if can_position_to_the_right {
9174 point(
9175 x_after_longest,
9176 text_bounds.origin.y
9177 + Pixels::from(
9178 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9179 - scroll_pixel_position.y,
9180 ),
9181 )
9182 } else {
9183 let cursor_row = newest_selection_head.map(|head| head.row());
9184 let above_edit = edit_start
9185 .row()
9186 .0
9187 .checked_sub(line_count as u32)
9188 .map(DisplayRow);
9189 let below_edit = Some(edit_end.row() + 1);
9190 let above_cursor =
9191 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9192 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9193
9194 // Place the edit popover adjacent to the edit if there is a location
9195 // available that is onscreen and does not obscure the cursor. Otherwise,
9196 // place it adjacent to the cursor.
9197 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9198 .into_iter()
9199 .flatten()
9200 .find(|&start_row| {
9201 let end_row = start_row + line_count as u32;
9202 visible_row_range.contains(&start_row)
9203 && visible_row_range.contains(&end_row)
9204 && cursor_row
9205 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9206 })?;
9207
9208 content_origin
9209 + point(
9210 Pixels::from(-scroll_pixel_position.x),
9211 Pixels::from(
9212 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9213 ),
9214 )
9215 };
9216
9217 origin.x -= BORDER_WIDTH;
9218
9219 window.defer_draw(element, origin, 1);
9220
9221 // Do not return an element, since it will already be drawn due to defer_draw.
9222 None
9223 }
9224
9225 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9226 px(30.)
9227 }
9228
9229 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9230 if self.read_only(cx) {
9231 cx.theme().players().read_only()
9232 } else {
9233 self.style.as_ref().unwrap().local_player
9234 }
9235 }
9236
9237 fn render_edit_prediction_accept_keybind(
9238 &self,
9239 window: &mut Window,
9240 cx: &App,
9241 ) -> Option<AnyElement> {
9242 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9243 let accept_keystroke = accept_binding.keystroke()?;
9244
9245 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9246
9247 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9248 Color::Accent
9249 } else {
9250 Color::Muted
9251 };
9252
9253 h_flex()
9254 .px_0p5()
9255 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9256 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9257 .text_size(TextSize::XSmall.rems(cx))
9258 .child(h_flex().children(ui::render_modifiers(
9259 accept_keystroke.modifiers(),
9260 PlatformStyle::platform(),
9261 Some(modifiers_color),
9262 Some(IconSize::XSmall.rems().into()),
9263 true,
9264 )))
9265 .when(is_platform_style_mac, |parent| {
9266 parent.child(accept_keystroke.key().to_string())
9267 })
9268 .when(!is_platform_style_mac, |parent| {
9269 parent.child(
9270 Key::new(
9271 util::capitalize(accept_keystroke.key()),
9272 Some(Color::Default),
9273 )
9274 .size(Some(IconSize::XSmall.rems().into())),
9275 )
9276 })
9277 .into_any()
9278 .into()
9279 }
9280
9281 fn render_edit_prediction_line_popover(
9282 &self,
9283 label: impl Into<SharedString>,
9284 icon: Option<IconName>,
9285 window: &mut Window,
9286 cx: &App,
9287 ) -> Stateful<Div> {
9288 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9289
9290 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9291 let has_keybind = keybind.is_some();
9292
9293 h_flex()
9294 .id("ep-line-popover")
9295 .py_0p5()
9296 .pl_1()
9297 .pr(padding_right)
9298 .gap_1()
9299 .rounded_md()
9300 .border_1()
9301 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9302 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9303 .shadow_xs()
9304 .when(!has_keybind, |el| {
9305 let status_colors = cx.theme().status();
9306
9307 el.bg(status_colors.error_background)
9308 .border_color(status_colors.error.opacity(0.6))
9309 .pl_2()
9310 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9311 .cursor_default()
9312 .hoverable_tooltip(move |_window, cx| {
9313 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9314 })
9315 })
9316 .children(keybind)
9317 .child(
9318 Label::new(label)
9319 .size(LabelSize::Small)
9320 .when(!has_keybind, |el| {
9321 el.color(cx.theme().status().error.into()).strikethrough()
9322 }),
9323 )
9324 .when(!has_keybind, |el| {
9325 el.child(
9326 h_flex().ml_1().child(
9327 Icon::new(IconName::Info)
9328 .size(IconSize::Small)
9329 .color(cx.theme().status().error.into()),
9330 ),
9331 )
9332 })
9333 .when_some(icon, |element, icon| {
9334 element.child(
9335 div()
9336 .mt(px(1.5))
9337 .child(Icon::new(icon).size(IconSize::Small)),
9338 )
9339 })
9340 }
9341
9342 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9343 let accent_color = cx.theme().colors().text_accent;
9344 let editor_bg_color = cx.theme().colors().editor_background;
9345 editor_bg_color.blend(accent_color.opacity(0.1))
9346 }
9347
9348 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9349 let accent_color = cx.theme().colors().text_accent;
9350 let editor_bg_color = cx.theme().colors().editor_background;
9351 editor_bg_color.blend(accent_color.opacity(0.6))
9352 }
9353 fn get_prediction_provider_icon_name(
9354 provider: &Option<RegisteredEditPredictionProvider>,
9355 ) -> IconName {
9356 match provider {
9357 Some(provider) => match provider.provider.name() {
9358 "copilot" => IconName::Copilot,
9359 "supermaven" => IconName::Supermaven,
9360 _ => IconName::ZedPredict,
9361 },
9362 None => IconName::ZedPredict,
9363 }
9364 }
9365
9366 fn render_edit_prediction_cursor_popover(
9367 &self,
9368 min_width: Pixels,
9369 max_width: Pixels,
9370 cursor_point: Point,
9371 style: &EditorStyle,
9372 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9373 _window: &Window,
9374 cx: &mut Context<Editor>,
9375 ) -> Option<AnyElement> {
9376 let provider = self.edit_prediction_provider.as_ref()?;
9377 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9378
9379 let is_refreshing = provider.provider.is_refreshing(cx);
9380
9381 fn pending_completion_container(icon: IconName) -> Div {
9382 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9383 }
9384
9385 let completion = match &self.active_edit_prediction {
9386 Some(prediction) => {
9387 if !self.has_visible_completions_menu() {
9388 const RADIUS: Pixels = px(6.);
9389 const BORDER_WIDTH: Pixels = px(1.);
9390
9391 return Some(
9392 h_flex()
9393 .elevation_2(cx)
9394 .border(BORDER_WIDTH)
9395 .border_color(cx.theme().colors().border)
9396 .when(accept_keystroke.is_none(), |el| {
9397 el.border_color(cx.theme().status().error)
9398 })
9399 .rounded(RADIUS)
9400 .rounded_tl(px(0.))
9401 .overflow_hidden()
9402 .child(div().px_1p5().child(match &prediction.completion {
9403 EditPrediction::MoveWithin { target, snapshot } => {
9404 use text::ToPoint as _;
9405 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9406 {
9407 Icon::new(IconName::ZedPredictDown)
9408 } else {
9409 Icon::new(IconName::ZedPredictUp)
9410 }
9411 }
9412 EditPrediction::MoveOutside { .. } => {
9413 // TODO [zeta2] custom icon for external jump?
9414 Icon::new(provider_icon)
9415 }
9416 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9417 }))
9418 .child(
9419 h_flex()
9420 .gap_1()
9421 .py_1()
9422 .px_2()
9423 .rounded_r(RADIUS - BORDER_WIDTH)
9424 .border_l_1()
9425 .border_color(cx.theme().colors().border)
9426 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9427 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9428 el.child(
9429 Label::new("Hold")
9430 .size(LabelSize::Small)
9431 .when(accept_keystroke.is_none(), |el| {
9432 el.strikethrough()
9433 })
9434 .line_height_style(LineHeightStyle::UiLabel),
9435 )
9436 })
9437 .id("edit_prediction_cursor_popover_keybind")
9438 .when(accept_keystroke.is_none(), |el| {
9439 let status_colors = cx.theme().status();
9440
9441 el.bg(status_colors.error_background)
9442 .border_color(status_colors.error.opacity(0.6))
9443 .child(Icon::new(IconName::Info).color(Color::Error))
9444 .cursor_default()
9445 .hoverable_tooltip(move |_window, cx| {
9446 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9447 .into()
9448 })
9449 })
9450 .when_some(
9451 accept_keystroke.as_ref(),
9452 |el, accept_keystroke| {
9453 el.child(h_flex().children(ui::render_modifiers(
9454 accept_keystroke.modifiers(),
9455 PlatformStyle::platform(),
9456 Some(Color::Default),
9457 Some(IconSize::XSmall.rems().into()),
9458 false,
9459 )))
9460 },
9461 ),
9462 )
9463 .into_any(),
9464 );
9465 }
9466
9467 self.render_edit_prediction_cursor_popover_preview(
9468 prediction,
9469 cursor_point,
9470 style,
9471 cx,
9472 )?
9473 }
9474
9475 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9476 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9477 stale_completion,
9478 cursor_point,
9479 style,
9480 cx,
9481 )?,
9482
9483 None => pending_completion_container(provider_icon)
9484 .child(Label::new("...").size(LabelSize::Small)),
9485 },
9486
9487 None => pending_completion_container(provider_icon)
9488 .child(Label::new("...").size(LabelSize::Small)),
9489 };
9490
9491 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9492 completion
9493 .with_animation(
9494 "loading-completion",
9495 Animation::new(Duration::from_secs(2))
9496 .repeat()
9497 .with_easing(pulsating_between(0.4, 0.8)),
9498 |label, delta| label.opacity(delta),
9499 )
9500 .into_any_element()
9501 } else {
9502 completion.into_any_element()
9503 };
9504
9505 let has_completion = self.active_edit_prediction.is_some();
9506
9507 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9508 Some(
9509 h_flex()
9510 .min_w(min_width)
9511 .max_w(max_width)
9512 .flex_1()
9513 .elevation_2(cx)
9514 .border_color(cx.theme().colors().border)
9515 .child(
9516 div()
9517 .flex_1()
9518 .py_1()
9519 .px_2()
9520 .overflow_hidden()
9521 .child(completion),
9522 )
9523 .when_some(accept_keystroke, |el, accept_keystroke| {
9524 if !accept_keystroke.modifiers().modified() {
9525 return el;
9526 }
9527
9528 el.child(
9529 h_flex()
9530 .h_full()
9531 .border_l_1()
9532 .rounded_r_lg()
9533 .border_color(cx.theme().colors().border)
9534 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9535 .gap_1()
9536 .py_1()
9537 .px_2()
9538 .child(
9539 h_flex()
9540 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9541 .when(is_platform_style_mac, |parent| parent.gap_1())
9542 .child(h_flex().children(ui::render_modifiers(
9543 accept_keystroke.modifiers(),
9544 PlatformStyle::platform(),
9545 Some(if !has_completion {
9546 Color::Muted
9547 } else {
9548 Color::Default
9549 }),
9550 None,
9551 false,
9552 ))),
9553 )
9554 .child(Label::new("Preview").into_any_element())
9555 .opacity(if has_completion { 1.0 } else { 0.4 }),
9556 )
9557 })
9558 .into_any(),
9559 )
9560 }
9561
9562 fn render_edit_prediction_cursor_popover_preview(
9563 &self,
9564 completion: &EditPredictionState,
9565 cursor_point: Point,
9566 style: &EditorStyle,
9567 cx: &mut Context<Editor>,
9568 ) -> Option<Div> {
9569 use text::ToPoint as _;
9570
9571 fn render_relative_row_jump(
9572 prefix: impl Into<String>,
9573 current_row: u32,
9574 target_row: u32,
9575 ) -> Div {
9576 let (row_diff, arrow) = if target_row < current_row {
9577 (current_row - target_row, IconName::ArrowUp)
9578 } else {
9579 (target_row - current_row, IconName::ArrowDown)
9580 };
9581
9582 h_flex()
9583 .child(
9584 Label::new(format!("{}{}", prefix.into(), row_diff))
9585 .color(Color::Muted)
9586 .size(LabelSize::Small),
9587 )
9588 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9589 }
9590
9591 let supports_jump = self
9592 .edit_prediction_provider
9593 .as_ref()
9594 .map(|provider| provider.provider.supports_jump_to_edit())
9595 .unwrap_or(true);
9596
9597 match &completion.completion {
9598 EditPrediction::MoveWithin {
9599 target, snapshot, ..
9600 } => {
9601 if !supports_jump {
9602 return None;
9603 }
9604
9605 Some(
9606 h_flex()
9607 .px_2()
9608 .gap_2()
9609 .flex_1()
9610 .child(
9611 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9612 Icon::new(IconName::ZedPredictDown)
9613 } else {
9614 Icon::new(IconName::ZedPredictUp)
9615 },
9616 )
9617 .child(Label::new("Jump to Edit")),
9618 )
9619 }
9620 EditPrediction::MoveOutside { snapshot, .. } => {
9621 let file_name = snapshot
9622 .file()
9623 .map(|file| file.file_name(cx))
9624 .unwrap_or("untitled");
9625 Some(
9626 h_flex()
9627 .px_2()
9628 .gap_2()
9629 .flex_1()
9630 .child(Icon::new(IconName::ZedPredict))
9631 .child(Label::new(format!("Jump to {file_name}"))),
9632 )
9633 }
9634 EditPrediction::Edit {
9635 edits,
9636 edit_preview,
9637 snapshot,
9638 display_mode: _,
9639 } => {
9640 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9641
9642 let (highlighted_edits, has_more_lines) =
9643 if let Some(edit_preview) = edit_preview.as_ref() {
9644 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9645 .first_line_preview()
9646 } else {
9647 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9648 };
9649
9650 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9651 .with_default_highlights(&style.text, highlighted_edits.highlights);
9652
9653 let preview = h_flex()
9654 .gap_1()
9655 .min_w_16()
9656 .child(styled_text)
9657 .when(has_more_lines, |parent| parent.child("…"));
9658
9659 let left = if supports_jump && first_edit_row != cursor_point.row {
9660 render_relative_row_jump("", cursor_point.row, first_edit_row)
9661 .into_any_element()
9662 } else {
9663 let icon_name =
9664 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9665 Icon::new(icon_name).into_any_element()
9666 };
9667
9668 Some(
9669 h_flex()
9670 .h_full()
9671 .flex_1()
9672 .gap_2()
9673 .pr_1()
9674 .overflow_x_hidden()
9675 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9676 .child(left)
9677 .child(preview),
9678 )
9679 }
9680 }
9681 }
9682
9683 pub fn render_context_menu(
9684 &self,
9685 style: &EditorStyle,
9686 max_height_in_lines: u32,
9687 window: &mut Window,
9688 cx: &mut Context<Editor>,
9689 ) -> Option<AnyElement> {
9690 let menu = self.context_menu.borrow();
9691 let menu = menu.as_ref()?;
9692 if !menu.visible() {
9693 return None;
9694 };
9695 Some(menu.render(style, max_height_in_lines, window, cx))
9696 }
9697
9698 fn render_context_menu_aside(
9699 &mut self,
9700 max_size: Size<Pixels>,
9701 window: &mut Window,
9702 cx: &mut Context<Editor>,
9703 ) -> Option<AnyElement> {
9704 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9705 if menu.visible() {
9706 menu.render_aside(max_size, window, cx)
9707 } else {
9708 None
9709 }
9710 })
9711 }
9712
9713 fn hide_context_menu(
9714 &mut self,
9715 window: &mut Window,
9716 cx: &mut Context<Self>,
9717 ) -> Option<CodeContextMenu> {
9718 cx.notify();
9719 self.completion_tasks.clear();
9720 let context_menu = self.context_menu.borrow_mut().take();
9721 self.stale_edit_prediction_in_menu.take();
9722 self.update_visible_edit_prediction(window, cx);
9723 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9724 && let Some(completion_provider) = &self.completion_provider
9725 {
9726 completion_provider.selection_changed(None, window, cx);
9727 }
9728 context_menu
9729 }
9730
9731 fn show_snippet_choices(
9732 &mut self,
9733 choices: &Vec<String>,
9734 selection: Range<Anchor>,
9735 cx: &mut Context<Self>,
9736 ) {
9737 let Some((_, buffer, _)) = self
9738 .buffer()
9739 .read(cx)
9740 .excerpt_containing(selection.start, cx)
9741 else {
9742 return;
9743 };
9744 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9745 else {
9746 return;
9747 };
9748 if buffer != end_buffer {
9749 log::error!("expected anchor range to have matching buffer IDs");
9750 return;
9751 }
9752
9753 let id = post_inc(&mut self.next_completion_id);
9754 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9755 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9756 CompletionsMenu::new_snippet_choices(
9757 id,
9758 true,
9759 choices,
9760 selection,
9761 buffer,
9762 snippet_sort_order,
9763 ),
9764 ));
9765 }
9766
9767 pub fn insert_snippet(
9768 &mut self,
9769 insertion_ranges: &[Range<usize>],
9770 snippet: Snippet,
9771 window: &mut Window,
9772 cx: &mut Context<Self>,
9773 ) -> Result<()> {
9774 struct Tabstop<T> {
9775 is_end_tabstop: bool,
9776 ranges: Vec<Range<T>>,
9777 choices: Option<Vec<String>>,
9778 }
9779
9780 let tabstops = self.buffer.update(cx, |buffer, cx| {
9781 let snippet_text: Arc<str> = snippet.text.clone().into();
9782 let edits = insertion_ranges
9783 .iter()
9784 .cloned()
9785 .map(|range| (range, snippet_text.clone()));
9786 let autoindent_mode = AutoindentMode::Block {
9787 original_indent_columns: Vec::new(),
9788 };
9789 buffer.edit(edits, Some(autoindent_mode), cx);
9790
9791 let snapshot = &*buffer.read(cx);
9792 let snippet = &snippet;
9793 snippet
9794 .tabstops
9795 .iter()
9796 .map(|tabstop| {
9797 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9798 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9799 });
9800 let mut tabstop_ranges = tabstop
9801 .ranges
9802 .iter()
9803 .flat_map(|tabstop_range| {
9804 let mut delta = 0_isize;
9805 insertion_ranges.iter().map(move |insertion_range| {
9806 let insertion_start = insertion_range.start as isize + delta;
9807 delta +=
9808 snippet.text.len() as isize - insertion_range.len() as isize;
9809
9810 let start = ((insertion_start + tabstop_range.start) as usize)
9811 .min(snapshot.len());
9812 let end = ((insertion_start + tabstop_range.end) as usize)
9813 .min(snapshot.len());
9814 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9815 })
9816 })
9817 .collect::<Vec<_>>();
9818 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9819
9820 Tabstop {
9821 is_end_tabstop,
9822 ranges: tabstop_ranges,
9823 choices: tabstop.choices.clone(),
9824 }
9825 })
9826 .collect::<Vec<_>>()
9827 });
9828 if let Some(tabstop) = tabstops.first() {
9829 self.change_selections(Default::default(), window, cx, |s| {
9830 // Reverse order so that the first range is the newest created selection.
9831 // Completions will use it and autoscroll will prioritize it.
9832 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9833 });
9834
9835 if let Some(choices) = &tabstop.choices
9836 && let Some(selection) = tabstop.ranges.first()
9837 {
9838 self.show_snippet_choices(choices, selection.clone(), cx)
9839 }
9840
9841 // If we're already at the last tabstop and it's at the end of the snippet,
9842 // we're done, we don't need to keep the state around.
9843 if !tabstop.is_end_tabstop {
9844 let choices = tabstops
9845 .iter()
9846 .map(|tabstop| tabstop.choices.clone())
9847 .collect();
9848
9849 let ranges = tabstops
9850 .into_iter()
9851 .map(|tabstop| tabstop.ranges)
9852 .collect::<Vec<_>>();
9853
9854 self.snippet_stack.push(SnippetState {
9855 active_index: 0,
9856 ranges,
9857 choices,
9858 });
9859 }
9860
9861 // Check whether the just-entered snippet ends with an auto-closable bracket.
9862 if self.autoclose_regions.is_empty() {
9863 let snapshot = self.buffer.read(cx).snapshot(cx);
9864 let mut all_selections = self.selections.all::<Point>(cx);
9865 for selection in &mut all_selections {
9866 let selection_head = selection.head();
9867 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9868 continue;
9869 };
9870
9871 let mut bracket_pair = None;
9872 let max_lookup_length = scope
9873 .brackets()
9874 .map(|(pair, _)| {
9875 pair.start
9876 .as_str()
9877 .chars()
9878 .count()
9879 .max(pair.end.as_str().chars().count())
9880 })
9881 .max();
9882 if let Some(max_lookup_length) = max_lookup_length {
9883 let next_text = snapshot
9884 .chars_at(selection_head)
9885 .take(max_lookup_length)
9886 .collect::<String>();
9887 let prev_text = snapshot
9888 .reversed_chars_at(selection_head)
9889 .take(max_lookup_length)
9890 .collect::<String>();
9891
9892 for (pair, enabled) in scope.brackets() {
9893 if enabled
9894 && pair.close
9895 && prev_text.starts_with(pair.start.as_str())
9896 && next_text.starts_with(pair.end.as_str())
9897 {
9898 bracket_pair = Some(pair.clone());
9899 break;
9900 }
9901 }
9902 }
9903
9904 if let Some(pair) = bracket_pair {
9905 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9906 let autoclose_enabled =
9907 self.use_autoclose && snapshot_settings.use_autoclose;
9908 if autoclose_enabled {
9909 let start = snapshot.anchor_after(selection_head);
9910 let end = snapshot.anchor_after(selection_head);
9911 self.autoclose_regions.push(AutocloseRegion {
9912 selection_id: selection.id,
9913 range: start..end,
9914 pair,
9915 });
9916 }
9917 }
9918 }
9919 }
9920 }
9921 Ok(())
9922 }
9923
9924 pub fn move_to_next_snippet_tabstop(
9925 &mut self,
9926 window: &mut Window,
9927 cx: &mut Context<Self>,
9928 ) -> bool {
9929 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9930 }
9931
9932 pub fn move_to_prev_snippet_tabstop(
9933 &mut self,
9934 window: &mut Window,
9935 cx: &mut Context<Self>,
9936 ) -> bool {
9937 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9938 }
9939
9940 pub fn move_to_snippet_tabstop(
9941 &mut self,
9942 bias: Bias,
9943 window: &mut Window,
9944 cx: &mut Context<Self>,
9945 ) -> bool {
9946 if let Some(mut snippet) = self.snippet_stack.pop() {
9947 match bias {
9948 Bias::Left => {
9949 if snippet.active_index > 0 {
9950 snippet.active_index -= 1;
9951 } else {
9952 self.snippet_stack.push(snippet);
9953 return false;
9954 }
9955 }
9956 Bias::Right => {
9957 if snippet.active_index + 1 < snippet.ranges.len() {
9958 snippet.active_index += 1;
9959 } else {
9960 self.snippet_stack.push(snippet);
9961 return false;
9962 }
9963 }
9964 }
9965 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9966 self.change_selections(Default::default(), window, cx, |s| {
9967 // Reverse order so that the first range is the newest created selection.
9968 // Completions will use it and autoscroll will prioritize it.
9969 s.select_ranges(current_ranges.iter().rev().cloned())
9970 });
9971
9972 if let Some(choices) = &snippet.choices[snippet.active_index]
9973 && let Some(selection) = current_ranges.first()
9974 {
9975 self.show_snippet_choices(choices, selection.clone(), cx);
9976 }
9977
9978 // If snippet state is not at the last tabstop, push it back on the stack
9979 if snippet.active_index + 1 < snippet.ranges.len() {
9980 self.snippet_stack.push(snippet);
9981 }
9982 return true;
9983 }
9984 }
9985
9986 false
9987 }
9988
9989 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9990 self.transact(window, cx, |this, window, cx| {
9991 this.select_all(&SelectAll, window, cx);
9992 this.insert("", window, cx);
9993 });
9994 }
9995
9996 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9997 if self.read_only(cx) {
9998 return;
9999 }
10000 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10001 self.transact(window, cx, |this, window, cx| {
10002 this.select_autoclose_pair(window, cx);
10003 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10004 if !this.linked_edit_ranges.is_empty() {
10005 let selections = this.selections.all::<MultiBufferPoint>(cx);
10006 let snapshot = this.buffer.read(cx).snapshot(cx);
10007
10008 for selection in selections.iter() {
10009 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10010 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10011 if selection_start.buffer_id != selection_end.buffer_id {
10012 continue;
10013 }
10014 if let Some(ranges) =
10015 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10016 {
10017 for (buffer, entries) in ranges {
10018 linked_ranges.entry(buffer).or_default().extend(entries);
10019 }
10020 }
10021 }
10022 }
10023
10024 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
10025 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10026 for selection in &mut selections {
10027 if selection.is_empty() {
10028 let old_head = selection.head();
10029 let mut new_head =
10030 movement::left(&display_map, old_head.to_display_point(&display_map))
10031 .to_point(&display_map);
10032 if let Some((buffer, line_buffer_range)) = display_map
10033 .buffer_snapshot()
10034 .buffer_line_for_row(MultiBufferRow(old_head.row))
10035 {
10036 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10037 let indent_len = match indent_size.kind {
10038 IndentKind::Space => {
10039 buffer.settings_at(line_buffer_range.start, cx).tab_size
10040 }
10041 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10042 };
10043 if old_head.column <= indent_size.len && old_head.column > 0 {
10044 let indent_len = indent_len.get();
10045 new_head = cmp::min(
10046 new_head,
10047 MultiBufferPoint::new(
10048 old_head.row,
10049 ((old_head.column - 1) / indent_len) * indent_len,
10050 ),
10051 );
10052 }
10053 }
10054
10055 selection.set_head(new_head, SelectionGoal::None);
10056 }
10057 }
10058
10059 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10060 this.insert("", window, cx);
10061 let empty_str: Arc<str> = Arc::from("");
10062 for (buffer, edits) in linked_ranges {
10063 let snapshot = buffer.read(cx).snapshot();
10064 use text::ToPoint as TP;
10065
10066 let edits = edits
10067 .into_iter()
10068 .map(|range| {
10069 let end_point = TP::to_point(&range.end, &snapshot);
10070 let mut start_point = TP::to_point(&range.start, &snapshot);
10071
10072 if end_point == start_point {
10073 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10074 .saturating_sub(1);
10075 start_point =
10076 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10077 };
10078
10079 (start_point..end_point, empty_str.clone())
10080 })
10081 .sorted_by_key(|(range, _)| range.start)
10082 .collect::<Vec<_>>();
10083 buffer.update(cx, |this, cx| {
10084 this.edit(edits, None, cx);
10085 })
10086 }
10087 this.refresh_edit_prediction(true, false, window, cx);
10088 refresh_linked_ranges(this, window, cx);
10089 });
10090 }
10091
10092 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10093 if self.read_only(cx) {
10094 return;
10095 }
10096 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10097 self.transact(window, cx, |this, window, cx| {
10098 this.change_selections(Default::default(), window, cx, |s| {
10099 s.move_with(|map, selection| {
10100 if selection.is_empty() {
10101 let cursor = movement::right(map, selection.head());
10102 selection.end = cursor;
10103 selection.reversed = true;
10104 selection.goal = SelectionGoal::None;
10105 }
10106 })
10107 });
10108 this.insert("", window, cx);
10109 this.refresh_edit_prediction(true, false, window, cx);
10110 });
10111 }
10112
10113 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10114 if self.mode.is_single_line() {
10115 cx.propagate();
10116 return;
10117 }
10118
10119 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10120 if self.move_to_prev_snippet_tabstop(window, cx) {
10121 return;
10122 }
10123 self.outdent(&Outdent, window, cx);
10124 }
10125
10126 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10127 if self.mode.is_single_line() {
10128 cx.propagate();
10129 return;
10130 }
10131
10132 if self.move_to_next_snippet_tabstop(window, cx) {
10133 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10134 return;
10135 }
10136 if self.read_only(cx) {
10137 return;
10138 }
10139 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10140 let mut selections = self.selections.all_adjusted(cx);
10141 let buffer = self.buffer.read(cx);
10142 let snapshot = buffer.snapshot(cx);
10143 let rows_iter = selections.iter().map(|s| s.head().row);
10144 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10145
10146 let has_some_cursor_in_whitespace = selections
10147 .iter()
10148 .filter(|selection| selection.is_empty())
10149 .any(|selection| {
10150 let cursor = selection.head();
10151 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10152 cursor.column < current_indent.len
10153 });
10154
10155 let mut edits = Vec::new();
10156 let mut prev_edited_row = 0;
10157 let mut row_delta = 0;
10158 for selection in &mut selections {
10159 if selection.start.row != prev_edited_row {
10160 row_delta = 0;
10161 }
10162 prev_edited_row = selection.end.row;
10163
10164 // If the selection is non-empty, then increase the indentation of the selected lines.
10165 if !selection.is_empty() {
10166 row_delta =
10167 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10168 continue;
10169 }
10170
10171 let cursor = selection.head();
10172 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10173 if let Some(suggested_indent) =
10174 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10175 {
10176 // Don't do anything if already at suggested indent
10177 // and there is any other cursor which is not
10178 if has_some_cursor_in_whitespace
10179 && cursor.column == current_indent.len
10180 && current_indent.len == suggested_indent.len
10181 {
10182 continue;
10183 }
10184
10185 // Adjust line and move cursor to suggested indent
10186 // if cursor is not at suggested indent
10187 if cursor.column < suggested_indent.len
10188 && cursor.column <= current_indent.len
10189 && current_indent.len <= suggested_indent.len
10190 {
10191 selection.start = Point::new(cursor.row, suggested_indent.len);
10192 selection.end = selection.start;
10193 if row_delta == 0 {
10194 edits.extend(Buffer::edit_for_indent_size_adjustment(
10195 cursor.row,
10196 current_indent,
10197 suggested_indent,
10198 ));
10199 row_delta = suggested_indent.len - current_indent.len;
10200 }
10201 continue;
10202 }
10203
10204 // If current indent is more than suggested indent
10205 // only move cursor to current indent and skip indent
10206 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10207 selection.start = Point::new(cursor.row, current_indent.len);
10208 selection.end = selection.start;
10209 continue;
10210 }
10211 }
10212
10213 // Otherwise, insert a hard or soft tab.
10214 let settings = buffer.language_settings_at(cursor, cx);
10215 let tab_size = if settings.hard_tabs {
10216 IndentSize::tab()
10217 } else {
10218 let tab_size = settings.tab_size.get();
10219 let indent_remainder = snapshot
10220 .text_for_range(Point::new(cursor.row, 0)..cursor)
10221 .flat_map(str::chars)
10222 .fold(row_delta % tab_size, |counter: u32, c| {
10223 if c == '\t' {
10224 0
10225 } else {
10226 (counter + 1) % tab_size
10227 }
10228 });
10229
10230 let chars_to_next_tab_stop = tab_size - indent_remainder;
10231 IndentSize::spaces(chars_to_next_tab_stop)
10232 };
10233 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10234 selection.end = selection.start;
10235 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10236 row_delta += tab_size.len;
10237 }
10238
10239 self.transact(window, cx, |this, window, cx| {
10240 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10241 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10242 this.refresh_edit_prediction(true, false, window, cx);
10243 });
10244 }
10245
10246 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10247 if self.read_only(cx) {
10248 return;
10249 }
10250 if self.mode.is_single_line() {
10251 cx.propagate();
10252 return;
10253 }
10254
10255 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10256 let mut selections = self.selections.all::<Point>(cx);
10257 let mut prev_edited_row = 0;
10258 let mut row_delta = 0;
10259 let mut edits = Vec::new();
10260 let buffer = self.buffer.read(cx);
10261 let snapshot = buffer.snapshot(cx);
10262 for selection in &mut selections {
10263 if selection.start.row != prev_edited_row {
10264 row_delta = 0;
10265 }
10266 prev_edited_row = selection.end.row;
10267
10268 row_delta =
10269 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10270 }
10271
10272 self.transact(window, cx, |this, window, cx| {
10273 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10274 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10275 });
10276 }
10277
10278 fn indent_selection(
10279 buffer: &MultiBuffer,
10280 snapshot: &MultiBufferSnapshot,
10281 selection: &mut Selection<Point>,
10282 edits: &mut Vec<(Range<Point>, String)>,
10283 delta_for_start_row: u32,
10284 cx: &App,
10285 ) -> u32 {
10286 let settings = buffer.language_settings_at(selection.start, cx);
10287 let tab_size = settings.tab_size.get();
10288 let indent_kind = if settings.hard_tabs {
10289 IndentKind::Tab
10290 } else {
10291 IndentKind::Space
10292 };
10293 let mut start_row = selection.start.row;
10294 let mut end_row = selection.end.row + 1;
10295
10296 // If a selection ends at the beginning of a line, don't indent
10297 // that last line.
10298 if selection.end.column == 0 && selection.end.row > selection.start.row {
10299 end_row -= 1;
10300 }
10301
10302 // Avoid re-indenting a row that has already been indented by a
10303 // previous selection, but still update this selection's column
10304 // to reflect that indentation.
10305 if delta_for_start_row > 0 {
10306 start_row += 1;
10307 selection.start.column += delta_for_start_row;
10308 if selection.end.row == selection.start.row {
10309 selection.end.column += delta_for_start_row;
10310 }
10311 }
10312
10313 let mut delta_for_end_row = 0;
10314 let has_multiple_rows = start_row + 1 != end_row;
10315 for row in start_row..end_row {
10316 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10317 let indent_delta = match (current_indent.kind, indent_kind) {
10318 (IndentKind::Space, IndentKind::Space) => {
10319 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10320 IndentSize::spaces(columns_to_next_tab_stop)
10321 }
10322 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10323 (_, IndentKind::Tab) => IndentSize::tab(),
10324 };
10325
10326 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10327 0
10328 } else {
10329 selection.start.column
10330 };
10331 let row_start = Point::new(row, start);
10332 edits.push((
10333 row_start..row_start,
10334 indent_delta.chars().collect::<String>(),
10335 ));
10336
10337 // Update this selection's endpoints to reflect the indentation.
10338 if row == selection.start.row {
10339 selection.start.column += indent_delta.len;
10340 }
10341 if row == selection.end.row {
10342 selection.end.column += indent_delta.len;
10343 delta_for_end_row = indent_delta.len;
10344 }
10345 }
10346
10347 if selection.start.row == selection.end.row {
10348 delta_for_start_row + delta_for_end_row
10349 } else {
10350 delta_for_end_row
10351 }
10352 }
10353
10354 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10355 if self.read_only(cx) {
10356 return;
10357 }
10358 if self.mode.is_single_line() {
10359 cx.propagate();
10360 return;
10361 }
10362
10363 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10364 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10365 let selections = self.selections.all::<Point>(cx);
10366 let mut deletion_ranges = Vec::new();
10367 let mut last_outdent = None;
10368 {
10369 let buffer = self.buffer.read(cx);
10370 let snapshot = buffer.snapshot(cx);
10371 for selection in &selections {
10372 let settings = buffer.language_settings_at(selection.start, cx);
10373 let tab_size = settings.tab_size.get();
10374 let mut rows = selection.spanned_rows(false, &display_map);
10375
10376 // Avoid re-outdenting a row that has already been outdented by a
10377 // previous selection.
10378 if let Some(last_row) = last_outdent
10379 && last_row == rows.start
10380 {
10381 rows.start = rows.start.next_row();
10382 }
10383 let has_multiple_rows = rows.len() > 1;
10384 for row in rows.iter_rows() {
10385 let indent_size = snapshot.indent_size_for_line(row);
10386 if indent_size.len > 0 {
10387 let deletion_len = match indent_size.kind {
10388 IndentKind::Space => {
10389 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10390 if columns_to_prev_tab_stop == 0 {
10391 tab_size
10392 } else {
10393 columns_to_prev_tab_stop
10394 }
10395 }
10396 IndentKind::Tab => 1,
10397 };
10398 let start = if has_multiple_rows
10399 || deletion_len > selection.start.column
10400 || indent_size.len < selection.start.column
10401 {
10402 0
10403 } else {
10404 selection.start.column - deletion_len
10405 };
10406 deletion_ranges.push(
10407 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10408 );
10409 last_outdent = Some(row);
10410 }
10411 }
10412 }
10413 }
10414
10415 self.transact(window, cx, |this, window, cx| {
10416 this.buffer.update(cx, |buffer, cx| {
10417 let empty_str: Arc<str> = Arc::default();
10418 buffer.edit(
10419 deletion_ranges
10420 .into_iter()
10421 .map(|range| (range, empty_str.clone())),
10422 None,
10423 cx,
10424 );
10425 });
10426 let selections = this.selections.all::<usize>(cx);
10427 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10428 });
10429 }
10430
10431 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10432 if self.read_only(cx) {
10433 return;
10434 }
10435 if self.mode.is_single_line() {
10436 cx.propagate();
10437 return;
10438 }
10439
10440 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10441 let selections = self
10442 .selections
10443 .all::<usize>(cx)
10444 .into_iter()
10445 .map(|s| s.range());
10446
10447 self.transact(window, cx, |this, window, cx| {
10448 this.buffer.update(cx, |buffer, cx| {
10449 buffer.autoindent_ranges(selections, cx);
10450 });
10451 let selections = this.selections.all::<usize>(cx);
10452 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10453 });
10454 }
10455
10456 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10457 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10458 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10459 let selections = self.selections.all::<Point>(cx);
10460
10461 let mut new_cursors = Vec::new();
10462 let mut edit_ranges = Vec::new();
10463 let mut selections = selections.iter().peekable();
10464 while let Some(selection) = selections.next() {
10465 let mut rows = selection.spanned_rows(false, &display_map);
10466
10467 // Accumulate contiguous regions of rows that we want to delete.
10468 while let Some(next_selection) = selections.peek() {
10469 let next_rows = next_selection.spanned_rows(false, &display_map);
10470 if next_rows.start <= rows.end {
10471 rows.end = next_rows.end;
10472 selections.next().unwrap();
10473 } else {
10474 break;
10475 }
10476 }
10477
10478 let buffer = display_map.buffer_snapshot();
10479 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10480 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10481 // If there's a line after the range, delete the \n from the end of the row range
10482 (
10483 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10484 rows.end,
10485 )
10486 } else {
10487 // If there isn't a line after the range, delete the \n from the line before the
10488 // start of the row range
10489 edit_start = edit_start.saturating_sub(1);
10490 (buffer.len(), rows.start.previous_row())
10491 };
10492
10493 let text_layout_details = self.text_layout_details(window);
10494 let x = display_map.x_for_display_point(
10495 selection.head().to_display_point(&display_map),
10496 &text_layout_details,
10497 );
10498 let row = Point::new(target_row.0, 0)
10499 .to_display_point(&display_map)
10500 .row();
10501 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10502
10503 new_cursors.push((
10504 selection.id,
10505 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10506 SelectionGoal::None,
10507 ));
10508 edit_ranges.push(edit_start..edit_end);
10509 }
10510
10511 self.transact(window, cx, |this, window, cx| {
10512 let buffer = this.buffer.update(cx, |buffer, cx| {
10513 let empty_str: Arc<str> = Arc::default();
10514 buffer.edit(
10515 edit_ranges
10516 .into_iter()
10517 .map(|range| (range, empty_str.clone())),
10518 None,
10519 cx,
10520 );
10521 buffer.snapshot(cx)
10522 });
10523 let new_selections = new_cursors
10524 .into_iter()
10525 .map(|(id, cursor, goal)| {
10526 let cursor = cursor.to_point(&buffer);
10527 Selection {
10528 id,
10529 start: cursor,
10530 end: cursor,
10531 reversed: false,
10532 goal,
10533 }
10534 })
10535 .collect();
10536
10537 this.change_selections(Default::default(), window, cx, |s| {
10538 s.select(new_selections);
10539 });
10540 });
10541 }
10542
10543 pub fn join_lines_impl(
10544 &mut self,
10545 insert_whitespace: bool,
10546 window: &mut Window,
10547 cx: &mut Context<Self>,
10548 ) {
10549 if self.read_only(cx) {
10550 return;
10551 }
10552 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10553 for selection in self.selections.all::<Point>(cx) {
10554 let start = MultiBufferRow(selection.start.row);
10555 // Treat single line selections as if they include the next line. Otherwise this action
10556 // would do nothing for single line selections individual cursors.
10557 let end = if selection.start.row == selection.end.row {
10558 MultiBufferRow(selection.start.row + 1)
10559 } else {
10560 MultiBufferRow(selection.end.row)
10561 };
10562
10563 if let Some(last_row_range) = row_ranges.last_mut()
10564 && start <= last_row_range.end
10565 {
10566 last_row_range.end = end;
10567 continue;
10568 }
10569 row_ranges.push(start..end);
10570 }
10571
10572 let snapshot = self.buffer.read(cx).snapshot(cx);
10573 let mut cursor_positions = Vec::new();
10574 for row_range in &row_ranges {
10575 let anchor = snapshot.anchor_before(Point::new(
10576 row_range.end.previous_row().0,
10577 snapshot.line_len(row_range.end.previous_row()),
10578 ));
10579 cursor_positions.push(anchor..anchor);
10580 }
10581
10582 self.transact(window, cx, |this, window, cx| {
10583 for row_range in row_ranges.into_iter().rev() {
10584 for row in row_range.iter_rows().rev() {
10585 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10586 let next_line_row = row.next_row();
10587 let indent = snapshot.indent_size_for_line(next_line_row);
10588 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10589
10590 let replace =
10591 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10592 " "
10593 } else {
10594 ""
10595 };
10596
10597 this.buffer.update(cx, |buffer, cx| {
10598 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10599 });
10600 }
10601 }
10602
10603 this.change_selections(Default::default(), window, cx, |s| {
10604 s.select_anchor_ranges(cursor_positions)
10605 });
10606 });
10607 }
10608
10609 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10610 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10611 self.join_lines_impl(true, window, cx);
10612 }
10613
10614 pub fn sort_lines_case_sensitive(
10615 &mut self,
10616 _: &SortLinesCaseSensitive,
10617 window: &mut Window,
10618 cx: &mut Context<Self>,
10619 ) {
10620 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10621 }
10622
10623 pub fn sort_lines_by_length(
10624 &mut self,
10625 _: &SortLinesByLength,
10626 window: &mut Window,
10627 cx: &mut Context<Self>,
10628 ) {
10629 self.manipulate_immutable_lines(window, cx, |lines| {
10630 lines.sort_by_key(|&line| line.chars().count())
10631 })
10632 }
10633
10634 pub fn sort_lines_case_insensitive(
10635 &mut self,
10636 _: &SortLinesCaseInsensitive,
10637 window: &mut Window,
10638 cx: &mut Context<Self>,
10639 ) {
10640 self.manipulate_immutable_lines(window, cx, |lines| {
10641 lines.sort_by_key(|line| line.to_lowercase())
10642 })
10643 }
10644
10645 pub fn unique_lines_case_insensitive(
10646 &mut self,
10647 _: &UniqueLinesCaseInsensitive,
10648 window: &mut Window,
10649 cx: &mut Context<Self>,
10650 ) {
10651 self.manipulate_immutable_lines(window, cx, |lines| {
10652 let mut seen = HashSet::default();
10653 lines.retain(|line| seen.insert(line.to_lowercase()));
10654 })
10655 }
10656
10657 pub fn unique_lines_case_sensitive(
10658 &mut self,
10659 _: &UniqueLinesCaseSensitive,
10660 window: &mut Window,
10661 cx: &mut Context<Self>,
10662 ) {
10663 self.manipulate_immutable_lines(window, cx, |lines| {
10664 let mut seen = HashSet::default();
10665 lines.retain(|line| seen.insert(*line));
10666 })
10667 }
10668
10669 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10670 let snapshot = self.buffer.read(cx).snapshot(cx);
10671 for selection in self.selections.disjoint_anchors_arc().iter() {
10672 if snapshot
10673 .language_at(selection.start)
10674 .and_then(|lang| lang.config().wrap_characters.as_ref())
10675 .is_some()
10676 {
10677 return true;
10678 }
10679 }
10680 false
10681 }
10682
10683 fn wrap_selections_in_tag(
10684 &mut self,
10685 _: &WrapSelectionsInTag,
10686 window: &mut Window,
10687 cx: &mut Context<Self>,
10688 ) {
10689 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10690
10691 let snapshot = self.buffer.read(cx).snapshot(cx);
10692
10693 let mut edits = Vec::new();
10694 let mut boundaries = Vec::new();
10695
10696 for selection in self.selections.all::<Point>(cx).iter() {
10697 let Some(wrap_config) = snapshot
10698 .language_at(selection.start)
10699 .and_then(|lang| lang.config().wrap_characters.clone())
10700 else {
10701 continue;
10702 };
10703
10704 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10705 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10706
10707 let start_before = snapshot.anchor_before(selection.start);
10708 let end_after = snapshot.anchor_after(selection.end);
10709
10710 edits.push((start_before..start_before, open_tag));
10711 edits.push((end_after..end_after, close_tag));
10712
10713 boundaries.push((
10714 start_before,
10715 end_after,
10716 wrap_config.start_prefix.len(),
10717 wrap_config.end_suffix.len(),
10718 ));
10719 }
10720
10721 if edits.is_empty() {
10722 return;
10723 }
10724
10725 self.transact(window, cx, |this, window, cx| {
10726 let buffer = this.buffer.update(cx, |buffer, cx| {
10727 buffer.edit(edits, None, cx);
10728 buffer.snapshot(cx)
10729 });
10730
10731 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10732 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10733 boundaries.into_iter()
10734 {
10735 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10736 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10737 new_selections.push(open_offset..open_offset);
10738 new_selections.push(close_offset..close_offset);
10739 }
10740
10741 this.change_selections(Default::default(), window, cx, |s| {
10742 s.select_ranges(new_selections);
10743 });
10744
10745 this.request_autoscroll(Autoscroll::fit(), cx);
10746 });
10747 }
10748
10749 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10750 let Some(project) = self.project.clone() else {
10751 return;
10752 };
10753 self.reload(project, window, cx)
10754 .detach_and_notify_err(window, cx);
10755 }
10756
10757 pub fn restore_file(
10758 &mut self,
10759 _: &::git::RestoreFile,
10760 window: &mut Window,
10761 cx: &mut Context<Self>,
10762 ) {
10763 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10764 let mut buffer_ids = HashSet::default();
10765 let snapshot = self.buffer().read(cx).snapshot(cx);
10766 for selection in self.selections.all::<usize>(cx) {
10767 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10768 }
10769
10770 let buffer = self.buffer().read(cx);
10771 let ranges = buffer_ids
10772 .into_iter()
10773 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10774 .collect::<Vec<_>>();
10775
10776 self.restore_hunks_in_ranges(ranges, window, cx);
10777 }
10778
10779 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10780 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10781 let selections = self
10782 .selections
10783 .all(cx)
10784 .into_iter()
10785 .map(|s| s.range())
10786 .collect();
10787 self.restore_hunks_in_ranges(selections, window, cx);
10788 }
10789
10790 pub fn restore_hunks_in_ranges(
10791 &mut self,
10792 ranges: Vec<Range<Point>>,
10793 window: &mut Window,
10794 cx: &mut Context<Editor>,
10795 ) {
10796 let mut revert_changes = HashMap::default();
10797 let chunk_by = self
10798 .snapshot(window, cx)
10799 .hunks_for_ranges(ranges)
10800 .into_iter()
10801 .chunk_by(|hunk| hunk.buffer_id);
10802 for (buffer_id, hunks) in &chunk_by {
10803 let hunks = hunks.collect::<Vec<_>>();
10804 for hunk in &hunks {
10805 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10806 }
10807 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10808 }
10809 drop(chunk_by);
10810 if !revert_changes.is_empty() {
10811 self.transact(window, cx, |editor, window, cx| {
10812 editor.restore(revert_changes, window, cx);
10813 });
10814 }
10815 }
10816
10817 pub fn open_active_item_in_terminal(
10818 &mut self,
10819 _: &OpenInTerminal,
10820 window: &mut Window,
10821 cx: &mut Context<Self>,
10822 ) {
10823 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10824 let project_path = buffer.read(cx).project_path(cx)?;
10825 let project = self.project()?.read(cx);
10826 let entry = project.entry_for_path(&project_path, cx)?;
10827 let parent = match &entry.canonical_path {
10828 Some(canonical_path) => canonical_path.to_path_buf(),
10829 None => project.absolute_path(&project_path, cx)?,
10830 }
10831 .parent()?
10832 .to_path_buf();
10833 Some(parent)
10834 }) {
10835 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10836 }
10837 }
10838
10839 fn set_breakpoint_context_menu(
10840 &mut self,
10841 display_row: DisplayRow,
10842 position: Option<Anchor>,
10843 clicked_point: gpui::Point<Pixels>,
10844 window: &mut Window,
10845 cx: &mut Context<Self>,
10846 ) {
10847 let source = self
10848 .buffer
10849 .read(cx)
10850 .snapshot(cx)
10851 .anchor_before(Point::new(display_row.0, 0u32));
10852
10853 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10854
10855 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10856 self,
10857 source,
10858 clicked_point,
10859 context_menu,
10860 window,
10861 cx,
10862 );
10863 }
10864
10865 fn add_edit_breakpoint_block(
10866 &mut self,
10867 anchor: Anchor,
10868 breakpoint: &Breakpoint,
10869 edit_action: BreakpointPromptEditAction,
10870 window: &mut Window,
10871 cx: &mut Context<Self>,
10872 ) {
10873 let weak_editor = cx.weak_entity();
10874 let bp_prompt = cx.new(|cx| {
10875 BreakpointPromptEditor::new(
10876 weak_editor,
10877 anchor,
10878 breakpoint.clone(),
10879 edit_action,
10880 window,
10881 cx,
10882 )
10883 });
10884
10885 let height = bp_prompt.update(cx, |this, cx| {
10886 this.prompt
10887 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10888 });
10889 let cloned_prompt = bp_prompt.clone();
10890 let blocks = vec![BlockProperties {
10891 style: BlockStyle::Sticky,
10892 placement: BlockPlacement::Above(anchor),
10893 height: Some(height),
10894 render: Arc::new(move |cx| {
10895 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10896 cloned_prompt.clone().into_any_element()
10897 }),
10898 priority: 0,
10899 }];
10900
10901 let focus_handle = bp_prompt.focus_handle(cx);
10902 window.focus(&focus_handle);
10903
10904 let block_ids = self.insert_blocks(blocks, None, cx);
10905 bp_prompt.update(cx, |prompt, _| {
10906 prompt.add_block_ids(block_ids);
10907 });
10908 }
10909
10910 pub(crate) fn breakpoint_at_row(
10911 &self,
10912 row: u32,
10913 window: &mut Window,
10914 cx: &mut Context<Self>,
10915 ) -> Option<(Anchor, Breakpoint)> {
10916 let snapshot = self.snapshot(window, cx);
10917 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
10918
10919 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10920 }
10921
10922 pub(crate) fn breakpoint_at_anchor(
10923 &self,
10924 breakpoint_position: Anchor,
10925 snapshot: &EditorSnapshot,
10926 cx: &mut Context<Self>,
10927 ) -> Option<(Anchor, Breakpoint)> {
10928 let buffer = self
10929 .buffer
10930 .read(cx)
10931 .buffer_for_anchor(breakpoint_position, cx)?;
10932
10933 let enclosing_excerpt = breakpoint_position.excerpt_id;
10934 let buffer_snapshot = buffer.read(cx).snapshot();
10935
10936 let row = buffer_snapshot
10937 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10938 .row;
10939
10940 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
10941 let anchor_end = snapshot
10942 .buffer_snapshot()
10943 .anchor_after(Point::new(row, line_len));
10944
10945 self.breakpoint_store
10946 .as_ref()?
10947 .read_with(cx, |breakpoint_store, cx| {
10948 breakpoint_store
10949 .breakpoints(
10950 &buffer,
10951 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10952 &buffer_snapshot,
10953 cx,
10954 )
10955 .next()
10956 .and_then(|(bp, _)| {
10957 let breakpoint_row = buffer_snapshot
10958 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10959 .row;
10960
10961 if breakpoint_row == row {
10962 snapshot
10963 .buffer_snapshot()
10964 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10965 .map(|position| (position, bp.bp.clone()))
10966 } else {
10967 None
10968 }
10969 })
10970 })
10971 }
10972
10973 pub fn edit_log_breakpoint(
10974 &mut self,
10975 _: &EditLogBreakpoint,
10976 window: &mut Window,
10977 cx: &mut Context<Self>,
10978 ) {
10979 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10980 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10981 message: None,
10982 state: BreakpointState::Enabled,
10983 condition: None,
10984 hit_condition: None,
10985 });
10986
10987 self.add_edit_breakpoint_block(
10988 anchor,
10989 &breakpoint,
10990 BreakpointPromptEditAction::Log,
10991 window,
10992 cx,
10993 );
10994 }
10995 }
10996
10997 fn breakpoints_at_cursors(
10998 &self,
10999 window: &mut Window,
11000 cx: &mut Context<Self>,
11001 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11002 let snapshot = self.snapshot(window, cx);
11003 let cursors = self
11004 .selections
11005 .disjoint_anchors_arc()
11006 .iter()
11007 .map(|selection| {
11008 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11009
11010 let breakpoint_position = self
11011 .breakpoint_at_row(cursor_position.row, window, cx)
11012 .map(|bp| bp.0)
11013 .unwrap_or_else(|| {
11014 snapshot
11015 .display_snapshot
11016 .buffer_snapshot()
11017 .anchor_after(Point::new(cursor_position.row, 0))
11018 });
11019
11020 let breakpoint = self
11021 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11022 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11023
11024 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11025 })
11026 // 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.
11027 .collect::<HashMap<Anchor, _>>();
11028
11029 cursors.into_iter().collect()
11030 }
11031
11032 pub fn enable_breakpoint(
11033 &mut self,
11034 _: &crate::actions::EnableBreakpoint,
11035 window: &mut Window,
11036 cx: &mut Context<Self>,
11037 ) {
11038 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11039 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11040 continue;
11041 };
11042 self.edit_breakpoint_at_anchor(
11043 anchor,
11044 breakpoint,
11045 BreakpointEditAction::InvertState,
11046 cx,
11047 );
11048 }
11049 }
11050
11051 pub fn disable_breakpoint(
11052 &mut self,
11053 _: &crate::actions::DisableBreakpoint,
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_enabled()) 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 toggle_breakpoint(
11071 &mut self,
11072 _: &crate::actions::ToggleBreakpoint,
11073 window: &mut Window,
11074 cx: &mut Context<Self>,
11075 ) {
11076 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11077 if let Some(breakpoint) = breakpoint {
11078 self.edit_breakpoint_at_anchor(
11079 anchor,
11080 breakpoint,
11081 BreakpointEditAction::Toggle,
11082 cx,
11083 );
11084 } else {
11085 self.edit_breakpoint_at_anchor(
11086 anchor,
11087 Breakpoint::new_standard(),
11088 BreakpointEditAction::Toggle,
11089 cx,
11090 );
11091 }
11092 }
11093 }
11094
11095 pub fn edit_breakpoint_at_anchor(
11096 &mut self,
11097 breakpoint_position: Anchor,
11098 breakpoint: Breakpoint,
11099 edit_action: BreakpointEditAction,
11100 cx: &mut Context<Self>,
11101 ) {
11102 let Some(breakpoint_store) = &self.breakpoint_store else {
11103 return;
11104 };
11105
11106 let Some(buffer) = self
11107 .buffer
11108 .read(cx)
11109 .buffer_for_anchor(breakpoint_position, cx)
11110 else {
11111 return;
11112 };
11113
11114 breakpoint_store.update(cx, |breakpoint_store, cx| {
11115 breakpoint_store.toggle_breakpoint(
11116 buffer,
11117 BreakpointWithPosition {
11118 position: breakpoint_position.text_anchor,
11119 bp: breakpoint,
11120 },
11121 edit_action,
11122 cx,
11123 );
11124 });
11125
11126 cx.notify();
11127 }
11128
11129 #[cfg(any(test, feature = "test-support"))]
11130 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11131 self.breakpoint_store.clone()
11132 }
11133
11134 pub fn prepare_restore_change(
11135 &self,
11136 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11137 hunk: &MultiBufferDiffHunk,
11138 cx: &mut App,
11139 ) -> Option<()> {
11140 if hunk.is_created_file() {
11141 return None;
11142 }
11143 let buffer = self.buffer.read(cx);
11144 let diff = buffer.diff_for(hunk.buffer_id)?;
11145 let buffer = buffer.buffer(hunk.buffer_id)?;
11146 let buffer = buffer.read(cx);
11147 let original_text = diff
11148 .read(cx)
11149 .base_text()
11150 .as_rope()
11151 .slice(hunk.diff_base_byte_range.clone());
11152 let buffer_snapshot = buffer.snapshot();
11153 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11154 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11155 probe
11156 .0
11157 .start
11158 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11159 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11160 }) {
11161 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11162 Some(())
11163 } else {
11164 None
11165 }
11166 }
11167
11168 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11169 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11170 }
11171
11172 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11173 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11174 }
11175
11176 fn manipulate_lines<M>(
11177 &mut self,
11178 window: &mut Window,
11179 cx: &mut Context<Self>,
11180 mut manipulate: M,
11181 ) where
11182 M: FnMut(&str) -> LineManipulationResult,
11183 {
11184 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11185
11186 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11187 let buffer = self.buffer.read(cx).snapshot(cx);
11188
11189 let mut edits = Vec::new();
11190
11191 let selections = self.selections.all::<Point>(cx);
11192 let mut selections = selections.iter().peekable();
11193 let mut contiguous_row_selections = Vec::new();
11194 let mut new_selections = Vec::new();
11195 let mut added_lines = 0;
11196 let mut removed_lines = 0;
11197
11198 while let Some(selection) = selections.next() {
11199 let (start_row, end_row) = consume_contiguous_rows(
11200 &mut contiguous_row_selections,
11201 selection,
11202 &display_map,
11203 &mut selections,
11204 );
11205
11206 let start_point = Point::new(start_row.0, 0);
11207 let end_point = Point::new(
11208 end_row.previous_row().0,
11209 buffer.line_len(end_row.previous_row()),
11210 );
11211 let text = buffer
11212 .text_for_range(start_point..end_point)
11213 .collect::<String>();
11214
11215 let LineManipulationResult {
11216 new_text,
11217 line_count_before,
11218 line_count_after,
11219 } = manipulate(&text);
11220
11221 edits.push((start_point..end_point, new_text));
11222
11223 // Selections must change based on added and removed line count
11224 let start_row =
11225 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11226 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11227 new_selections.push(Selection {
11228 id: selection.id,
11229 start: start_row,
11230 end: end_row,
11231 goal: SelectionGoal::None,
11232 reversed: selection.reversed,
11233 });
11234
11235 if line_count_after > line_count_before {
11236 added_lines += line_count_after - line_count_before;
11237 } else if line_count_before > line_count_after {
11238 removed_lines += line_count_before - line_count_after;
11239 }
11240 }
11241
11242 self.transact(window, cx, |this, window, cx| {
11243 let buffer = this.buffer.update(cx, |buffer, cx| {
11244 buffer.edit(edits, None, cx);
11245 buffer.snapshot(cx)
11246 });
11247
11248 // Recalculate offsets on newly edited buffer
11249 let new_selections = new_selections
11250 .iter()
11251 .map(|s| {
11252 let start_point = Point::new(s.start.0, 0);
11253 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11254 Selection {
11255 id: s.id,
11256 start: buffer.point_to_offset(start_point),
11257 end: buffer.point_to_offset(end_point),
11258 goal: s.goal,
11259 reversed: s.reversed,
11260 }
11261 })
11262 .collect();
11263
11264 this.change_selections(Default::default(), window, cx, |s| {
11265 s.select(new_selections);
11266 });
11267
11268 this.request_autoscroll(Autoscroll::fit(), cx);
11269 });
11270 }
11271
11272 fn manipulate_immutable_lines<Fn>(
11273 &mut self,
11274 window: &mut Window,
11275 cx: &mut Context<Self>,
11276 mut callback: Fn,
11277 ) where
11278 Fn: FnMut(&mut Vec<&str>),
11279 {
11280 self.manipulate_lines(window, cx, |text| {
11281 let mut lines: Vec<&str> = text.split('\n').collect();
11282 let line_count_before = lines.len();
11283
11284 callback(&mut lines);
11285
11286 LineManipulationResult {
11287 new_text: lines.join("\n"),
11288 line_count_before,
11289 line_count_after: lines.len(),
11290 }
11291 });
11292 }
11293
11294 fn manipulate_mutable_lines<Fn>(
11295 &mut self,
11296 window: &mut Window,
11297 cx: &mut Context<Self>,
11298 mut callback: Fn,
11299 ) where
11300 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11301 {
11302 self.manipulate_lines(window, cx, |text| {
11303 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11304 let line_count_before = lines.len();
11305
11306 callback(&mut lines);
11307
11308 LineManipulationResult {
11309 new_text: lines.join("\n"),
11310 line_count_before,
11311 line_count_after: lines.len(),
11312 }
11313 });
11314 }
11315
11316 pub fn convert_indentation_to_spaces(
11317 &mut self,
11318 _: &ConvertIndentationToSpaces,
11319 window: &mut Window,
11320 cx: &mut Context<Self>,
11321 ) {
11322 let settings = self.buffer.read(cx).language_settings(cx);
11323 let tab_size = settings.tab_size.get() as usize;
11324
11325 self.manipulate_mutable_lines(window, cx, |lines| {
11326 // Allocates a reasonably sized scratch buffer once for the whole loop
11327 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11328 // Avoids recomputing spaces that could be inserted many times
11329 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11330 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11331 .collect();
11332
11333 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11334 let mut chars = line.as_ref().chars();
11335 let mut col = 0;
11336 let mut changed = false;
11337
11338 for ch in chars.by_ref() {
11339 match ch {
11340 ' ' => {
11341 reindented_line.push(' ');
11342 col += 1;
11343 }
11344 '\t' => {
11345 // \t are converted to spaces depending on the current column
11346 let spaces_len = tab_size - (col % tab_size);
11347 reindented_line.extend(&space_cache[spaces_len - 1]);
11348 col += spaces_len;
11349 changed = true;
11350 }
11351 _ => {
11352 // If we dont append before break, the character is consumed
11353 reindented_line.push(ch);
11354 break;
11355 }
11356 }
11357 }
11358
11359 if !changed {
11360 reindented_line.clear();
11361 continue;
11362 }
11363 // Append the rest of the line and replace old reference with new one
11364 reindented_line.extend(chars);
11365 *line = Cow::Owned(reindented_line.clone());
11366 reindented_line.clear();
11367 }
11368 });
11369 }
11370
11371 pub fn convert_indentation_to_tabs(
11372 &mut self,
11373 _: &ConvertIndentationToTabs,
11374 window: &mut Window,
11375 cx: &mut Context<Self>,
11376 ) {
11377 let settings = self.buffer.read(cx).language_settings(cx);
11378 let tab_size = settings.tab_size.get() as usize;
11379
11380 self.manipulate_mutable_lines(window, cx, |lines| {
11381 // Allocates a reasonably sized buffer once for the whole loop
11382 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11383 // Avoids recomputing spaces that could be inserted many times
11384 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11385 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11386 .collect();
11387
11388 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11389 let mut chars = line.chars();
11390 let mut spaces_count = 0;
11391 let mut first_non_indent_char = None;
11392 let mut changed = false;
11393
11394 for ch in chars.by_ref() {
11395 match ch {
11396 ' ' => {
11397 // Keep track of spaces. Append \t when we reach tab_size
11398 spaces_count += 1;
11399 changed = true;
11400 if spaces_count == tab_size {
11401 reindented_line.push('\t');
11402 spaces_count = 0;
11403 }
11404 }
11405 '\t' => {
11406 reindented_line.push('\t');
11407 spaces_count = 0;
11408 }
11409 _ => {
11410 // Dont append it yet, we might have remaining spaces
11411 first_non_indent_char = Some(ch);
11412 break;
11413 }
11414 }
11415 }
11416
11417 if !changed {
11418 reindented_line.clear();
11419 continue;
11420 }
11421 // Remaining spaces that didn't make a full tab stop
11422 if spaces_count > 0 {
11423 reindented_line.extend(&space_cache[spaces_count - 1]);
11424 }
11425 // If we consume an extra character that was not indentation, add it back
11426 if let Some(extra_char) = first_non_indent_char {
11427 reindented_line.push(extra_char);
11428 }
11429 // Append the rest of the line and replace old reference with new one
11430 reindented_line.extend(chars);
11431 *line = Cow::Owned(reindented_line.clone());
11432 reindented_line.clear();
11433 }
11434 });
11435 }
11436
11437 pub fn convert_to_upper_case(
11438 &mut self,
11439 _: &ConvertToUpperCase,
11440 window: &mut Window,
11441 cx: &mut Context<Self>,
11442 ) {
11443 self.manipulate_text(window, cx, |text| text.to_uppercase())
11444 }
11445
11446 pub fn convert_to_lower_case(
11447 &mut self,
11448 _: &ConvertToLowerCase,
11449 window: &mut Window,
11450 cx: &mut Context<Self>,
11451 ) {
11452 self.manipulate_text(window, cx, |text| text.to_lowercase())
11453 }
11454
11455 pub fn convert_to_title_case(
11456 &mut self,
11457 _: &ConvertToTitleCase,
11458 window: &mut Window,
11459 cx: &mut Context<Self>,
11460 ) {
11461 self.manipulate_text(window, cx, |text| {
11462 text.split('\n')
11463 .map(|line| line.to_case(Case::Title))
11464 .join("\n")
11465 })
11466 }
11467
11468 pub fn convert_to_snake_case(
11469 &mut self,
11470 _: &ConvertToSnakeCase,
11471 window: &mut Window,
11472 cx: &mut Context<Self>,
11473 ) {
11474 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11475 }
11476
11477 pub fn convert_to_kebab_case(
11478 &mut self,
11479 _: &ConvertToKebabCase,
11480 window: &mut Window,
11481 cx: &mut Context<Self>,
11482 ) {
11483 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11484 }
11485
11486 pub fn convert_to_upper_camel_case(
11487 &mut self,
11488 _: &ConvertToUpperCamelCase,
11489 window: &mut Window,
11490 cx: &mut Context<Self>,
11491 ) {
11492 self.manipulate_text(window, cx, |text| {
11493 text.split('\n')
11494 .map(|line| line.to_case(Case::UpperCamel))
11495 .join("\n")
11496 })
11497 }
11498
11499 pub fn convert_to_lower_camel_case(
11500 &mut self,
11501 _: &ConvertToLowerCamelCase,
11502 window: &mut Window,
11503 cx: &mut Context<Self>,
11504 ) {
11505 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11506 }
11507
11508 pub fn convert_to_opposite_case(
11509 &mut self,
11510 _: &ConvertToOppositeCase,
11511 window: &mut Window,
11512 cx: &mut Context<Self>,
11513 ) {
11514 self.manipulate_text(window, cx, |text| {
11515 text.chars()
11516 .fold(String::with_capacity(text.len()), |mut t, c| {
11517 if c.is_uppercase() {
11518 t.extend(c.to_lowercase());
11519 } else {
11520 t.extend(c.to_uppercase());
11521 }
11522 t
11523 })
11524 })
11525 }
11526
11527 pub fn convert_to_sentence_case(
11528 &mut self,
11529 _: &ConvertToSentenceCase,
11530 window: &mut Window,
11531 cx: &mut Context<Self>,
11532 ) {
11533 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11534 }
11535
11536 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11537 self.manipulate_text(window, cx, |text| {
11538 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11539 if has_upper_case_characters {
11540 text.to_lowercase()
11541 } else {
11542 text.to_uppercase()
11543 }
11544 })
11545 }
11546
11547 pub fn convert_to_rot13(
11548 &mut self,
11549 _: &ConvertToRot13,
11550 window: &mut Window,
11551 cx: &mut Context<Self>,
11552 ) {
11553 self.manipulate_text(window, cx, |text| {
11554 text.chars()
11555 .map(|c| match c {
11556 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11557 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11558 _ => c,
11559 })
11560 .collect()
11561 })
11562 }
11563
11564 pub fn convert_to_rot47(
11565 &mut self,
11566 _: &ConvertToRot47,
11567 window: &mut Window,
11568 cx: &mut Context<Self>,
11569 ) {
11570 self.manipulate_text(window, cx, |text| {
11571 text.chars()
11572 .map(|c| {
11573 let code_point = c as u32;
11574 if code_point >= 33 && code_point <= 126 {
11575 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11576 }
11577 c
11578 })
11579 .collect()
11580 })
11581 }
11582
11583 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11584 where
11585 Fn: FnMut(&str) -> String,
11586 {
11587 let buffer = self.buffer.read(cx).snapshot(cx);
11588
11589 let mut new_selections = Vec::new();
11590 let mut edits = Vec::new();
11591 let mut selection_adjustment = 0i32;
11592
11593 for selection in self.selections.all_adjusted(cx) {
11594 let selection_is_empty = selection.is_empty();
11595
11596 let (start, end) = if selection_is_empty {
11597 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11598 (word_range.start, word_range.end)
11599 } else {
11600 (
11601 buffer.point_to_offset(selection.start),
11602 buffer.point_to_offset(selection.end),
11603 )
11604 };
11605
11606 let text = buffer.text_for_range(start..end).collect::<String>();
11607 let old_length = text.len() as i32;
11608 let text = callback(&text);
11609
11610 new_selections.push(Selection {
11611 start: (start as i32 - selection_adjustment) as usize,
11612 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11613 goal: SelectionGoal::None,
11614 id: selection.id,
11615 reversed: selection.reversed,
11616 });
11617
11618 selection_adjustment += old_length - text.len() as i32;
11619
11620 edits.push((start..end, text));
11621 }
11622
11623 self.transact(window, cx, |this, window, cx| {
11624 this.buffer.update(cx, |buffer, cx| {
11625 buffer.edit(edits, None, cx);
11626 });
11627
11628 this.change_selections(Default::default(), window, cx, |s| {
11629 s.select(new_selections);
11630 });
11631
11632 this.request_autoscroll(Autoscroll::fit(), cx);
11633 });
11634 }
11635
11636 pub fn move_selection_on_drop(
11637 &mut self,
11638 selection: &Selection<Anchor>,
11639 target: DisplayPoint,
11640 is_cut: bool,
11641 window: &mut Window,
11642 cx: &mut Context<Self>,
11643 ) {
11644 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11645 let buffer = display_map.buffer_snapshot();
11646 let mut edits = Vec::new();
11647 let insert_point = display_map
11648 .clip_point(target, Bias::Left)
11649 .to_point(&display_map);
11650 let text = buffer
11651 .text_for_range(selection.start..selection.end)
11652 .collect::<String>();
11653 if is_cut {
11654 edits.push(((selection.start..selection.end), String::new()));
11655 }
11656 let insert_anchor = buffer.anchor_before(insert_point);
11657 edits.push(((insert_anchor..insert_anchor), text));
11658 let last_edit_start = insert_anchor.bias_left(buffer);
11659 let last_edit_end = insert_anchor.bias_right(buffer);
11660 self.transact(window, cx, |this, window, cx| {
11661 this.buffer.update(cx, |buffer, cx| {
11662 buffer.edit(edits, None, cx);
11663 });
11664 this.change_selections(Default::default(), window, cx, |s| {
11665 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11666 });
11667 });
11668 }
11669
11670 pub fn clear_selection_drag_state(&mut self) {
11671 self.selection_drag_state = SelectionDragState::None;
11672 }
11673
11674 pub fn duplicate(
11675 &mut self,
11676 upwards: bool,
11677 whole_lines: bool,
11678 window: &mut Window,
11679 cx: &mut Context<Self>,
11680 ) {
11681 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11682
11683 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11684 let buffer = display_map.buffer_snapshot();
11685 let selections = self.selections.all::<Point>(cx);
11686
11687 let mut edits = Vec::new();
11688 let mut selections_iter = selections.iter().peekable();
11689 while let Some(selection) = selections_iter.next() {
11690 let mut rows = selection.spanned_rows(false, &display_map);
11691 // duplicate line-wise
11692 if whole_lines || selection.start == selection.end {
11693 // Avoid duplicating the same lines twice.
11694 while let Some(next_selection) = selections_iter.peek() {
11695 let next_rows = next_selection.spanned_rows(false, &display_map);
11696 if next_rows.start < rows.end {
11697 rows.end = next_rows.end;
11698 selections_iter.next().unwrap();
11699 } else {
11700 break;
11701 }
11702 }
11703
11704 // Copy the text from the selected row region and splice it either at the start
11705 // or end of the region.
11706 let start = Point::new(rows.start.0, 0);
11707 let end = Point::new(
11708 rows.end.previous_row().0,
11709 buffer.line_len(rows.end.previous_row()),
11710 );
11711
11712 let mut text = buffer.text_for_range(start..end).collect::<String>();
11713
11714 let insert_location = if upwards {
11715 // When duplicating upward, we need to insert before the current line.
11716 // If we're on the last line and it doesn't end with a newline,
11717 // we need to add a newline before the duplicated content.
11718 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11719 && buffer.max_point().column > 0
11720 && !text.ends_with('\n');
11721
11722 if needs_leading_newline {
11723 text.insert(0, '\n');
11724 end
11725 } else {
11726 text.push('\n');
11727 Point::new(rows.end.0, 0)
11728 }
11729 } else {
11730 text.push('\n');
11731 start
11732 };
11733 edits.push((insert_location..insert_location, text));
11734 } else {
11735 // duplicate character-wise
11736 let start = selection.start;
11737 let end = selection.end;
11738 let text = buffer.text_for_range(start..end).collect::<String>();
11739 edits.push((selection.end..selection.end, text));
11740 }
11741 }
11742
11743 self.transact(window, cx, |this, _, cx| {
11744 this.buffer.update(cx, |buffer, cx| {
11745 buffer.edit(edits, None, cx);
11746 });
11747
11748 this.request_autoscroll(Autoscroll::fit(), cx);
11749 });
11750 }
11751
11752 pub fn duplicate_line_up(
11753 &mut self,
11754 _: &DuplicateLineUp,
11755 window: &mut Window,
11756 cx: &mut Context<Self>,
11757 ) {
11758 self.duplicate(true, true, window, cx);
11759 }
11760
11761 pub fn duplicate_line_down(
11762 &mut self,
11763 _: &DuplicateLineDown,
11764 window: &mut Window,
11765 cx: &mut Context<Self>,
11766 ) {
11767 self.duplicate(false, true, window, cx);
11768 }
11769
11770 pub fn duplicate_selection(
11771 &mut self,
11772 _: &DuplicateSelection,
11773 window: &mut Window,
11774 cx: &mut Context<Self>,
11775 ) {
11776 self.duplicate(false, false, window, cx);
11777 }
11778
11779 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11780 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11781 if self.mode.is_single_line() {
11782 cx.propagate();
11783 return;
11784 }
11785
11786 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11787 let buffer = self.buffer.read(cx).snapshot(cx);
11788
11789 let mut edits = Vec::new();
11790 let mut unfold_ranges = Vec::new();
11791 let mut refold_creases = Vec::new();
11792
11793 let selections = self.selections.all::<Point>(cx);
11794 let mut selections = selections.iter().peekable();
11795 let mut contiguous_row_selections = Vec::new();
11796 let mut new_selections = Vec::new();
11797
11798 while let Some(selection) = selections.next() {
11799 // Find all the selections that span a contiguous row range
11800 let (start_row, end_row) = consume_contiguous_rows(
11801 &mut contiguous_row_selections,
11802 selection,
11803 &display_map,
11804 &mut selections,
11805 );
11806
11807 // Move the text spanned by the row range to be before the line preceding the row range
11808 if start_row.0 > 0 {
11809 let range_to_move = Point::new(
11810 start_row.previous_row().0,
11811 buffer.line_len(start_row.previous_row()),
11812 )
11813 ..Point::new(
11814 end_row.previous_row().0,
11815 buffer.line_len(end_row.previous_row()),
11816 );
11817 let insertion_point = display_map
11818 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11819 .0;
11820
11821 // Don't move lines across excerpts
11822 if buffer
11823 .excerpt_containing(insertion_point..range_to_move.end)
11824 .is_some()
11825 {
11826 let text = buffer
11827 .text_for_range(range_to_move.clone())
11828 .flat_map(|s| s.chars())
11829 .skip(1)
11830 .chain(['\n'])
11831 .collect::<String>();
11832
11833 edits.push((
11834 buffer.anchor_after(range_to_move.start)
11835 ..buffer.anchor_before(range_to_move.end),
11836 String::new(),
11837 ));
11838 let insertion_anchor = buffer.anchor_after(insertion_point);
11839 edits.push((insertion_anchor..insertion_anchor, text));
11840
11841 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11842
11843 // Move selections up
11844 new_selections.extend(contiguous_row_selections.drain(..).map(
11845 |mut selection| {
11846 selection.start.row -= row_delta;
11847 selection.end.row -= row_delta;
11848 selection
11849 },
11850 ));
11851
11852 // Move folds up
11853 unfold_ranges.push(range_to_move.clone());
11854 for fold in display_map.folds_in_range(
11855 buffer.anchor_before(range_to_move.start)
11856 ..buffer.anchor_after(range_to_move.end),
11857 ) {
11858 let mut start = fold.range.start.to_point(&buffer);
11859 let mut end = fold.range.end.to_point(&buffer);
11860 start.row -= row_delta;
11861 end.row -= row_delta;
11862 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11863 }
11864 }
11865 }
11866
11867 // If we didn't move line(s), preserve the existing selections
11868 new_selections.append(&mut contiguous_row_selections);
11869 }
11870
11871 self.transact(window, cx, |this, window, cx| {
11872 this.unfold_ranges(&unfold_ranges, true, true, cx);
11873 this.buffer.update(cx, |buffer, cx| {
11874 for (range, text) in edits {
11875 buffer.edit([(range, text)], None, cx);
11876 }
11877 });
11878 this.fold_creases(refold_creases, true, window, cx);
11879 this.change_selections(Default::default(), window, cx, |s| {
11880 s.select(new_selections);
11881 })
11882 });
11883 }
11884
11885 pub fn move_line_down(
11886 &mut self,
11887 _: &MoveLineDown,
11888 window: &mut Window,
11889 cx: &mut Context<Self>,
11890 ) {
11891 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11892 if self.mode.is_single_line() {
11893 cx.propagate();
11894 return;
11895 }
11896
11897 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11898 let buffer = self.buffer.read(cx).snapshot(cx);
11899
11900 let mut edits = Vec::new();
11901 let mut unfold_ranges = Vec::new();
11902 let mut refold_creases = Vec::new();
11903
11904 let selections = self.selections.all::<Point>(cx);
11905 let mut selections = selections.iter().peekable();
11906 let mut contiguous_row_selections = Vec::new();
11907 let mut new_selections = Vec::new();
11908
11909 while let Some(selection) = selections.next() {
11910 // Find all the selections that span a contiguous row range
11911 let (start_row, end_row) = consume_contiguous_rows(
11912 &mut contiguous_row_selections,
11913 selection,
11914 &display_map,
11915 &mut selections,
11916 );
11917
11918 // Move the text spanned by the row range to be after the last line of the row range
11919 if end_row.0 <= buffer.max_point().row {
11920 let range_to_move =
11921 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11922 let insertion_point = display_map
11923 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11924 .0;
11925
11926 // Don't move lines across excerpt boundaries
11927 if buffer
11928 .excerpt_containing(range_to_move.start..insertion_point)
11929 .is_some()
11930 {
11931 let mut text = String::from("\n");
11932 text.extend(buffer.text_for_range(range_to_move.clone()));
11933 text.pop(); // Drop trailing newline
11934 edits.push((
11935 buffer.anchor_after(range_to_move.start)
11936 ..buffer.anchor_before(range_to_move.end),
11937 String::new(),
11938 ));
11939 let insertion_anchor = buffer.anchor_after(insertion_point);
11940 edits.push((insertion_anchor..insertion_anchor, text));
11941
11942 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11943
11944 // Move selections down
11945 new_selections.extend(contiguous_row_selections.drain(..).map(
11946 |mut selection| {
11947 selection.start.row += row_delta;
11948 selection.end.row += row_delta;
11949 selection
11950 },
11951 ));
11952
11953 // Move folds down
11954 unfold_ranges.push(range_to_move.clone());
11955 for fold in display_map.folds_in_range(
11956 buffer.anchor_before(range_to_move.start)
11957 ..buffer.anchor_after(range_to_move.end),
11958 ) {
11959 let mut start = fold.range.start.to_point(&buffer);
11960 let mut end = fold.range.end.to_point(&buffer);
11961 start.row += row_delta;
11962 end.row += row_delta;
11963 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11964 }
11965 }
11966 }
11967
11968 // If we didn't move line(s), preserve the existing selections
11969 new_selections.append(&mut contiguous_row_selections);
11970 }
11971
11972 self.transact(window, cx, |this, window, cx| {
11973 this.unfold_ranges(&unfold_ranges, true, true, cx);
11974 this.buffer.update(cx, |buffer, cx| {
11975 for (range, text) in edits {
11976 buffer.edit([(range, text)], None, cx);
11977 }
11978 });
11979 this.fold_creases(refold_creases, true, window, cx);
11980 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11981 });
11982 }
11983
11984 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11985 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11986 let text_layout_details = &self.text_layout_details(window);
11987 self.transact(window, cx, |this, window, cx| {
11988 let edits = this.change_selections(Default::default(), window, cx, |s| {
11989 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11990 s.move_with(|display_map, selection| {
11991 if !selection.is_empty() {
11992 return;
11993 }
11994
11995 let mut head = selection.head();
11996 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11997 if head.column() == display_map.line_len(head.row()) {
11998 transpose_offset = display_map
11999 .buffer_snapshot()
12000 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12001 }
12002
12003 if transpose_offset == 0 {
12004 return;
12005 }
12006
12007 *head.column_mut() += 1;
12008 head = display_map.clip_point(head, Bias::Right);
12009 let goal = SelectionGoal::HorizontalPosition(
12010 display_map
12011 .x_for_display_point(head, text_layout_details)
12012 .into(),
12013 );
12014 selection.collapse_to(head, goal);
12015
12016 let transpose_start = display_map
12017 .buffer_snapshot()
12018 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12019 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12020 let transpose_end = display_map
12021 .buffer_snapshot()
12022 .clip_offset(transpose_offset + 1, Bias::Right);
12023 if let Some(ch) = display_map
12024 .buffer_snapshot()
12025 .chars_at(transpose_start)
12026 .next()
12027 {
12028 edits.push((transpose_start..transpose_offset, String::new()));
12029 edits.push((transpose_end..transpose_end, ch.to_string()));
12030 }
12031 }
12032 });
12033 edits
12034 });
12035 this.buffer
12036 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12037 let selections = this.selections.all::<usize>(cx);
12038 this.change_selections(Default::default(), window, cx, |s| {
12039 s.select(selections);
12040 });
12041 });
12042 }
12043
12044 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12045 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12046 if self.mode.is_single_line() {
12047 cx.propagate();
12048 return;
12049 }
12050
12051 self.rewrap_impl(RewrapOptions::default(), cx)
12052 }
12053
12054 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12055 let buffer = self.buffer.read(cx).snapshot(cx);
12056 let selections = self.selections.all::<Point>(cx);
12057
12058 #[derive(Clone, Debug, PartialEq)]
12059 enum CommentFormat {
12060 /// single line comment, with prefix for line
12061 Line(String),
12062 /// single line within a block comment, with prefix for line
12063 BlockLine(String),
12064 /// a single line of a block comment that includes the initial delimiter
12065 BlockCommentWithStart(BlockCommentConfig),
12066 /// a single line of a block comment that includes the ending delimiter
12067 BlockCommentWithEnd(BlockCommentConfig),
12068 }
12069
12070 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12071 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12072 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12073 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12074 .peekable();
12075
12076 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12077 row
12078 } else {
12079 return Vec::new();
12080 };
12081
12082 let language_settings = buffer.language_settings_at(selection.head(), cx);
12083 let language_scope = buffer.language_scope_at(selection.head());
12084
12085 let indent_and_prefix_for_row =
12086 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12087 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12088 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12089 &language_scope
12090 {
12091 let indent_end = Point::new(row, indent.len);
12092 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12093 let line_text_after_indent = buffer
12094 .text_for_range(indent_end..line_end)
12095 .collect::<String>();
12096
12097 let is_within_comment_override = buffer
12098 .language_scope_at(indent_end)
12099 .is_some_and(|scope| scope.override_name() == Some("comment"));
12100 let comment_delimiters = if is_within_comment_override {
12101 // we are within a comment syntax node, but we don't
12102 // yet know what kind of comment: block, doc or line
12103 match (
12104 language_scope.documentation_comment(),
12105 language_scope.block_comment(),
12106 ) {
12107 (Some(config), _) | (_, Some(config))
12108 if buffer.contains_str_at(indent_end, &config.start) =>
12109 {
12110 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12111 }
12112 (Some(config), _) | (_, Some(config))
12113 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12114 {
12115 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12116 }
12117 (Some(config), _) | (_, Some(config))
12118 if buffer.contains_str_at(indent_end, &config.prefix) =>
12119 {
12120 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12121 }
12122 (_, _) => language_scope
12123 .line_comment_prefixes()
12124 .iter()
12125 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12126 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12127 }
12128 } else {
12129 // we not in an overridden comment node, but we may
12130 // be within a non-overridden line comment node
12131 language_scope
12132 .line_comment_prefixes()
12133 .iter()
12134 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12135 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12136 };
12137
12138 let rewrap_prefix = language_scope
12139 .rewrap_prefixes()
12140 .iter()
12141 .find_map(|prefix_regex| {
12142 prefix_regex.find(&line_text_after_indent).map(|mat| {
12143 if mat.start() == 0 {
12144 Some(mat.as_str().to_string())
12145 } else {
12146 None
12147 }
12148 })
12149 })
12150 .flatten();
12151 (comment_delimiters, rewrap_prefix)
12152 } else {
12153 (None, None)
12154 };
12155 (indent, comment_prefix, rewrap_prefix)
12156 };
12157
12158 let mut ranges = Vec::new();
12159 let from_empty_selection = selection.is_empty();
12160
12161 let mut current_range_start = first_row;
12162 let mut prev_row = first_row;
12163 let (
12164 mut current_range_indent,
12165 mut current_range_comment_delimiters,
12166 mut current_range_rewrap_prefix,
12167 ) = indent_and_prefix_for_row(first_row);
12168
12169 for row in non_blank_rows_iter.skip(1) {
12170 let has_paragraph_break = row > prev_row + 1;
12171
12172 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12173 indent_and_prefix_for_row(row);
12174
12175 let has_indent_change = row_indent != current_range_indent;
12176 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12177
12178 let has_boundary_change = has_comment_change
12179 || row_rewrap_prefix.is_some()
12180 || (has_indent_change && current_range_comment_delimiters.is_some());
12181
12182 if has_paragraph_break || has_boundary_change {
12183 ranges.push((
12184 language_settings.clone(),
12185 Point::new(current_range_start, 0)
12186 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12187 current_range_indent,
12188 current_range_comment_delimiters.clone(),
12189 current_range_rewrap_prefix.clone(),
12190 from_empty_selection,
12191 ));
12192 current_range_start = row;
12193 current_range_indent = row_indent;
12194 current_range_comment_delimiters = row_comment_delimiters;
12195 current_range_rewrap_prefix = row_rewrap_prefix;
12196 }
12197 prev_row = row;
12198 }
12199
12200 ranges.push((
12201 language_settings.clone(),
12202 Point::new(current_range_start, 0)
12203 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12204 current_range_indent,
12205 current_range_comment_delimiters,
12206 current_range_rewrap_prefix,
12207 from_empty_selection,
12208 ));
12209
12210 ranges
12211 });
12212
12213 let mut edits = Vec::new();
12214 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12215
12216 for (
12217 language_settings,
12218 wrap_range,
12219 mut indent_size,
12220 comment_prefix,
12221 rewrap_prefix,
12222 from_empty_selection,
12223 ) in wrap_ranges
12224 {
12225 let mut start_row = wrap_range.start.row;
12226 let mut end_row = wrap_range.end.row;
12227
12228 // Skip selections that overlap with a range that has already been rewrapped.
12229 let selection_range = start_row..end_row;
12230 if rewrapped_row_ranges
12231 .iter()
12232 .any(|range| range.overlaps(&selection_range))
12233 {
12234 continue;
12235 }
12236
12237 let tab_size = language_settings.tab_size;
12238
12239 let (line_prefix, inside_comment) = match &comment_prefix {
12240 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12241 (Some(prefix.as_str()), true)
12242 }
12243 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12244 (Some(prefix.as_ref()), true)
12245 }
12246 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12247 start: _,
12248 end: _,
12249 prefix,
12250 tab_size,
12251 })) => {
12252 indent_size.len += tab_size;
12253 (Some(prefix.as_ref()), true)
12254 }
12255 None => (None, false),
12256 };
12257 let indent_prefix = indent_size.chars().collect::<String>();
12258 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12259
12260 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12261 RewrapBehavior::InComments => inside_comment,
12262 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12263 RewrapBehavior::Anywhere => true,
12264 };
12265
12266 let should_rewrap = options.override_language_settings
12267 || allow_rewrap_based_on_language
12268 || self.hard_wrap.is_some();
12269 if !should_rewrap {
12270 continue;
12271 }
12272
12273 if from_empty_selection {
12274 'expand_upwards: while start_row > 0 {
12275 let prev_row = start_row - 1;
12276 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12277 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12278 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12279 {
12280 start_row = prev_row;
12281 } else {
12282 break 'expand_upwards;
12283 }
12284 }
12285
12286 'expand_downwards: while end_row < buffer.max_point().row {
12287 let next_row = end_row + 1;
12288 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12289 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12290 && !buffer.is_line_blank(MultiBufferRow(next_row))
12291 {
12292 end_row = next_row;
12293 } else {
12294 break 'expand_downwards;
12295 }
12296 }
12297 }
12298
12299 let start = Point::new(start_row, 0);
12300 let start_offset = ToOffset::to_offset(&start, &buffer);
12301 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12302 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12303 let mut first_line_delimiter = None;
12304 let mut last_line_delimiter = None;
12305 let Some(lines_without_prefixes) = selection_text
12306 .lines()
12307 .enumerate()
12308 .map(|(ix, line)| {
12309 let line_trimmed = line.trim_start();
12310 if rewrap_prefix.is_some() && ix > 0 {
12311 Ok(line_trimmed)
12312 } else if let Some(
12313 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12314 start,
12315 prefix,
12316 end,
12317 tab_size,
12318 })
12319 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12320 start,
12321 prefix,
12322 end,
12323 tab_size,
12324 }),
12325 ) = &comment_prefix
12326 {
12327 let line_trimmed = line_trimmed
12328 .strip_prefix(start.as_ref())
12329 .map(|s| {
12330 let mut indent_size = indent_size;
12331 indent_size.len -= tab_size;
12332 let indent_prefix: String = indent_size.chars().collect();
12333 first_line_delimiter = Some((indent_prefix, start));
12334 s.trim_start()
12335 })
12336 .unwrap_or(line_trimmed);
12337 let line_trimmed = line_trimmed
12338 .strip_suffix(end.as_ref())
12339 .map(|s| {
12340 last_line_delimiter = Some(end);
12341 s.trim_end()
12342 })
12343 .unwrap_or(line_trimmed);
12344 let line_trimmed = line_trimmed
12345 .strip_prefix(prefix.as_ref())
12346 .unwrap_or(line_trimmed);
12347 Ok(line_trimmed)
12348 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12349 line_trimmed.strip_prefix(prefix).with_context(|| {
12350 format!("line did not start with prefix {prefix:?}: {line:?}")
12351 })
12352 } else {
12353 line_trimmed
12354 .strip_prefix(&line_prefix.trim_start())
12355 .with_context(|| {
12356 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12357 })
12358 }
12359 })
12360 .collect::<Result<Vec<_>, _>>()
12361 .log_err()
12362 else {
12363 continue;
12364 };
12365
12366 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12367 buffer
12368 .language_settings_at(Point::new(start_row, 0), cx)
12369 .preferred_line_length as usize
12370 });
12371
12372 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12373 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12374 } else {
12375 line_prefix.clone()
12376 };
12377
12378 let wrapped_text = {
12379 let mut wrapped_text = wrap_with_prefix(
12380 line_prefix,
12381 subsequent_lines_prefix,
12382 lines_without_prefixes.join("\n"),
12383 wrap_column,
12384 tab_size,
12385 options.preserve_existing_whitespace,
12386 );
12387
12388 if let Some((indent, delimiter)) = first_line_delimiter {
12389 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12390 }
12391 if let Some(last_line) = last_line_delimiter {
12392 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12393 }
12394
12395 wrapped_text
12396 };
12397
12398 // TODO: should always use char-based diff while still supporting cursor behavior that
12399 // matches vim.
12400 let mut diff_options = DiffOptions::default();
12401 if options.override_language_settings {
12402 diff_options.max_word_diff_len = 0;
12403 diff_options.max_word_diff_line_count = 0;
12404 } else {
12405 diff_options.max_word_diff_len = usize::MAX;
12406 diff_options.max_word_diff_line_count = usize::MAX;
12407 }
12408
12409 for (old_range, new_text) in
12410 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12411 {
12412 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12413 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12414 edits.push((edit_start..edit_end, new_text));
12415 }
12416
12417 rewrapped_row_ranges.push(start_row..=end_row);
12418 }
12419
12420 self.buffer
12421 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12422 }
12423
12424 pub fn cut_common(
12425 &mut self,
12426 cut_no_selection_line: bool,
12427 window: &mut Window,
12428 cx: &mut Context<Self>,
12429 ) -> ClipboardItem {
12430 let mut text = String::new();
12431 let buffer = self.buffer.read(cx).snapshot(cx);
12432 let mut selections = self.selections.all::<Point>(cx);
12433 let mut clipboard_selections = Vec::with_capacity(selections.len());
12434 {
12435 let max_point = buffer.max_point();
12436 let mut is_first = true;
12437 for selection in &mut selections {
12438 let is_entire_line =
12439 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12440 if is_entire_line {
12441 selection.start = Point::new(selection.start.row, 0);
12442 if !selection.is_empty() && selection.end.column == 0 {
12443 selection.end = cmp::min(max_point, selection.end);
12444 } else {
12445 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12446 }
12447 selection.goal = SelectionGoal::None;
12448 }
12449 if is_first {
12450 is_first = false;
12451 } else {
12452 text += "\n";
12453 }
12454 let mut len = 0;
12455 for chunk in buffer.text_for_range(selection.start..selection.end) {
12456 text.push_str(chunk);
12457 len += chunk.len();
12458 }
12459 clipboard_selections.push(ClipboardSelection {
12460 len,
12461 is_entire_line,
12462 first_line_indent: buffer
12463 .indent_size_for_line(MultiBufferRow(selection.start.row))
12464 .len,
12465 });
12466 }
12467 }
12468
12469 self.transact(window, cx, |this, window, cx| {
12470 this.change_selections(Default::default(), window, cx, |s| {
12471 s.select(selections);
12472 });
12473 this.insert("", window, cx);
12474 });
12475 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12476 }
12477
12478 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12479 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12480 let item = self.cut_common(true, window, cx);
12481 cx.write_to_clipboard(item);
12482 }
12483
12484 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12485 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12486 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12487 s.move_with(|snapshot, sel| {
12488 if sel.is_empty() {
12489 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12490 }
12491 if sel.is_empty() {
12492 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12493 }
12494 });
12495 });
12496 let item = self.cut_common(false, window, cx);
12497 cx.set_global(KillRing(item))
12498 }
12499
12500 pub fn kill_ring_yank(
12501 &mut self,
12502 _: &KillRingYank,
12503 window: &mut Window,
12504 cx: &mut Context<Self>,
12505 ) {
12506 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12507 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12508 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12509 (kill_ring.text().to_string(), kill_ring.metadata_json())
12510 } else {
12511 return;
12512 }
12513 } else {
12514 return;
12515 };
12516 self.do_paste(&text, metadata, false, window, cx);
12517 }
12518
12519 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12520 self.do_copy(true, cx);
12521 }
12522
12523 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12524 self.do_copy(false, cx);
12525 }
12526
12527 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12528 let selections = self.selections.all::<Point>(cx);
12529 let buffer = self.buffer.read(cx).read(cx);
12530 let mut text = String::new();
12531
12532 let mut clipboard_selections = Vec::with_capacity(selections.len());
12533 {
12534 let max_point = buffer.max_point();
12535 let mut is_first = true;
12536 for selection in &selections {
12537 let mut start = selection.start;
12538 let mut end = selection.end;
12539 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12540 let mut add_trailing_newline = false;
12541 if is_entire_line {
12542 start = Point::new(start.row, 0);
12543 let next_line_start = Point::new(end.row + 1, 0);
12544 if next_line_start <= max_point {
12545 end = next_line_start;
12546 } else {
12547 // We're on the last line without a trailing newline.
12548 // Copy to the end of the line and add a newline afterwards.
12549 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12550 add_trailing_newline = true;
12551 }
12552 }
12553
12554 let mut trimmed_selections = Vec::new();
12555 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12556 let row = MultiBufferRow(start.row);
12557 let first_indent = buffer.indent_size_for_line(row);
12558 if first_indent.len == 0 || start.column > first_indent.len {
12559 trimmed_selections.push(start..end);
12560 } else {
12561 trimmed_selections.push(
12562 Point::new(row.0, first_indent.len)
12563 ..Point::new(row.0, buffer.line_len(row)),
12564 );
12565 for row in start.row + 1..=end.row {
12566 let mut line_len = buffer.line_len(MultiBufferRow(row));
12567 if row == end.row {
12568 line_len = end.column;
12569 }
12570 if line_len == 0 {
12571 trimmed_selections
12572 .push(Point::new(row, 0)..Point::new(row, line_len));
12573 continue;
12574 }
12575 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12576 if row_indent_size.len >= first_indent.len {
12577 trimmed_selections.push(
12578 Point::new(row, first_indent.len)..Point::new(row, line_len),
12579 );
12580 } else {
12581 trimmed_selections.clear();
12582 trimmed_selections.push(start..end);
12583 break;
12584 }
12585 }
12586 }
12587 } else {
12588 trimmed_selections.push(start..end);
12589 }
12590
12591 for trimmed_range in trimmed_selections {
12592 if is_first {
12593 is_first = false;
12594 } else {
12595 text += "\n";
12596 }
12597 let mut len = 0;
12598 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12599 text.push_str(chunk);
12600 len += chunk.len();
12601 }
12602 if add_trailing_newline {
12603 text.push('\n');
12604 len += 1;
12605 }
12606 clipboard_selections.push(ClipboardSelection {
12607 len,
12608 is_entire_line,
12609 first_line_indent: buffer
12610 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12611 .len,
12612 });
12613 }
12614 }
12615 }
12616
12617 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12618 text,
12619 clipboard_selections,
12620 ));
12621 }
12622
12623 pub fn do_paste(
12624 &mut self,
12625 text: &String,
12626 clipboard_selections: Option<Vec<ClipboardSelection>>,
12627 handle_entire_lines: bool,
12628 window: &mut Window,
12629 cx: &mut Context<Self>,
12630 ) {
12631 if self.read_only(cx) {
12632 return;
12633 }
12634
12635 let clipboard_text = Cow::Borrowed(text.as_str());
12636
12637 self.transact(window, cx, |this, window, cx| {
12638 let had_active_edit_prediction = this.has_active_edit_prediction();
12639 let old_selections = this.selections.all::<usize>(cx);
12640 let cursor_offset = this.selections.last::<usize>(cx).head();
12641
12642 if let Some(mut clipboard_selections) = clipboard_selections {
12643 let all_selections_were_entire_line =
12644 clipboard_selections.iter().all(|s| s.is_entire_line);
12645 let first_selection_indent_column =
12646 clipboard_selections.first().map(|s| s.first_line_indent);
12647 if clipboard_selections.len() != old_selections.len() {
12648 clipboard_selections.drain(..);
12649 }
12650 let mut auto_indent_on_paste = true;
12651
12652 this.buffer.update(cx, |buffer, cx| {
12653 let snapshot = buffer.read(cx);
12654 auto_indent_on_paste = snapshot
12655 .language_settings_at(cursor_offset, cx)
12656 .auto_indent_on_paste;
12657
12658 let mut start_offset = 0;
12659 let mut edits = Vec::new();
12660 let mut original_indent_columns = Vec::new();
12661 for (ix, selection) in old_selections.iter().enumerate() {
12662 let to_insert;
12663 let entire_line;
12664 let original_indent_column;
12665 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12666 let end_offset = start_offset + clipboard_selection.len;
12667 to_insert = &clipboard_text[start_offset..end_offset];
12668 entire_line = clipboard_selection.is_entire_line;
12669 start_offset = end_offset + 1;
12670 original_indent_column = Some(clipboard_selection.first_line_indent);
12671 } else {
12672 to_insert = &*clipboard_text;
12673 entire_line = all_selections_were_entire_line;
12674 original_indent_column = first_selection_indent_column
12675 }
12676
12677 let (range, to_insert) =
12678 if selection.is_empty() && handle_entire_lines && entire_line {
12679 // If the corresponding selection was empty when this slice of the
12680 // clipboard text was written, then the entire line containing the
12681 // selection was copied. If this selection is also currently empty,
12682 // then paste the line before the current line of the buffer.
12683 let column = selection.start.to_point(&snapshot).column as usize;
12684 let line_start = selection.start - column;
12685 (line_start..line_start, Cow::Borrowed(to_insert))
12686 } else {
12687 let language = snapshot.language_at(selection.head());
12688 let range = selection.range();
12689 if let Some(language) = language
12690 && language.name() == "Markdown".into()
12691 {
12692 edit_for_markdown_paste(
12693 &snapshot,
12694 range,
12695 to_insert,
12696 url::Url::parse(to_insert).ok(),
12697 )
12698 } else {
12699 (range, Cow::Borrowed(to_insert))
12700 }
12701 };
12702
12703 edits.push((range, to_insert));
12704 original_indent_columns.push(original_indent_column);
12705 }
12706 drop(snapshot);
12707
12708 buffer.edit(
12709 edits,
12710 if auto_indent_on_paste {
12711 Some(AutoindentMode::Block {
12712 original_indent_columns,
12713 })
12714 } else {
12715 None
12716 },
12717 cx,
12718 );
12719 });
12720
12721 let selections = this.selections.all::<usize>(cx);
12722 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12723 } else {
12724 let url = url::Url::parse(&clipboard_text).ok();
12725
12726 let auto_indent_mode = if !clipboard_text.is_empty() {
12727 Some(AutoindentMode::Block {
12728 original_indent_columns: Vec::new(),
12729 })
12730 } else {
12731 None
12732 };
12733
12734 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12735 let snapshot = buffer.snapshot(cx);
12736
12737 let anchors = old_selections
12738 .iter()
12739 .map(|s| {
12740 let anchor = snapshot.anchor_after(s.head());
12741 s.map(|_| anchor)
12742 })
12743 .collect::<Vec<_>>();
12744
12745 let mut edits = Vec::new();
12746
12747 for selection in old_selections.iter() {
12748 let language = snapshot.language_at(selection.head());
12749 let range = selection.range();
12750
12751 let (edit_range, edit_text) = if let Some(language) = language
12752 && language.name() == "Markdown".into()
12753 {
12754 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12755 } else {
12756 (range, clipboard_text.clone())
12757 };
12758
12759 edits.push((edit_range, edit_text));
12760 }
12761
12762 drop(snapshot);
12763 buffer.edit(edits, auto_indent_mode, cx);
12764
12765 anchors
12766 });
12767
12768 this.change_selections(Default::default(), window, cx, |s| {
12769 s.select_anchors(selection_anchors);
12770 });
12771 }
12772
12773 let trigger_in_words =
12774 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12775
12776 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12777 });
12778 }
12779
12780 pub fn diff_clipboard_with_selection(
12781 &mut self,
12782 _: &DiffClipboardWithSelection,
12783 window: &mut Window,
12784 cx: &mut Context<Self>,
12785 ) {
12786 let selections = self.selections.all::<usize>(cx);
12787
12788 if selections.is_empty() {
12789 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12790 return;
12791 };
12792
12793 let clipboard_text = match cx.read_from_clipboard() {
12794 Some(item) => match item.entries().first() {
12795 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12796 _ => None,
12797 },
12798 None => None,
12799 };
12800
12801 let Some(clipboard_text) = clipboard_text else {
12802 log::warn!("Clipboard doesn't contain text.");
12803 return;
12804 };
12805
12806 window.dispatch_action(
12807 Box::new(DiffClipboardWithSelectionData {
12808 clipboard_text,
12809 editor: cx.entity(),
12810 }),
12811 cx,
12812 );
12813 }
12814
12815 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12816 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12817 if let Some(item) = cx.read_from_clipboard() {
12818 let entries = item.entries();
12819
12820 match entries.first() {
12821 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12822 // of all the pasted entries.
12823 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12824 .do_paste(
12825 clipboard_string.text(),
12826 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12827 true,
12828 window,
12829 cx,
12830 ),
12831 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12832 }
12833 }
12834 }
12835
12836 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12837 if self.read_only(cx) {
12838 return;
12839 }
12840
12841 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12842
12843 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12844 if let Some((selections, _)) =
12845 self.selection_history.transaction(transaction_id).cloned()
12846 {
12847 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12848 s.select_anchors(selections.to_vec());
12849 });
12850 } else {
12851 log::error!(
12852 "No entry in selection_history found for undo. \
12853 This may correspond to a bug where undo does not update the selection. \
12854 If this is occurring, please add details to \
12855 https://github.com/zed-industries/zed/issues/22692"
12856 );
12857 }
12858 self.request_autoscroll(Autoscroll::fit(), cx);
12859 self.unmark_text(window, cx);
12860 self.refresh_edit_prediction(true, false, window, cx);
12861 cx.emit(EditorEvent::Edited { transaction_id });
12862 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12863 }
12864 }
12865
12866 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12867 if self.read_only(cx) {
12868 return;
12869 }
12870
12871 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12872
12873 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12874 if let Some((_, Some(selections))) =
12875 self.selection_history.transaction(transaction_id).cloned()
12876 {
12877 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12878 s.select_anchors(selections.to_vec());
12879 });
12880 } else {
12881 log::error!(
12882 "No entry in selection_history found for redo. \
12883 This may correspond to a bug where undo does not update the selection. \
12884 If this is occurring, please add details to \
12885 https://github.com/zed-industries/zed/issues/22692"
12886 );
12887 }
12888 self.request_autoscroll(Autoscroll::fit(), cx);
12889 self.unmark_text(window, cx);
12890 self.refresh_edit_prediction(true, false, window, cx);
12891 cx.emit(EditorEvent::Edited { transaction_id });
12892 }
12893 }
12894
12895 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12896 self.buffer
12897 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12898 }
12899
12900 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12901 self.buffer
12902 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12903 }
12904
12905 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12906 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12907 self.change_selections(Default::default(), window, cx, |s| {
12908 s.move_with(|map, selection| {
12909 let cursor = if selection.is_empty() {
12910 movement::left(map, selection.start)
12911 } else {
12912 selection.start
12913 };
12914 selection.collapse_to(cursor, SelectionGoal::None);
12915 });
12916 })
12917 }
12918
12919 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12920 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12921 self.change_selections(Default::default(), window, cx, |s| {
12922 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12923 })
12924 }
12925
12926 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12927 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12928 self.change_selections(Default::default(), window, cx, |s| {
12929 s.move_with(|map, selection| {
12930 let cursor = if selection.is_empty() {
12931 movement::right(map, selection.end)
12932 } else {
12933 selection.end
12934 };
12935 selection.collapse_to(cursor, SelectionGoal::None)
12936 });
12937 })
12938 }
12939
12940 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12941 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12942 self.change_selections(Default::default(), window, cx, |s| {
12943 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12944 });
12945 }
12946
12947 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12948 if self.take_rename(true, window, cx).is_some() {
12949 return;
12950 }
12951
12952 if self.mode.is_single_line() {
12953 cx.propagate();
12954 return;
12955 }
12956
12957 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12958
12959 let text_layout_details = &self.text_layout_details(window);
12960 let selection_count = self.selections.count();
12961 let first_selection = self.selections.first_anchor();
12962
12963 self.change_selections(Default::default(), window, cx, |s| {
12964 s.move_with(|map, selection| {
12965 if !selection.is_empty() {
12966 selection.goal = SelectionGoal::None;
12967 }
12968 let (cursor, goal) = movement::up(
12969 map,
12970 selection.start,
12971 selection.goal,
12972 false,
12973 text_layout_details,
12974 );
12975 selection.collapse_to(cursor, goal);
12976 });
12977 });
12978
12979 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12980 {
12981 cx.propagate();
12982 }
12983 }
12984
12985 pub fn move_up_by_lines(
12986 &mut self,
12987 action: &MoveUpByLines,
12988 window: &mut Window,
12989 cx: &mut Context<Self>,
12990 ) {
12991 if self.take_rename(true, window, cx).is_some() {
12992 return;
12993 }
12994
12995 if self.mode.is_single_line() {
12996 cx.propagate();
12997 return;
12998 }
12999
13000 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13001
13002 let text_layout_details = &self.text_layout_details(window);
13003
13004 self.change_selections(Default::default(), window, cx, |s| {
13005 s.move_with(|map, selection| {
13006 if !selection.is_empty() {
13007 selection.goal = SelectionGoal::None;
13008 }
13009 let (cursor, goal) = movement::up_by_rows(
13010 map,
13011 selection.start,
13012 action.lines,
13013 selection.goal,
13014 false,
13015 text_layout_details,
13016 );
13017 selection.collapse_to(cursor, goal);
13018 });
13019 })
13020 }
13021
13022 pub fn move_down_by_lines(
13023 &mut self,
13024 action: &MoveDownByLines,
13025 window: &mut Window,
13026 cx: &mut Context<Self>,
13027 ) {
13028 if self.take_rename(true, window, cx).is_some() {
13029 return;
13030 }
13031
13032 if self.mode.is_single_line() {
13033 cx.propagate();
13034 return;
13035 }
13036
13037 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13038
13039 let text_layout_details = &self.text_layout_details(window);
13040
13041 self.change_selections(Default::default(), window, cx, |s| {
13042 s.move_with(|map, selection| {
13043 if !selection.is_empty() {
13044 selection.goal = SelectionGoal::None;
13045 }
13046 let (cursor, goal) = movement::down_by_rows(
13047 map,
13048 selection.start,
13049 action.lines,
13050 selection.goal,
13051 false,
13052 text_layout_details,
13053 );
13054 selection.collapse_to(cursor, goal);
13055 });
13056 })
13057 }
13058
13059 pub fn select_down_by_lines(
13060 &mut self,
13061 action: &SelectDownByLines,
13062 window: &mut Window,
13063 cx: &mut Context<Self>,
13064 ) {
13065 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13066 let text_layout_details = &self.text_layout_details(window);
13067 self.change_selections(Default::default(), window, cx, |s| {
13068 s.move_heads_with(|map, head, goal| {
13069 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13070 })
13071 })
13072 }
13073
13074 pub fn select_up_by_lines(
13075 &mut self,
13076 action: &SelectUpByLines,
13077 window: &mut Window,
13078 cx: &mut Context<Self>,
13079 ) {
13080 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13081 let text_layout_details = &self.text_layout_details(window);
13082 self.change_selections(Default::default(), window, cx, |s| {
13083 s.move_heads_with(|map, head, goal| {
13084 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13085 })
13086 })
13087 }
13088
13089 pub fn select_page_up(
13090 &mut self,
13091 _: &SelectPageUp,
13092 window: &mut Window,
13093 cx: &mut Context<Self>,
13094 ) {
13095 let Some(row_count) = self.visible_row_count() else {
13096 return;
13097 };
13098
13099 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13100
13101 let text_layout_details = &self.text_layout_details(window);
13102
13103 self.change_selections(Default::default(), window, cx, |s| {
13104 s.move_heads_with(|map, head, goal| {
13105 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13106 })
13107 })
13108 }
13109
13110 pub fn move_page_up(
13111 &mut self,
13112 action: &MovePageUp,
13113 window: &mut Window,
13114 cx: &mut Context<Self>,
13115 ) {
13116 if self.take_rename(true, window, cx).is_some() {
13117 return;
13118 }
13119
13120 if self
13121 .context_menu
13122 .borrow_mut()
13123 .as_mut()
13124 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13125 .unwrap_or(false)
13126 {
13127 return;
13128 }
13129
13130 if matches!(self.mode, EditorMode::SingleLine) {
13131 cx.propagate();
13132 return;
13133 }
13134
13135 let Some(row_count) = self.visible_row_count() else {
13136 return;
13137 };
13138
13139 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13140
13141 let effects = if action.center_cursor {
13142 SelectionEffects::scroll(Autoscroll::center())
13143 } else {
13144 SelectionEffects::default()
13145 };
13146
13147 let text_layout_details = &self.text_layout_details(window);
13148
13149 self.change_selections(effects, window, cx, |s| {
13150 s.move_with(|map, selection| {
13151 if !selection.is_empty() {
13152 selection.goal = SelectionGoal::None;
13153 }
13154 let (cursor, goal) = movement::up_by_rows(
13155 map,
13156 selection.end,
13157 row_count,
13158 selection.goal,
13159 false,
13160 text_layout_details,
13161 );
13162 selection.collapse_to(cursor, goal);
13163 });
13164 });
13165 }
13166
13167 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13168 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13169 let text_layout_details = &self.text_layout_details(window);
13170 self.change_selections(Default::default(), window, cx, |s| {
13171 s.move_heads_with(|map, head, goal| {
13172 movement::up(map, head, goal, false, text_layout_details)
13173 })
13174 })
13175 }
13176
13177 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13178 self.take_rename(true, window, cx);
13179
13180 if self.mode.is_single_line() {
13181 cx.propagate();
13182 return;
13183 }
13184
13185 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13186
13187 let text_layout_details = &self.text_layout_details(window);
13188 let selection_count = self.selections.count();
13189 let first_selection = self.selections.first_anchor();
13190
13191 self.change_selections(Default::default(), window, cx, |s| {
13192 s.move_with(|map, selection| {
13193 if !selection.is_empty() {
13194 selection.goal = SelectionGoal::None;
13195 }
13196 let (cursor, goal) = movement::down(
13197 map,
13198 selection.end,
13199 selection.goal,
13200 false,
13201 text_layout_details,
13202 );
13203 selection.collapse_to(cursor, goal);
13204 });
13205 });
13206
13207 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13208 {
13209 cx.propagate();
13210 }
13211 }
13212
13213 pub fn select_page_down(
13214 &mut self,
13215 _: &SelectPageDown,
13216 window: &mut Window,
13217 cx: &mut Context<Self>,
13218 ) {
13219 let Some(row_count) = self.visible_row_count() else {
13220 return;
13221 };
13222
13223 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13224
13225 let text_layout_details = &self.text_layout_details(window);
13226
13227 self.change_selections(Default::default(), window, cx, |s| {
13228 s.move_heads_with(|map, head, goal| {
13229 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13230 })
13231 })
13232 }
13233
13234 pub fn move_page_down(
13235 &mut self,
13236 action: &MovePageDown,
13237 window: &mut Window,
13238 cx: &mut Context<Self>,
13239 ) {
13240 if self.take_rename(true, window, cx).is_some() {
13241 return;
13242 }
13243
13244 if self
13245 .context_menu
13246 .borrow_mut()
13247 .as_mut()
13248 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13249 .unwrap_or(false)
13250 {
13251 return;
13252 }
13253
13254 if matches!(self.mode, EditorMode::SingleLine) {
13255 cx.propagate();
13256 return;
13257 }
13258
13259 let Some(row_count) = self.visible_row_count() else {
13260 return;
13261 };
13262
13263 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13264
13265 let effects = if action.center_cursor {
13266 SelectionEffects::scroll(Autoscroll::center())
13267 } else {
13268 SelectionEffects::default()
13269 };
13270
13271 let text_layout_details = &self.text_layout_details(window);
13272 self.change_selections(effects, window, cx, |s| {
13273 s.move_with(|map, selection| {
13274 if !selection.is_empty() {
13275 selection.goal = SelectionGoal::None;
13276 }
13277 let (cursor, goal) = movement::down_by_rows(
13278 map,
13279 selection.end,
13280 row_count,
13281 selection.goal,
13282 false,
13283 text_layout_details,
13284 );
13285 selection.collapse_to(cursor, goal);
13286 });
13287 });
13288 }
13289
13290 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13291 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13292 let text_layout_details = &self.text_layout_details(window);
13293 self.change_selections(Default::default(), window, cx, |s| {
13294 s.move_heads_with(|map, head, goal| {
13295 movement::down(map, head, goal, false, text_layout_details)
13296 })
13297 });
13298 }
13299
13300 pub fn context_menu_first(
13301 &mut self,
13302 _: &ContextMenuFirst,
13303 window: &mut Window,
13304 cx: &mut Context<Self>,
13305 ) {
13306 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13307 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13308 }
13309 }
13310
13311 pub fn context_menu_prev(
13312 &mut self,
13313 _: &ContextMenuPrevious,
13314 window: &mut Window,
13315 cx: &mut Context<Self>,
13316 ) {
13317 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13318 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13319 }
13320 }
13321
13322 pub fn context_menu_next(
13323 &mut self,
13324 _: &ContextMenuNext,
13325 window: &mut Window,
13326 cx: &mut Context<Self>,
13327 ) {
13328 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13329 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13330 }
13331 }
13332
13333 pub fn context_menu_last(
13334 &mut self,
13335 _: &ContextMenuLast,
13336 window: &mut Window,
13337 cx: &mut Context<Self>,
13338 ) {
13339 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13340 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13341 }
13342 }
13343
13344 pub fn signature_help_prev(
13345 &mut self,
13346 _: &SignatureHelpPrevious,
13347 _: &mut Window,
13348 cx: &mut Context<Self>,
13349 ) {
13350 if let Some(popover) = self.signature_help_state.popover_mut() {
13351 if popover.current_signature == 0 {
13352 popover.current_signature = popover.signatures.len() - 1;
13353 } else {
13354 popover.current_signature -= 1;
13355 }
13356 cx.notify();
13357 }
13358 }
13359
13360 pub fn signature_help_next(
13361 &mut self,
13362 _: &SignatureHelpNext,
13363 _: &mut Window,
13364 cx: &mut Context<Self>,
13365 ) {
13366 if let Some(popover) = self.signature_help_state.popover_mut() {
13367 if popover.current_signature + 1 == popover.signatures.len() {
13368 popover.current_signature = 0;
13369 } else {
13370 popover.current_signature += 1;
13371 }
13372 cx.notify();
13373 }
13374 }
13375
13376 pub fn move_to_previous_word_start(
13377 &mut self,
13378 _: &MoveToPreviousWordStart,
13379 window: &mut Window,
13380 cx: &mut Context<Self>,
13381 ) {
13382 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13383 self.change_selections(Default::default(), window, cx, |s| {
13384 s.move_cursors_with(|map, head, _| {
13385 (
13386 movement::previous_word_start(map, head),
13387 SelectionGoal::None,
13388 )
13389 });
13390 })
13391 }
13392
13393 pub fn move_to_previous_subword_start(
13394 &mut self,
13395 _: &MoveToPreviousSubwordStart,
13396 window: &mut Window,
13397 cx: &mut Context<Self>,
13398 ) {
13399 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13400 self.change_selections(Default::default(), window, cx, |s| {
13401 s.move_cursors_with(|map, head, _| {
13402 (
13403 movement::previous_subword_start(map, head),
13404 SelectionGoal::None,
13405 )
13406 });
13407 })
13408 }
13409
13410 pub fn select_to_previous_word_start(
13411 &mut self,
13412 _: &SelectToPreviousWordStart,
13413 window: &mut Window,
13414 cx: &mut Context<Self>,
13415 ) {
13416 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13417 self.change_selections(Default::default(), window, cx, |s| {
13418 s.move_heads_with(|map, head, _| {
13419 (
13420 movement::previous_word_start(map, head),
13421 SelectionGoal::None,
13422 )
13423 });
13424 })
13425 }
13426
13427 pub fn select_to_previous_subword_start(
13428 &mut self,
13429 _: &SelectToPreviousSubwordStart,
13430 window: &mut Window,
13431 cx: &mut Context<Self>,
13432 ) {
13433 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13434 self.change_selections(Default::default(), window, cx, |s| {
13435 s.move_heads_with(|map, head, _| {
13436 (
13437 movement::previous_subword_start(map, head),
13438 SelectionGoal::None,
13439 )
13440 });
13441 })
13442 }
13443
13444 pub fn delete_to_previous_word_start(
13445 &mut self,
13446 action: &DeleteToPreviousWordStart,
13447 window: &mut Window,
13448 cx: &mut Context<Self>,
13449 ) {
13450 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13451 self.transact(window, cx, |this, window, cx| {
13452 this.select_autoclose_pair(window, cx);
13453 this.change_selections(Default::default(), window, cx, |s| {
13454 s.move_with(|map, selection| {
13455 if selection.is_empty() {
13456 let mut cursor = if action.ignore_newlines {
13457 movement::previous_word_start(map, selection.head())
13458 } else {
13459 movement::previous_word_start_or_newline(map, selection.head())
13460 };
13461 cursor = movement::adjust_greedy_deletion(
13462 map,
13463 selection.head(),
13464 cursor,
13465 action.ignore_brackets,
13466 );
13467 selection.set_head(cursor, SelectionGoal::None);
13468 }
13469 });
13470 });
13471 this.insert("", window, cx);
13472 });
13473 }
13474
13475 pub fn delete_to_previous_subword_start(
13476 &mut self,
13477 _: &DeleteToPreviousSubwordStart,
13478 window: &mut Window,
13479 cx: &mut Context<Self>,
13480 ) {
13481 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13482 self.transact(window, cx, |this, window, cx| {
13483 this.select_autoclose_pair(window, cx);
13484 this.change_selections(Default::default(), window, cx, |s| {
13485 s.move_with(|map, selection| {
13486 if selection.is_empty() {
13487 let mut cursor = movement::previous_subword_start(map, selection.head());
13488 cursor =
13489 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13490 selection.set_head(cursor, SelectionGoal::None);
13491 }
13492 });
13493 });
13494 this.insert("", window, cx);
13495 });
13496 }
13497
13498 pub fn move_to_next_word_end(
13499 &mut self,
13500 _: &MoveToNextWordEnd,
13501 window: &mut Window,
13502 cx: &mut Context<Self>,
13503 ) {
13504 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13505 self.change_selections(Default::default(), window, cx, |s| {
13506 s.move_cursors_with(|map, head, _| {
13507 (movement::next_word_end(map, head), SelectionGoal::None)
13508 });
13509 })
13510 }
13511
13512 pub fn move_to_next_subword_end(
13513 &mut self,
13514 _: &MoveToNextSubwordEnd,
13515 window: &mut Window,
13516 cx: &mut Context<Self>,
13517 ) {
13518 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13519 self.change_selections(Default::default(), window, cx, |s| {
13520 s.move_cursors_with(|map, head, _| {
13521 (movement::next_subword_end(map, head), SelectionGoal::None)
13522 });
13523 })
13524 }
13525
13526 pub fn select_to_next_word_end(
13527 &mut self,
13528 _: &SelectToNextWordEnd,
13529 window: &mut Window,
13530 cx: &mut Context<Self>,
13531 ) {
13532 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13533 self.change_selections(Default::default(), window, cx, |s| {
13534 s.move_heads_with(|map, head, _| {
13535 (movement::next_word_end(map, head), SelectionGoal::None)
13536 });
13537 })
13538 }
13539
13540 pub fn select_to_next_subword_end(
13541 &mut self,
13542 _: &SelectToNextSubwordEnd,
13543 window: &mut Window,
13544 cx: &mut Context<Self>,
13545 ) {
13546 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13547 self.change_selections(Default::default(), window, cx, |s| {
13548 s.move_heads_with(|map, head, _| {
13549 (movement::next_subword_end(map, head), SelectionGoal::None)
13550 });
13551 })
13552 }
13553
13554 pub fn delete_to_next_word_end(
13555 &mut self,
13556 action: &DeleteToNextWordEnd,
13557 window: &mut Window,
13558 cx: &mut Context<Self>,
13559 ) {
13560 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13561 self.transact(window, cx, |this, window, cx| {
13562 this.change_selections(Default::default(), window, cx, |s| {
13563 s.move_with(|map, selection| {
13564 if selection.is_empty() {
13565 let mut cursor = if action.ignore_newlines {
13566 movement::next_word_end(map, selection.head())
13567 } else {
13568 movement::next_word_end_or_newline(map, selection.head())
13569 };
13570 cursor = movement::adjust_greedy_deletion(
13571 map,
13572 selection.head(),
13573 cursor,
13574 action.ignore_brackets,
13575 );
13576 selection.set_head(cursor, SelectionGoal::None);
13577 }
13578 });
13579 });
13580 this.insert("", window, cx);
13581 });
13582 }
13583
13584 pub fn delete_to_next_subword_end(
13585 &mut self,
13586 _: &DeleteToNextSubwordEnd,
13587 window: &mut Window,
13588 cx: &mut Context<Self>,
13589 ) {
13590 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13591 self.transact(window, cx, |this, window, cx| {
13592 this.change_selections(Default::default(), window, cx, |s| {
13593 s.move_with(|map, selection| {
13594 if selection.is_empty() {
13595 let mut cursor = movement::next_subword_end(map, selection.head());
13596 cursor =
13597 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13598 selection.set_head(cursor, SelectionGoal::None);
13599 }
13600 });
13601 });
13602 this.insert("", window, cx);
13603 });
13604 }
13605
13606 pub fn move_to_beginning_of_line(
13607 &mut self,
13608 action: &MoveToBeginningOfLine,
13609 window: &mut Window,
13610 cx: &mut Context<Self>,
13611 ) {
13612 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13613 self.change_selections(Default::default(), window, cx, |s| {
13614 s.move_cursors_with(|map, head, _| {
13615 (
13616 movement::indented_line_beginning(
13617 map,
13618 head,
13619 action.stop_at_soft_wraps,
13620 action.stop_at_indent,
13621 ),
13622 SelectionGoal::None,
13623 )
13624 });
13625 })
13626 }
13627
13628 pub fn select_to_beginning_of_line(
13629 &mut self,
13630 action: &SelectToBeginningOfLine,
13631 window: &mut Window,
13632 cx: &mut Context<Self>,
13633 ) {
13634 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13635 self.change_selections(Default::default(), window, cx, |s| {
13636 s.move_heads_with(|map, head, _| {
13637 (
13638 movement::indented_line_beginning(
13639 map,
13640 head,
13641 action.stop_at_soft_wraps,
13642 action.stop_at_indent,
13643 ),
13644 SelectionGoal::None,
13645 )
13646 });
13647 });
13648 }
13649
13650 pub fn delete_to_beginning_of_line(
13651 &mut self,
13652 action: &DeleteToBeginningOfLine,
13653 window: &mut Window,
13654 cx: &mut Context<Self>,
13655 ) {
13656 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13657 self.transact(window, cx, |this, window, cx| {
13658 this.change_selections(Default::default(), window, cx, |s| {
13659 s.move_with(|_, selection| {
13660 selection.reversed = true;
13661 });
13662 });
13663
13664 this.select_to_beginning_of_line(
13665 &SelectToBeginningOfLine {
13666 stop_at_soft_wraps: false,
13667 stop_at_indent: action.stop_at_indent,
13668 },
13669 window,
13670 cx,
13671 );
13672 this.backspace(&Backspace, window, cx);
13673 });
13674 }
13675
13676 pub fn move_to_end_of_line(
13677 &mut self,
13678 action: &MoveToEndOfLine,
13679 window: &mut Window,
13680 cx: &mut Context<Self>,
13681 ) {
13682 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13683 self.change_selections(Default::default(), window, cx, |s| {
13684 s.move_cursors_with(|map, head, _| {
13685 (
13686 movement::line_end(map, head, action.stop_at_soft_wraps),
13687 SelectionGoal::None,
13688 )
13689 });
13690 })
13691 }
13692
13693 pub fn select_to_end_of_line(
13694 &mut self,
13695 action: &SelectToEndOfLine,
13696 window: &mut Window,
13697 cx: &mut Context<Self>,
13698 ) {
13699 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13700 self.change_selections(Default::default(), window, cx, |s| {
13701 s.move_heads_with(|map, head, _| {
13702 (
13703 movement::line_end(map, head, action.stop_at_soft_wraps),
13704 SelectionGoal::None,
13705 )
13706 });
13707 })
13708 }
13709
13710 pub fn delete_to_end_of_line(
13711 &mut self,
13712 _: &DeleteToEndOfLine,
13713 window: &mut Window,
13714 cx: &mut Context<Self>,
13715 ) {
13716 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13717 self.transact(window, cx, |this, window, cx| {
13718 this.select_to_end_of_line(
13719 &SelectToEndOfLine {
13720 stop_at_soft_wraps: false,
13721 },
13722 window,
13723 cx,
13724 );
13725 this.delete(&Delete, window, cx);
13726 });
13727 }
13728
13729 pub fn cut_to_end_of_line(
13730 &mut self,
13731 action: &CutToEndOfLine,
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 if !action.stop_at_newlines {
13745 this.change_selections(Default::default(), window, cx, |s| {
13746 s.move_with(|_, sel| {
13747 if sel.is_empty() {
13748 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13749 }
13750 });
13751 });
13752 }
13753 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13754 let item = this.cut_common(false, window, cx);
13755 cx.write_to_clipboard(item);
13756 });
13757 }
13758
13759 pub fn move_to_start_of_paragraph(
13760 &mut self,
13761 _: &MoveToStartOfParagraph,
13762 window: &mut Window,
13763 cx: &mut Context<Self>,
13764 ) {
13765 if matches!(self.mode, EditorMode::SingleLine) {
13766 cx.propagate();
13767 return;
13768 }
13769 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13770 self.change_selections(Default::default(), window, cx, |s| {
13771 s.move_with(|map, selection| {
13772 selection.collapse_to(
13773 movement::start_of_paragraph(map, selection.head(), 1),
13774 SelectionGoal::None,
13775 )
13776 });
13777 })
13778 }
13779
13780 pub fn move_to_end_of_paragraph(
13781 &mut self,
13782 _: &MoveToEndOfParagraph,
13783 window: &mut Window,
13784 cx: &mut Context<Self>,
13785 ) {
13786 if matches!(self.mode, EditorMode::SingleLine) {
13787 cx.propagate();
13788 return;
13789 }
13790 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13791 self.change_selections(Default::default(), window, cx, |s| {
13792 s.move_with(|map, selection| {
13793 selection.collapse_to(
13794 movement::end_of_paragraph(map, selection.head(), 1),
13795 SelectionGoal::None,
13796 )
13797 });
13798 })
13799 }
13800
13801 pub fn select_to_start_of_paragraph(
13802 &mut self,
13803 _: &SelectToStartOfParagraph,
13804 window: &mut Window,
13805 cx: &mut Context<Self>,
13806 ) {
13807 if matches!(self.mode, EditorMode::SingleLine) {
13808 cx.propagate();
13809 return;
13810 }
13811 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13812 self.change_selections(Default::default(), window, cx, |s| {
13813 s.move_heads_with(|map, head, _| {
13814 (
13815 movement::start_of_paragraph(map, head, 1),
13816 SelectionGoal::None,
13817 )
13818 });
13819 })
13820 }
13821
13822 pub fn select_to_end_of_paragraph(
13823 &mut self,
13824 _: &SelectToEndOfParagraph,
13825 window: &mut Window,
13826 cx: &mut Context<Self>,
13827 ) {
13828 if matches!(self.mode, EditorMode::SingleLine) {
13829 cx.propagate();
13830 return;
13831 }
13832 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13833 self.change_selections(Default::default(), window, cx, |s| {
13834 s.move_heads_with(|map, head, _| {
13835 (
13836 movement::end_of_paragraph(map, head, 1),
13837 SelectionGoal::None,
13838 )
13839 });
13840 })
13841 }
13842
13843 pub fn move_to_start_of_excerpt(
13844 &mut self,
13845 _: &MoveToStartOfExcerpt,
13846 window: &mut Window,
13847 cx: &mut Context<Self>,
13848 ) {
13849 if matches!(self.mode, EditorMode::SingleLine) {
13850 cx.propagate();
13851 return;
13852 }
13853 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13854 self.change_selections(Default::default(), window, cx, |s| {
13855 s.move_with(|map, selection| {
13856 selection.collapse_to(
13857 movement::start_of_excerpt(
13858 map,
13859 selection.head(),
13860 workspace::searchable::Direction::Prev,
13861 ),
13862 SelectionGoal::None,
13863 )
13864 });
13865 })
13866 }
13867
13868 pub fn move_to_start_of_next_excerpt(
13869 &mut self,
13870 _: &MoveToStartOfNextExcerpt,
13871 window: &mut Window,
13872 cx: &mut Context<Self>,
13873 ) {
13874 if matches!(self.mode, EditorMode::SingleLine) {
13875 cx.propagate();
13876 return;
13877 }
13878
13879 self.change_selections(Default::default(), window, cx, |s| {
13880 s.move_with(|map, selection| {
13881 selection.collapse_to(
13882 movement::start_of_excerpt(
13883 map,
13884 selection.head(),
13885 workspace::searchable::Direction::Next,
13886 ),
13887 SelectionGoal::None,
13888 )
13889 });
13890 })
13891 }
13892
13893 pub fn move_to_end_of_excerpt(
13894 &mut self,
13895 _: &MoveToEndOfExcerpt,
13896 window: &mut Window,
13897 cx: &mut Context<Self>,
13898 ) {
13899 if matches!(self.mode, EditorMode::SingleLine) {
13900 cx.propagate();
13901 return;
13902 }
13903 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13904 self.change_selections(Default::default(), window, cx, |s| {
13905 s.move_with(|map, selection| {
13906 selection.collapse_to(
13907 movement::end_of_excerpt(
13908 map,
13909 selection.head(),
13910 workspace::searchable::Direction::Next,
13911 ),
13912 SelectionGoal::None,
13913 )
13914 });
13915 })
13916 }
13917
13918 pub fn move_to_end_of_previous_excerpt(
13919 &mut self,
13920 _: &MoveToEndOfPreviousExcerpt,
13921 window: &mut Window,
13922 cx: &mut Context<Self>,
13923 ) {
13924 if matches!(self.mode, EditorMode::SingleLine) {
13925 cx.propagate();
13926 return;
13927 }
13928 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13929 self.change_selections(Default::default(), window, cx, |s| {
13930 s.move_with(|map, selection| {
13931 selection.collapse_to(
13932 movement::end_of_excerpt(
13933 map,
13934 selection.head(),
13935 workspace::searchable::Direction::Prev,
13936 ),
13937 SelectionGoal::None,
13938 )
13939 });
13940 })
13941 }
13942
13943 pub fn select_to_start_of_excerpt(
13944 &mut self,
13945 _: &SelectToStartOfExcerpt,
13946 window: &mut Window,
13947 cx: &mut Context<Self>,
13948 ) {
13949 if matches!(self.mode, EditorMode::SingleLine) {
13950 cx.propagate();
13951 return;
13952 }
13953 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13954 self.change_selections(Default::default(), window, cx, |s| {
13955 s.move_heads_with(|map, head, _| {
13956 (
13957 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13958 SelectionGoal::None,
13959 )
13960 });
13961 })
13962 }
13963
13964 pub fn select_to_start_of_next_excerpt(
13965 &mut self,
13966 _: &SelectToStartOfNextExcerpt,
13967 window: &mut Window,
13968 cx: &mut Context<Self>,
13969 ) {
13970 if matches!(self.mode, EditorMode::SingleLine) {
13971 cx.propagate();
13972 return;
13973 }
13974 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13975 self.change_selections(Default::default(), window, cx, |s| {
13976 s.move_heads_with(|map, head, _| {
13977 (
13978 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13979 SelectionGoal::None,
13980 )
13981 });
13982 })
13983 }
13984
13985 pub fn select_to_end_of_excerpt(
13986 &mut self,
13987 _: &SelectToEndOfExcerpt,
13988 window: &mut Window,
13989 cx: &mut Context<Self>,
13990 ) {
13991 if matches!(self.mode, EditorMode::SingleLine) {
13992 cx.propagate();
13993 return;
13994 }
13995 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13996 self.change_selections(Default::default(), window, cx, |s| {
13997 s.move_heads_with(|map, head, _| {
13998 (
13999 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14000 SelectionGoal::None,
14001 )
14002 });
14003 })
14004 }
14005
14006 pub fn select_to_end_of_previous_excerpt(
14007 &mut self,
14008 _: &SelectToEndOfPreviousExcerpt,
14009 window: &mut Window,
14010 cx: &mut Context<Self>,
14011 ) {
14012 if matches!(self.mode, EditorMode::SingleLine) {
14013 cx.propagate();
14014 return;
14015 }
14016 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14017 self.change_selections(Default::default(), window, cx, |s| {
14018 s.move_heads_with(|map, head, _| {
14019 (
14020 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14021 SelectionGoal::None,
14022 )
14023 });
14024 })
14025 }
14026
14027 pub fn move_to_beginning(
14028 &mut self,
14029 _: &MoveToBeginning,
14030 window: &mut Window,
14031 cx: &mut Context<Self>,
14032 ) {
14033 if matches!(self.mode, EditorMode::SingleLine) {
14034 cx.propagate();
14035 return;
14036 }
14037 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14038 self.change_selections(Default::default(), window, cx, |s| {
14039 s.select_ranges(vec![0..0]);
14040 });
14041 }
14042
14043 pub fn select_to_beginning(
14044 &mut self,
14045 _: &SelectToBeginning,
14046 window: &mut Window,
14047 cx: &mut Context<Self>,
14048 ) {
14049 let mut selection = self.selections.last::<Point>(cx);
14050 selection.set_head(Point::zero(), SelectionGoal::None);
14051 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14052 self.change_selections(Default::default(), window, cx, |s| {
14053 s.select(vec![selection]);
14054 });
14055 }
14056
14057 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14058 if matches!(self.mode, EditorMode::SingleLine) {
14059 cx.propagate();
14060 return;
14061 }
14062 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14063 let cursor = self.buffer.read(cx).read(cx).len();
14064 self.change_selections(Default::default(), window, cx, |s| {
14065 s.select_ranges(vec![cursor..cursor])
14066 });
14067 }
14068
14069 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14070 self.nav_history = nav_history;
14071 }
14072
14073 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14074 self.nav_history.as_ref()
14075 }
14076
14077 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14078 self.push_to_nav_history(
14079 self.selections.newest_anchor().head(),
14080 None,
14081 false,
14082 true,
14083 cx,
14084 );
14085 }
14086
14087 fn push_to_nav_history(
14088 &mut self,
14089 cursor_anchor: Anchor,
14090 new_position: Option<Point>,
14091 is_deactivate: bool,
14092 always: bool,
14093 cx: &mut Context<Self>,
14094 ) {
14095 if let Some(nav_history) = self.nav_history.as_mut() {
14096 let buffer = self.buffer.read(cx).read(cx);
14097 let cursor_position = cursor_anchor.to_point(&buffer);
14098 let scroll_state = self.scroll_manager.anchor();
14099 let scroll_top_row = scroll_state.top_row(&buffer);
14100 drop(buffer);
14101
14102 if let Some(new_position) = new_position {
14103 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14104 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14105 return;
14106 }
14107 }
14108
14109 nav_history.push(
14110 Some(NavigationData {
14111 cursor_anchor,
14112 cursor_position,
14113 scroll_anchor: scroll_state,
14114 scroll_top_row,
14115 }),
14116 cx,
14117 );
14118 cx.emit(EditorEvent::PushedToNavHistory {
14119 anchor: cursor_anchor,
14120 is_deactivate,
14121 })
14122 }
14123 }
14124
14125 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14126 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14127 let buffer = self.buffer.read(cx).snapshot(cx);
14128 let mut selection = self.selections.first::<usize>(cx);
14129 selection.set_head(buffer.len(), SelectionGoal::None);
14130 self.change_selections(Default::default(), window, cx, |s| {
14131 s.select(vec![selection]);
14132 });
14133 }
14134
14135 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14136 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14137 let end = self.buffer.read(cx).read(cx).len();
14138 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14139 s.select_ranges(vec![0..end]);
14140 });
14141 }
14142
14143 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14144 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14145 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14146 let mut selections = self.selections.all::<Point>(cx);
14147 let max_point = display_map.buffer_snapshot().max_point();
14148 for selection in &mut selections {
14149 let rows = selection.spanned_rows(true, &display_map);
14150 selection.start = Point::new(rows.start.0, 0);
14151 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14152 selection.reversed = false;
14153 }
14154 self.change_selections(Default::default(), window, cx, |s| {
14155 s.select(selections);
14156 });
14157 }
14158
14159 pub fn split_selection_into_lines(
14160 &mut self,
14161 action: &SplitSelectionIntoLines,
14162 window: &mut Window,
14163 cx: &mut Context<Self>,
14164 ) {
14165 let selections = self
14166 .selections
14167 .all::<Point>(cx)
14168 .into_iter()
14169 .map(|selection| selection.start..selection.end)
14170 .collect::<Vec<_>>();
14171 self.unfold_ranges(&selections, true, true, cx);
14172
14173 let mut new_selection_ranges = Vec::new();
14174 {
14175 let buffer = self.buffer.read(cx).read(cx);
14176 for selection in selections {
14177 for row in selection.start.row..selection.end.row {
14178 let line_start = Point::new(row, 0);
14179 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14180
14181 if action.keep_selections {
14182 // Keep the selection range for each line
14183 let selection_start = if row == selection.start.row {
14184 selection.start
14185 } else {
14186 line_start
14187 };
14188 new_selection_ranges.push(selection_start..line_end);
14189 } else {
14190 // Collapse to cursor at end of line
14191 new_selection_ranges.push(line_end..line_end);
14192 }
14193 }
14194
14195 let is_multiline_selection = selection.start.row != selection.end.row;
14196 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14197 // so this action feels more ergonomic when paired with other selection operations
14198 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14199 if !should_skip_last {
14200 if action.keep_selections {
14201 if is_multiline_selection {
14202 let line_start = Point::new(selection.end.row, 0);
14203 new_selection_ranges.push(line_start..selection.end);
14204 } else {
14205 new_selection_ranges.push(selection.start..selection.end);
14206 }
14207 } else {
14208 new_selection_ranges.push(selection.end..selection.end);
14209 }
14210 }
14211 }
14212 }
14213 self.change_selections(Default::default(), window, cx, |s| {
14214 s.select_ranges(new_selection_ranges);
14215 });
14216 }
14217
14218 pub fn add_selection_above(
14219 &mut self,
14220 action: &AddSelectionAbove,
14221 window: &mut Window,
14222 cx: &mut Context<Self>,
14223 ) {
14224 self.add_selection(true, action.skip_soft_wrap, window, cx);
14225 }
14226
14227 pub fn add_selection_below(
14228 &mut self,
14229 action: &AddSelectionBelow,
14230 window: &mut Window,
14231 cx: &mut Context<Self>,
14232 ) {
14233 self.add_selection(false, action.skip_soft_wrap, window, cx);
14234 }
14235
14236 fn add_selection(
14237 &mut self,
14238 above: bool,
14239 skip_soft_wrap: bool,
14240 window: &mut Window,
14241 cx: &mut Context<Self>,
14242 ) {
14243 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14244
14245 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14246 let all_selections = self.selections.all::<Point>(cx);
14247 let text_layout_details = self.text_layout_details(window);
14248
14249 let (mut columnar_selections, new_selections_to_columnarize) = {
14250 if let Some(state) = self.add_selections_state.as_ref() {
14251 let columnar_selection_ids: HashSet<_> = state
14252 .groups
14253 .iter()
14254 .flat_map(|group| group.stack.iter())
14255 .copied()
14256 .collect();
14257
14258 all_selections
14259 .into_iter()
14260 .partition(|s| columnar_selection_ids.contains(&s.id))
14261 } else {
14262 (Vec::new(), all_selections)
14263 }
14264 };
14265
14266 let mut state = self
14267 .add_selections_state
14268 .take()
14269 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14270
14271 for selection in new_selections_to_columnarize {
14272 let range = selection.display_range(&display_map).sorted();
14273 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14274 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14275 let positions = start_x.min(end_x)..start_x.max(end_x);
14276 let mut stack = Vec::new();
14277 for row in range.start.row().0..=range.end.row().0 {
14278 if let Some(selection) = self.selections.build_columnar_selection(
14279 &display_map,
14280 DisplayRow(row),
14281 &positions,
14282 selection.reversed,
14283 &text_layout_details,
14284 ) {
14285 stack.push(selection.id);
14286 columnar_selections.push(selection);
14287 }
14288 }
14289 if !stack.is_empty() {
14290 if above {
14291 stack.reverse();
14292 }
14293 state.groups.push(AddSelectionsGroup { above, stack });
14294 }
14295 }
14296
14297 let mut final_selections = Vec::new();
14298 let end_row = if above {
14299 DisplayRow(0)
14300 } else {
14301 display_map.max_point().row()
14302 };
14303
14304 let mut last_added_item_per_group = HashMap::default();
14305 for group in state.groups.iter_mut() {
14306 if let Some(last_id) = group.stack.last() {
14307 last_added_item_per_group.insert(*last_id, group);
14308 }
14309 }
14310
14311 for selection in columnar_selections {
14312 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14313 if above == group.above {
14314 let range = selection.display_range(&display_map).sorted();
14315 debug_assert_eq!(range.start.row(), range.end.row());
14316 let mut row = range.start.row();
14317 let positions =
14318 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14319 Pixels::from(start)..Pixels::from(end)
14320 } else {
14321 let start_x =
14322 display_map.x_for_display_point(range.start, &text_layout_details);
14323 let end_x =
14324 display_map.x_for_display_point(range.end, &text_layout_details);
14325 start_x.min(end_x)..start_x.max(end_x)
14326 };
14327
14328 let mut maybe_new_selection = None;
14329 let direction = if above { -1 } else { 1 };
14330
14331 while row != end_row {
14332 if skip_soft_wrap {
14333 row = display_map
14334 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14335 .row();
14336 } else if above {
14337 row.0 -= 1;
14338 } else {
14339 row.0 += 1;
14340 }
14341
14342 if let Some(new_selection) = self.selections.build_columnar_selection(
14343 &display_map,
14344 row,
14345 &positions,
14346 selection.reversed,
14347 &text_layout_details,
14348 ) {
14349 maybe_new_selection = Some(new_selection);
14350 break;
14351 }
14352 }
14353
14354 if let Some(new_selection) = maybe_new_selection {
14355 group.stack.push(new_selection.id);
14356 if above {
14357 final_selections.push(new_selection);
14358 final_selections.push(selection);
14359 } else {
14360 final_selections.push(selection);
14361 final_selections.push(new_selection);
14362 }
14363 } else {
14364 final_selections.push(selection);
14365 }
14366 } else {
14367 group.stack.pop();
14368 }
14369 } else {
14370 final_selections.push(selection);
14371 }
14372 }
14373
14374 self.change_selections(Default::default(), window, cx, |s| {
14375 s.select(final_selections);
14376 });
14377
14378 let final_selection_ids: HashSet<_> = self
14379 .selections
14380 .all::<Point>(cx)
14381 .iter()
14382 .map(|s| s.id)
14383 .collect();
14384 state.groups.retain_mut(|group| {
14385 // selections might get merged above so we remove invalid items from stacks
14386 group.stack.retain(|id| final_selection_ids.contains(id));
14387
14388 // single selection in stack can be treated as initial state
14389 group.stack.len() > 1
14390 });
14391
14392 if !state.groups.is_empty() {
14393 self.add_selections_state = Some(state);
14394 }
14395 }
14396
14397 fn select_match_ranges(
14398 &mut self,
14399 range: Range<usize>,
14400 reversed: bool,
14401 replace_newest: bool,
14402 auto_scroll: Option<Autoscroll>,
14403 window: &mut Window,
14404 cx: &mut Context<Editor>,
14405 ) {
14406 self.unfold_ranges(
14407 std::slice::from_ref(&range),
14408 false,
14409 auto_scroll.is_some(),
14410 cx,
14411 );
14412 let effects = if let Some(scroll) = auto_scroll {
14413 SelectionEffects::scroll(scroll)
14414 } else {
14415 SelectionEffects::no_scroll()
14416 };
14417 self.change_selections(effects, window, cx, |s| {
14418 if replace_newest {
14419 s.delete(s.newest_anchor().id);
14420 }
14421 if reversed {
14422 s.insert_range(range.end..range.start);
14423 } else {
14424 s.insert_range(range);
14425 }
14426 });
14427 }
14428
14429 pub fn select_next_match_internal(
14430 &mut self,
14431 display_map: &DisplaySnapshot,
14432 replace_newest: bool,
14433 autoscroll: Option<Autoscroll>,
14434 window: &mut Window,
14435 cx: &mut Context<Self>,
14436 ) -> Result<()> {
14437 let buffer = display_map.buffer_snapshot();
14438 let mut selections = self.selections.all::<usize>(cx);
14439 if let Some(mut select_next_state) = self.select_next_state.take() {
14440 let query = &select_next_state.query;
14441 if !select_next_state.done {
14442 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14443 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14444 let mut next_selected_range = None;
14445
14446 // Collect and sort selection ranges for efficient overlap checking
14447 let mut selection_ranges: Vec<_> = selections.iter().map(|s| s.range()).collect();
14448 selection_ranges.sort_by_key(|r| r.start);
14449
14450 let bytes_after_last_selection =
14451 buffer.bytes_in_range(last_selection.end..buffer.len());
14452 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14453 let query_matches = query
14454 .stream_find_iter(bytes_after_last_selection)
14455 .map(|result| (last_selection.end, result))
14456 .chain(
14457 query
14458 .stream_find_iter(bytes_before_first_selection)
14459 .map(|result| (0, result)),
14460 );
14461
14462 for (start_offset, query_match) in query_matches {
14463 let query_match = query_match.unwrap(); // can only fail due to I/O
14464 let offset_range =
14465 start_offset + query_match.start()..start_offset + query_match.end();
14466
14467 if !select_next_state.wordwise
14468 || (!buffer.is_inside_word(offset_range.start, None)
14469 && !buffer.is_inside_word(offset_range.end, None))
14470 {
14471 // Use binary search to check for overlap (O(log n))
14472 let overlaps = selection_ranges
14473 .binary_search_by(|range| {
14474 if range.end <= offset_range.start {
14475 std::cmp::Ordering::Less
14476 } else if range.start >= offset_range.end {
14477 std::cmp::Ordering::Greater
14478 } else {
14479 std::cmp::Ordering::Equal
14480 }
14481 })
14482 .is_ok();
14483
14484 if !overlaps {
14485 next_selected_range = Some(offset_range);
14486 break;
14487 }
14488 }
14489 }
14490
14491 if let Some(next_selected_range) = next_selected_range {
14492 self.select_match_ranges(
14493 next_selected_range,
14494 last_selection.reversed,
14495 replace_newest,
14496 autoscroll,
14497 window,
14498 cx,
14499 );
14500 } else {
14501 select_next_state.done = true;
14502 }
14503 }
14504
14505 self.select_next_state = Some(select_next_state);
14506 } else {
14507 let mut only_carets = true;
14508 let mut same_text_selected = true;
14509 let mut selected_text = None;
14510
14511 let mut selections_iter = selections.iter().peekable();
14512 while let Some(selection) = selections_iter.next() {
14513 if selection.start != selection.end {
14514 only_carets = false;
14515 }
14516
14517 if same_text_selected {
14518 if selected_text.is_none() {
14519 selected_text =
14520 Some(buffer.text_for_range(selection.range()).collect::<String>());
14521 }
14522
14523 if let Some(next_selection) = selections_iter.peek() {
14524 if next_selection.range().len() == selection.range().len() {
14525 let next_selected_text = buffer
14526 .text_for_range(next_selection.range())
14527 .collect::<String>();
14528 if Some(next_selected_text) != selected_text {
14529 same_text_selected = false;
14530 selected_text = None;
14531 }
14532 } else {
14533 same_text_selected = false;
14534 selected_text = None;
14535 }
14536 }
14537 }
14538 }
14539
14540 if only_carets {
14541 for selection in &mut selections {
14542 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14543 selection.start = word_range.start;
14544 selection.end = word_range.end;
14545 selection.goal = SelectionGoal::None;
14546 selection.reversed = false;
14547 self.select_match_ranges(
14548 selection.start..selection.end,
14549 selection.reversed,
14550 replace_newest,
14551 autoscroll,
14552 window,
14553 cx,
14554 );
14555 }
14556
14557 if selections.len() == 1 {
14558 let selection = selections
14559 .last()
14560 .expect("ensured that there's only one selection");
14561 let query = buffer
14562 .text_for_range(selection.start..selection.end)
14563 .collect::<String>();
14564 let is_empty = query.is_empty();
14565 let select_state = SelectNextState {
14566 query: AhoCorasick::new(&[query])?,
14567 wordwise: true,
14568 done: is_empty,
14569 };
14570 self.select_next_state = Some(select_state);
14571 } else {
14572 self.select_next_state = None;
14573 }
14574 } else if let Some(selected_text) = selected_text {
14575 self.select_next_state = Some(SelectNextState {
14576 query: AhoCorasick::new(&[selected_text])?,
14577 wordwise: false,
14578 done: false,
14579 });
14580 self.select_next_match_internal(
14581 display_map,
14582 replace_newest,
14583 autoscroll,
14584 window,
14585 cx,
14586 )?;
14587 }
14588 }
14589 Ok(())
14590 }
14591
14592 pub fn select_all_matches(
14593 &mut self,
14594 _action: &SelectAllMatches,
14595 window: &mut Window,
14596 cx: &mut Context<Self>,
14597 ) -> Result<()> {
14598 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14599
14600 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14601
14602 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14603 let Some(select_next_state) = self.select_next_state.as_mut() else {
14604 return Ok(());
14605 };
14606 if select_next_state.done {
14607 return Ok(());
14608 }
14609
14610 let mut new_selections = Vec::new();
14611
14612 let reversed = self.selections.oldest::<usize>(cx).reversed;
14613 let buffer = display_map.buffer_snapshot();
14614 let query_matches = select_next_state
14615 .query
14616 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14617
14618 for query_match in query_matches.into_iter() {
14619 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14620 let offset_range = if reversed {
14621 query_match.end()..query_match.start()
14622 } else {
14623 query_match.start()..query_match.end()
14624 };
14625
14626 if !select_next_state.wordwise
14627 || (!buffer.is_inside_word(offset_range.start, None)
14628 && !buffer.is_inside_word(offset_range.end, None))
14629 {
14630 new_selections.push(offset_range.start..offset_range.end);
14631 }
14632 }
14633
14634 select_next_state.done = true;
14635
14636 if new_selections.is_empty() {
14637 log::error!("bug: new_selections is empty in select_all_matches");
14638 return Ok(());
14639 }
14640
14641 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14642 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14643 selections.select_ranges(new_selections)
14644 });
14645
14646 Ok(())
14647 }
14648
14649 pub fn select_next(
14650 &mut self,
14651 action: &SelectNext,
14652 window: &mut Window,
14653 cx: &mut Context<Self>,
14654 ) -> Result<()> {
14655 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14656 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14657 self.select_next_match_internal(
14658 &display_map,
14659 action.replace_newest,
14660 Some(Autoscroll::newest()),
14661 window,
14662 cx,
14663 )?;
14664 Ok(())
14665 }
14666
14667 pub fn select_previous(
14668 &mut self,
14669 action: &SelectPrevious,
14670 window: &mut Window,
14671 cx: &mut Context<Self>,
14672 ) -> Result<()> {
14673 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14674 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14675 let buffer = display_map.buffer_snapshot();
14676 let mut selections = self.selections.all::<usize>(cx);
14677 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14678 let query = &select_prev_state.query;
14679 if !select_prev_state.done {
14680 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14681 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14682 let mut next_selected_range = None;
14683 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14684 let bytes_before_last_selection =
14685 buffer.reversed_bytes_in_range(0..last_selection.start);
14686 let bytes_after_first_selection =
14687 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14688 let query_matches = query
14689 .stream_find_iter(bytes_before_last_selection)
14690 .map(|result| (last_selection.start, result))
14691 .chain(
14692 query
14693 .stream_find_iter(bytes_after_first_selection)
14694 .map(|result| (buffer.len(), result)),
14695 );
14696 for (end_offset, query_match) in query_matches {
14697 let query_match = query_match.unwrap(); // can only fail due to I/O
14698 let offset_range =
14699 end_offset - query_match.end()..end_offset - query_match.start();
14700
14701 if !select_prev_state.wordwise
14702 || (!buffer.is_inside_word(offset_range.start, None)
14703 && !buffer.is_inside_word(offset_range.end, None))
14704 {
14705 next_selected_range = Some(offset_range);
14706 break;
14707 }
14708 }
14709
14710 if let Some(next_selected_range) = next_selected_range {
14711 self.select_match_ranges(
14712 next_selected_range,
14713 last_selection.reversed,
14714 action.replace_newest,
14715 Some(Autoscroll::newest()),
14716 window,
14717 cx,
14718 );
14719 } else {
14720 select_prev_state.done = true;
14721 }
14722 }
14723
14724 self.select_prev_state = Some(select_prev_state);
14725 } else {
14726 let mut only_carets = true;
14727 let mut same_text_selected = true;
14728 let mut selected_text = None;
14729
14730 let mut selections_iter = selections.iter().peekable();
14731 while let Some(selection) = selections_iter.next() {
14732 if selection.start != selection.end {
14733 only_carets = false;
14734 }
14735
14736 if same_text_selected {
14737 if selected_text.is_none() {
14738 selected_text =
14739 Some(buffer.text_for_range(selection.range()).collect::<String>());
14740 }
14741
14742 if let Some(next_selection) = selections_iter.peek() {
14743 if next_selection.range().len() == selection.range().len() {
14744 let next_selected_text = buffer
14745 .text_for_range(next_selection.range())
14746 .collect::<String>();
14747 if Some(next_selected_text) != selected_text {
14748 same_text_selected = false;
14749 selected_text = None;
14750 }
14751 } else {
14752 same_text_selected = false;
14753 selected_text = None;
14754 }
14755 }
14756 }
14757 }
14758
14759 if only_carets {
14760 for selection in &mut selections {
14761 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14762 selection.start = word_range.start;
14763 selection.end = word_range.end;
14764 selection.goal = SelectionGoal::None;
14765 selection.reversed = false;
14766 self.select_match_ranges(
14767 selection.start..selection.end,
14768 selection.reversed,
14769 action.replace_newest,
14770 Some(Autoscroll::newest()),
14771 window,
14772 cx,
14773 );
14774 }
14775 if selections.len() == 1 {
14776 let selection = selections
14777 .last()
14778 .expect("ensured that there's only one selection");
14779 let query = buffer
14780 .text_for_range(selection.start..selection.end)
14781 .collect::<String>();
14782 let is_empty = query.is_empty();
14783 let select_state = SelectNextState {
14784 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14785 wordwise: true,
14786 done: is_empty,
14787 };
14788 self.select_prev_state = Some(select_state);
14789 } else {
14790 self.select_prev_state = None;
14791 }
14792 } else if let Some(selected_text) = selected_text {
14793 self.select_prev_state = Some(SelectNextState {
14794 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14795 wordwise: false,
14796 done: false,
14797 });
14798 self.select_previous(action, window, cx)?;
14799 }
14800 }
14801 Ok(())
14802 }
14803
14804 pub fn find_next_match(
14805 &mut self,
14806 _: &FindNextMatch,
14807 window: &mut Window,
14808 cx: &mut Context<Self>,
14809 ) -> Result<()> {
14810 let selections = self.selections.disjoint_anchors_arc();
14811 match selections.first() {
14812 Some(first) if selections.len() >= 2 => {
14813 self.change_selections(Default::default(), window, cx, |s| {
14814 s.select_ranges([first.range()]);
14815 });
14816 }
14817 _ => self.select_next(
14818 &SelectNext {
14819 replace_newest: true,
14820 },
14821 window,
14822 cx,
14823 )?,
14824 }
14825 Ok(())
14826 }
14827
14828 pub fn find_previous_match(
14829 &mut self,
14830 _: &FindPreviousMatch,
14831 window: &mut Window,
14832 cx: &mut Context<Self>,
14833 ) -> Result<()> {
14834 let selections = self.selections.disjoint_anchors_arc();
14835 match selections.last() {
14836 Some(last) if selections.len() >= 2 => {
14837 self.change_selections(Default::default(), window, cx, |s| {
14838 s.select_ranges([last.range()]);
14839 });
14840 }
14841 _ => self.select_previous(
14842 &SelectPrevious {
14843 replace_newest: true,
14844 },
14845 window,
14846 cx,
14847 )?,
14848 }
14849 Ok(())
14850 }
14851
14852 pub fn toggle_comments(
14853 &mut self,
14854 action: &ToggleComments,
14855 window: &mut Window,
14856 cx: &mut Context<Self>,
14857 ) {
14858 if self.read_only(cx) {
14859 return;
14860 }
14861 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14862 let text_layout_details = &self.text_layout_details(window);
14863 self.transact(window, cx, |this, window, cx| {
14864 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14865 let mut edits = Vec::new();
14866 let mut selection_edit_ranges = Vec::new();
14867 let mut last_toggled_row = None;
14868 let snapshot = this.buffer.read(cx).read(cx);
14869 let empty_str: Arc<str> = Arc::default();
14870 let mut suffixes_inserted = Vec::new();
14871 let ignore_indent = action.ignore_indent;
14872
14873 fn comment_prefix_range(
14874 snapshot: &MultiBufferSnapshot,
14875 row: MultiBufferRow,
14876 comment_prefix: &str,
14877 comment_prefix_whitespace: &str,
14878 ignore_indent: bool,
14879 ) -> Range<Point> {
14880 let indent_size = if ignore_indent {
14881 0
14882 } else {
14883 snapshot.indent_size_for_line(row).len
14884 };
14885
14886 let start = Point::new(row.0, indent_size);
14887
14888 let mut line_bytes = snapshot
14889 .bytes_in_range(start..snapshot.max_point())
14890 .flatten()
14891 .copied();
14892
14893 // If this line currently begins with the line comment prefix, then record
14894 // the range containing the prefix.
14895 if line_bytes
14896 .by_ref()
14897 .take(comment_prefix.len())
14898 .eq(comment_prefix.bytes())
14899 {
14900 // Include any whitespace that matches the comment prefix.
14901 let matching_whitespace_len = line_bytes
14902 .zip(comment_prefix_whitespace.bytes())
14903 .take_while(|(a, b)| a == b)
14904 .count() as u32;
14905 let end = Point::new(
14906 start.row,
14907 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14908 );
14909 start..end
14910 } else {
14911 start..start
14912 }
14913 }
14914
14915 fn comment_suffix_range(
14916 snapshot: &MultiBufferSnapshot,
14917 row: MultiBufferRow,
14918 comment_suffix: &str,
14919 comment_suffix_has_leading_space: bool,
14920 ) -> Range<Point> {
14921 let end = Point::new(row.0, snapshot.line_len(row));
14922 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14923
14924 let mut line_end_bytes = snapshot
14925 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14926 .flatten()
14927 .copied();
14928
14929 let leading_space_len = if suffix_start_column > 0
14930 && line_end_bytes.next() == Some(b' ')
14931 && comment_suffix_has_leading_space
14932 {
14933 1
14934 } else {
14935 0
14936 };
14937
14938 // If this line currently begins with the line comment prefix, then record
14939 // the range containing the prefix.
14940 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14941 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14942 start..end
14943 } else {
14944 end..end
14945 }
14946 }
14947
14948 // TODO: Handle selections that cross excerpts
14949 for selection in &mut selections {
14950 let start_column = snapshot
14951 .indent_size_for_line(MultiBufferRow(selection.start.row))
14952 .len;
14953 let language = if let Some(language) =
14954 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14955 {
14956 language
14957 } else {
14958 continue;
14959 };
14960
14961 selection_edit_ranges.clear();
14962
14963 // If multiple selections contain a given row, avoid processing that
14964 // row more than once.
14965 let mut start_row = MultiBufferRow(selection.start.row);
14966 if last_toggled_row == Some(start_row) {
14967 start_row = start_row.next_row();
14968 }
14969 let end_row =
14970 if selection.end.row > selection.start.row && selection.end.column == 0 {
14971 MultiBufferRow(selection.end.row - 1)
14972 } else {
14973 MultiBufferRow(selection.end.row)
14974 };
14975 last_toggled_row = Some(end_row);
14976
14977 if start_row > end_row {
14978 continue;
14979 }
14980
14981 // If the language has line comments, toggle those.
14982 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14983
14984 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14985 if ignore_indent {
14986 full_comment_prefixes = full_comment_prefixes
14987 .into_iter()
14988 .map(|s| Arc::from(s.trim_end()))
14989 .collect();
14990 }
14991
14992 if !full_comment_prefixes.is_empty() {
14993 let first_prefix = full_comment_prefixes
14994 .first()
14995 .expect("prefixes is non-empty");
14996 let prefix_trimmed_lengths = full_comment_prefixes
14997 .iter()
14998 .map(|p| p.trim_end_matches(' ').len())
14999 .collect::<SmallVec<[usize; 4]>>();
15000
15001 let mut all_selection_lines_are_comments = true;
15002
15003 for row in start_row.0..=end_row.0 {
15004 let row = MultiBufferRow(row);
15005 if start_row < end_row && snapshot.is_line_blank(row) {
15006 continue;
15007 }
15008
15009 let prefix_range = full_comment_prefixes
15010 .iter()
15011 .zip(prefix_trimmed_lengths.iter().copied())
15012 .map(|(prefix, trimmed_prefix_len)| {
15013 comment_prefix_range(
15014 snapshot.deref(),
15015 row,
15016 &prefix[..trimmed_prefix_len],
15017 &prefix[trimmed_prefix_len..],
15018 ignore_indent,
15019 )
15020 })
15021 .max_by_key(|range| range.end.column - range.start.column)
15022 .expect("prefixes is non-empty");
15023
15024 if prefix_range.is_empty() {
15025 all_selection_lines_are_comments = false;
15026 }
15027
15028 selection_edit_ranges.push(prefix_range);
15029 }
15030
15031 if all_selection_lines_are_comments {
15032 edits.extend(
15033 selection_edit_ranges
15034 .iter()
15035 .cloned()
15036 .map(|range| (range, empty_str.clone())),
15037 );
15038 } else {
15039 let min_column = selection_edit_ranges
15040 .iter()
15041 .map(|range| range.start.column)
15042 .min()
15043 .unwrap_or(0);
15044 edits.extend(selection_edit_ranges.iter().map(|range| {
15045 let position = Point::new(range.start.row, min_column);
15046 (position..position, first_prefix.clone())
15047 }));
15048 }
15049 } else if let Some(BlockCommentConfig {
15050 start: full_comment_prefix,
15051 end: comment_suffix,
15052 ..
15053 }) = language.block_comment()
15054 {
15055 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15056 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15057 let prefix_range = comment_prefix_range(
15058 snapshot.deref(),
15059 start_row,
15060 comment_prefix,
15061 comment_prefix_whitespace,
15062 ignore_indent,
15063 );
15064 let suffix_range = comment_suffix_range(
15065 snapshot.deref(),
15066 end_row,
15067 comment_suffix.trim_start_matches(' '),
15068 comment_suffix.starts_with(' '),
15069 );
15070
15071 if prefix_range.is_empty() || suffix_range.is_empty() {
15072 edits.push((
15073 prefix_range.start..prefix_range.start,
15074 full_comment_prefix.clone(),
15075 ));
15076 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15077 suffixes_inserted.push((end_row, comment_suffix.len()));
15078 } else {
15079 edits.push((prefix_range, empty_str.clone()));
15080 edits.push((suffix_range, empty_str.clone()));
15081 }
15082 } else {
15083 continue;
15084 }
15085 }
15086
15087 drop(snapshot);
15088 this.buffer.update(cx, |buffer, cx| {
15089 buffer.edit(edits, None, cx);
15090 });
15091
15092 // Adjust selections so that they end before any comment suffixes that
15093 // were inserted.
15094 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15095 let mut selections = this.selections.all::<Point>(cx);
15096 let snapshot = this.buffer.read(cx).read(cx);
15097 for selection in &mut selections {
15098 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15099 match row.cmp(&MultiBufferRow(selection.end.row)) {
15100 Ordering::Less => {
15101 suffixes_inserted.next();
15102 continue;
15103 }
15104 Ordering::Greater => break,
15105 Ordering::Equal => {
15106 if selection.end.column == snapshot.line_len(row) {
15107 if selection.is_empty() {
15108 selection.start.column -= suffix_len as u32;
15109 }
15110 selection.end.column -= suffix_len as u32;
15111 }
15112 break;
15113 }
15114 }
15115 }
15116 }
15117
15118 drop(snapshot);
15119 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15120
15121 let selections = this.selections.all::<Point>(cx);
15122 let selections_on_single_row = selections.windows(2).all(|selections| {
15123 selections[0].start.row == selections[1].start.row
15124 && selections[0].end.row == selections[1].end.row
15125 && selections[0].start.row == selections[0].end.row
15126 });
15127 let selections_selecting = selections
15128 .iter()
15129 .any(|selection| selection.start != selection.end);
15130 let advance_downwards = action.advance_downwards
15131 && selections_on_single_row
15132 && !selections_selecting
15133 && !matches!(this.mode, EditorMode::SingleLine);
15134
15135 if advance_downwards {
15136 let snapshot = this.buffer.read(cx).snapshot(cx);
15137
15138 this.change_selections(Default::default(), window, cx, |s| {
15139 s.move_cursors_with(|display_snapshot, display_point, _| {
15140 let mut point = display_point.to_point(display_snapshot);
15141 point.row += 1;
15142 point = snapshot.clip_point(point, Bias::Left);
15143 let display_point = point.to_display_point(display_snapshot);
15144 let goal = SelectionGoal::HorizontalPosition(
15145 display_snapshot
15146 .x_for_display_point(display_point, text_layout_details)
15147 .into(),
15148 );
15149 (display_point, goal)
15150 })
15151 });
15152 }
15153 });
15154 }
15155
15156 pub fn select_enclosing_symbol(
15157 &mut self,
15158 _: &SelectEnclosingSymbol,
15159 window: &mut Window,
15160 cx: &mut Context<Self>,
15161 ) {
15162 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15163
15164 let buffer = self.buffer.read(cx).snapshot(cx);
15165 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
15166
15167 fn update_selection(
15168 selection: &Selection<usize>,
15169 buffer_snap: &MultiBufferSnapshot,
15170 ) -> Option<Selection<usize>> {
15171 let cursor = selection.head();
15172 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15173 for symbol in symbols.iter().rev() {
15174 let start = symbol.range.start.to_offset(buffer_snap);
15175 let end = symbol.range.end.to_offset(buffer_snap);
15176 let new_range = start..end;
15177 if start < selection.start || end > selection.end {
15178 return Some(Selection {
15179 id: selection.id,
15180 start: new_range.start,
15181 end: new_range.end,
15182 goal: SelectionGoal::None,
15183 reversed: selection.reversed,
15184 });
15185 }
15186 }
15187 None
15188 }
15189
15190 let mut selected_larger_symbol = false;
15191 let new_selections = old_selections
15192 .iter()
15193 .map(|selection| match update_selection(selection, &buffer) {
15194 Some(new_selection) => {
15195 if new_selection.range() != selection.range() {
15196 selected_larger_symbol = true;
15197 }
15198 new_selection
15199 }
15200 None => selection.clone(),
15201 })
15202 .collect::<Vec<_>>();
15203
15204 if selected_larger_symbol {
15205 self.change_selections(Default::default(), window, cx, |s| {
15206 s.select(new_selections);
15207 });
15208 }
15209 }
15210
15211 pub fn select_larger_syntax_node(
15212 &mut self,
15213 _: &SelectLargerSyntaxNode,
15214 window: &mut Window,
15215 cx: &mut Context<Self>,
15216 ) {
15217 let Some(visible_row_count) = self.visible_row_count() else {
15218 return;
15219 };
15220 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15221 if old_selections.is_empty() {
15222 return;
15223 }
15224
15225 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15226
15227 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15228 let buffer = self.buffer.read(cx).snapshot(cx);
15229
15230 let mut selected_larger_node = false;
15231 let mut new_selections = old_selections
15232 .iter()
15233 .map(|selection| {
15234 let old_range = selection.start..selection.end;
15235
15236 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15237 // manually select word at selection
15238 if ["string_content", "inline"].contains(&node.kind()) {
15239 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15240 // ignore if word is already selected
15241 if !word_range.is_empty() && old_range != word_range {
15242 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15243 // only select word if start and end point belongs to same word
15244 if word_range == last_word_range {
15245 selected_larger_node = true;
15246 return Selection {
15247 id: selection.id,
15248 start: word_range.start,
15249 end: word_range.end,
15250 goal: SelectionGoal::None,
15251 reversed: selection.reversed,
15252 };
15253 }
15254 }
15255 }
15256 }
15257
15258 let mut new_range = old_range.clone();
15259 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15260 new_range = range;
15261 if !node.is_named() {
15262 continue;
15263 }
15264 if !display_map.intersects_fold(new_range.start)
15265 && !display_map.intersects_fold(new_range.end)
15266 {
15267 break;
15268 }
15269 }
15270
15271 selected_larger_node |= new_range != old_range;
15272 Selection {
15273 id: selection.id,
15274 start: new_range.start,
15275 end: new_range.end,
15276 goal: SelectionGoal::None,
15277 reversed: selection.reversed,
15278 }
15279 })
15280 .collect::<Vec<_>>();
15281
15282 if !selected_larger_node {
15283 return; // don't put this call in the history
15284 }
15285
15286 // scroll based on transformation done to the last selection created by the user
15287 let (last_old, last_new) = old_selections
15288 .last()
15289 .zip(new_selections.last().cloned())
15290 .expect("old_selections isn't empty");
15291
15292 // revert selection
15293 let is_selection_reversed = {
15294 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15295 new_selections.last_mut().expect("checked above").reversed =
15296 should_newest_selection_be_reversed;
15297 should_newest_selection_be_reversed
15298 };
15299
15300 if selected_larger_node {
15301 self.select_syntax_node_history.disable_clearing = true;
15302 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15303 s.select(new_selections.clone());
15304 });
15305 self.select_syntax_node_history.disable_clearing = false;
15306 }
15307
15308 let start_row = last_new.start.to_display_point(&display_map).row().0;
15309 let end_row = last_new.end.to_display_point(&display_map).row().0;
15310 let selection_height = end_row - start_row + 1;
15311 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15312
15313 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15314 let scroll_behavior = if fits_on_the_screen {
15315 self.request_autoscroll(Autoscroll::fit(), cx);
15316 SelectSyntaxNodeScrollBehavior::FitSelection
15317 } else if is_selection_reversed {
15318 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15319 SelectSyntaxNodeScrollBehavior::CursorTop
15320 } else {
15321 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15322 SelectSyntaxNodeScrollBehavior::CursorBottom
15323 };
15324
15325 self.select_syntax_node_history.push((
15326 old_selections,
15327 scroll_behavior,
15328 is_selection_reversed,
15329 ));
15330 }
15331
15332 pub fn select_smaller_syntax_node(
15333 &mut self,
15334 _: &SelectSmallerSyntaxNode,
15335 window: &mut Window,
15336 cx: &mut Context<Self>,
15337 ) {
15338 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15339
15340 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15341 self.select_syntax_node_history.pop()
15342 {
15343 if let Some(selection) = selections.last_mut() {
15344 selection.reversed = is_selection_reversed;
15345 }
15346
15347 self.select_syntax_node_history.disable_clearing = true;
15348 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15349 s.select(selections.to_vec());
15350 });
15351 self.select_syntax_node_history.disable_clearing = false;
15352
15353 match scroll_behavior {
15354 SelectSyntaxNodeScrollBehavior::CursorTop => {
15355 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15356 }
15357 SelectSyntaxNodeScrollBehavior::FitSelection => {
15358 self.request_autoscroll(Autoscroll::fit(), cx);
15359 }
15360 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15361 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15362 }
15363 }
15364 }
15365 }
15366
15367 pub fn unwrap_syntax_node(
15368 &mut self,
15369 _: &UnwrapSyntaxNode,
15370 window: &mut Window,
15371 cx: &mut Context<Self>,
15372 ) {
15373 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15374
15375 let buffer = self.buffer.read(cx).snapshot(cx);
15376 let selections = self
15377 .selections
15378 .all::<usize>(cx)
15379 .into_iter()
15380 // subtracting the offset requires sorting
15381 .sorted_by_key(|i| i.start);
15382
15383 let full_edits = selections
15384 .into_iter()
15385 .filter_map(|selection| {
15386 let child = if selection.is_empty()
15387 && let Some((_, ancestor_range)) =
15388 buffer.syntax_ancestor(selection.start..selection.end)
15389 {
15390 ancestor_range
15391 } else {
15392 selection.range()
15393 };
15394
15395 let mut parent = child.clone();
15396 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15397 parent = ancestor_range;
15398 if parent.start < child.start || parent.end > child.end {
15399 break;
15400 }
15401 }
15402
15403 if parent == child {
15404 return None;
15405 }
15406 let text = buffer.text_for_range(child).collect::<String>();
15407 Some((selection.id, parent, text))
15408 })
15409 .collect::<Vec<_>>();
15410 if full_edits.is_empty() {
15411 return;
15412 }
15413
15414 self.transact(window, cx, |this, window, cx| {
15415 this.buffer.update(cx, |buffer, cx| {
15416 buffer.edit(
15417 full_edits
15418 .iter()
15419 .map(|(_, p, t)| (p.clone(), t.clone()))
15420 .collect::<Vec<_>>(),
15421 None,
15422 cx,
15423 );
15424 });
15425 this.change_selections(Default::default(), window, cx, |s| {
15426 let mut offset = 0;
15427 let mut selections = vec![];
15428 for (id, parent, text) in full_edits {
15429 let start = parent.start - offset;
15430 offset += parent.len() - text.len();
15431 selections.push(Selection {
15432 id,
15433 start,
15434 end: start + text.len(),
15435 reversed: false,
15436 goal: Default::default(),
15437 });
15438 }
15439 s.select(selections);
15440 });
15441 });
15442 }
15443
15444 pub fn select_next_syntax_node(
15445 &mut self,
15446 _: &SelectNextSyntaxNode,
15447 window: &mut Window,
15448 cx: &mut Context<Self>,
15449 ) {
15450 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15451 if old_selections.is_empty() {
15452 return;
15453 }
15454
15455 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15456
15457 let buffer = self.buffer.read(cx).snapshot(cx);
15458 let mut selected_sibling = false;
15459
15460 let new_selections = old_selections
15461 .iter()
15462 .map(|selection| {
15463 let old_range = selection.start..selection.end;
15464
15465 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15466 let new_range = node.byte_range();
15467 selected_sibling = true;
15468 Selection {
15469 id: selection.id,
15470 start: new_range.start,
15471 end: new_range.end,
15472 goal: SelectionGoal::None,
15473 reversed: selection.reversed,
15474 }
15475 } else {
15476 selection.clone()
15477 }
15478 })
15479 .collect::<Vec<_>>();
15480
15481 if selected_sibling {
15482 self.change_selections(
15483 SelectionEffects::scroll(Autoscroll::fit()),
15484 window,
15485 cx,
15486 |s| {
15487 s.select(new_selections);
15488 },
15489 );
15490 }
15491 }
15492
15493 pub fn select_prev_syntax_node(
15494 &mut self,
15495 _: &SelectPreviousSyntaxNode,
15496 window: &mut Window,
15497 cx: &mut Context<Self>,
15498 ) {
15499 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15500 if old_selections.is_empty() {
15501 return;
15502 }
15503
15504 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15505
15506 let buffer = self.buffer.read(cx).snapshot(cx);
15507 let mut selected_sibling = false;
15508
15509 let new_selections = old_selections
15510 .iter()
15511 .map(|selection| {
15512 let old_range = selection.start..selection.end;
15513
15514 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15515 let new_range = node.byte_range();
15516 selected_sibling = true;
15517 Selection {
15518 id: selection.id,
15519 start: new_range.start,
15520 end: new_range.end,
15521 goal: SelectionGoal::None,
15522 reversed: selection.reversed,
15523 }
15524 } else {
15525 selection.clone()
15526 }
15527 })
15528 .collect::<Vec<_>>();
15529
15530 if selected_sibling {
15531 self.change_selections(
15532 SelectionEffects::scroll(Autoscroll::fit()),
15533 window,
15534 cx,
15535 |s| {
15536 s.select(new_selections);
15537 },
15538 );
15539 }
15540 }
15541
15542 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15543 if !EditorSettings::get_global(cx).gutter.runnables {
15544 self.clear_tasks();
15545 return Task::ready(());
15546 }
15547 let project = self.project().map(Entity::downgrade);
15548 let task_sources = self.lsp_task_sources(cx);
15549 let multi_buffer = self.buffer.downgrade();
15550 cx.spawn_in(window, async move |editor, cx| {
15551 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15552 let Some(project) = project.and_then(|p| p.upgrade()) else {
15553 return;
15554 };
15555 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15556 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15557 }) else {
15558 return;
15559 };
15560
15561 let hide_runnables = project
15562 .update(cx, |project, _| project.is_via_collab())
15563 .unwrap_or(true);
15564 if hide_runnables {
15565 return;
15566 }
15567 let new_rows =
15568 cx.background_spawn({
15569 let snapshot = display_snapshot.clone();
15570 async move {
15571 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15572 }
15573 })
15574 .await;
15575 let Ok(lsp_tasks) =
15576 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15577 else {
15578 return;
15579 };
15580 let lsp_tasks = lsp_tasks.await;
15581
15582 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15583 lsp_tasks
15584 .into_iter()
15585 .flat_map(|(kind, tasks)| {
15586 tasks.into_iter().filter_map(move |(location, task)| {
15587 Some((kind.clone(), location?, task))
15588 })
15589 })
15590 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15591 let buffer = location.target.buffer;
15592 let buffer_snapshot = buffer.read(cx).snapshot();
15593 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15594 |(excerpt_id, snapshot, _)| {
15595 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15596 display_snapshot
15597 .buffer_snapshot()
15598 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15599 } else {
15600 None
15601 }
15602 },
15603 );
15604 if let Some(offset) = offset {
15605 let task_buffer_range =
15606 location.target.range.to_point(&buffer_snapshot);
15607 let context_buffer_range =
15608 task_buffer_range.to_offset(&buffer_snapshot);
15609 let context_range = BufferOffset(context_buffer_range.start)
15610 ..BufferOffset(context_buffer_range.end);
15611
15612 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15613 .or_insert_with(|| RunnableTasks {
15614 templates: Vec::new(),
15615 offset,
15616 column: task_buffer_range.start.column,
15617 extra_variables: HashMap::default(),
15618 context_range,
15619 })
15620 .templates
15621 .push((kind, task.original_task().clone()));
15622 }
15623
15624 acc
15625 })
15626 }) else {
15627 return;
15628 };
15629
15630 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15631 buffer.language_settings(cx).tasks.prefer_lsp
15632 }) else {
15633 return;
15634 };
15635
15636 let rows = Self::runnable_rows(
15637 project,
15638 display_snapshot,
15639 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15640 new_rows,
15641 cx.clone(),
15642 )
15643 .await;
15644 editor
15645 .update(cx, |editor, _| {
15646 editor.clear_tasks();
15647 for (key, mut value) in rows {
15648 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15649 value.templates.extend(lsp_tasks.templates);
15650 }
15651
15652 editor.insert_tasks(key, value);
15653 }
15654 for (key, value) in lsp_tasks_by_rows {
15655 editor.insert_tasks(key, value);
15656 }
15657 })
15658 .ok();
15659 })
15660 }
15661 fn fetch_runnable_ranges(
15662 snapshot: &DisplaySnapshot,
15663 range: Range<Anchor>,
15664 ) -> Vec<language::RunnableRange> {
15665 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15666 }
15667
15668 fn runnable_rows(
15669 project: Entity<Project>,
15670 snapshot: DisplaySnapshot,
15671 prefer_lsp: bool,
15672 runnable_ranges: Vec<RunnableRange>,
15673 cx: AsyncWindowContext,
15674 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15675 cx.spawn(async move |cx| {
15676 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15677 for mut runnable in runnable_ranges {
15678 let Some(tasks) = cx
15679 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15680 .ok()
15681 else {
15682 continue;
15683 };
15684 let mut tasks = tasks.await;
15685
15686 if prefer_lsp {
15687 tasks.retain(|(task_kind, _)| {
15688 !matches!(task_kind, TaskSourceKind::Language { .. })
15689 });
15690 }
15691 if tasks.is_empty() {
15692 continue;
15693 }
15694
15695 let point = runnable
15696 .run_range
15697 .start
15698 .to_point(&snapshot.buffer_snapshot());
15699 let Some(row) = snapshot
15700 .buffer_snapshot()
15701 .buffer_line_for_row(MultiBufferRow(point.row))
15702 .map(|(_, range)| range.start.row)
15703 else {
15704 continue;
15705 };
15706
15707 let context_range =
15708 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15709 runnable_rows.push((
15710 (runnable.buffer_id, row),
15711 RunnableTasks {
15712 templates: tasks,
15713 offset: snapshot
15714 .buffer_snapshot()
15715 .anchor_before(runnable.run_range.start),
15716 context_range,
15717 column: point.column,
15718 extra_variables: runnable.extra_captures,
15719 },
15720 ));
15721 }
15722 runnable_rows
15723 })
15724 }
15725
15726 fn templates_with_tags(
15727 project: &Entity<Project>,
15728 runnable: &mut Runnable,
15729 cx: &mut App,
15730 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15731 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15732 let (worktree_id, file) = project
15733 .buffer_for_id(runnable.buffer, cx)
15734 .and_then(|buffer| buffer.read(cx).file())
15735 .map(|file| (file.worktree_id(cx), file.clone()))
15736 .unzip();
15737
15738 (
15739 project.task_store().read(cx).task_inventory().cloned(),
15740 worktree_id,
15741 file,
15742 )
15743 });
15744
15745 let tags = mem::take(&mut runnable.tags);
15746 let language = runnable.language.clone();
15747 cx.spawn(async move |cx| {
15748 let mut templates_with_tags = Vec::new();
15749 if let Some(inventory) = inventory {
15750 for RunnableTag(tag) in tags {
15751 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15752 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15753 }) else {
15754 return templates_with_tags;
15755 };
15756 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15757 move |(_, template)| {
15758 template.tags.iter().any(|source_tag| source_tag == &tag)
15759 },
15760 ));
15761 }
15762 }
15763 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15764
15765 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15766 // Strongest source wins; if we have worktree tag binding, prefer that to
15767 // global and language bindings;
15768 // if we have a global binding, prefer that to language binding.
15769 let first_mismatch = templates_with_tags
15770 .iter()
15771 .position(|(tag_source, _)| tag_source != leading_tag_source);
15772 if let Some(index) = first_mismatch {
15773 templates_with_tags.truncate(index);
15774 }
15775 }
15776
15777 templates_with_tags
15778 })
15779 }
15780
15781 pub fn move_to_enclosing_bracket(
15782 &mut self,
15783 _: &MoveToEnclosingBracket,
15784 window: &mut Window,
15785 cx: &mut Context<Self>,
15786 ) {
15787 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15788 self.change_selections(Default::default(), window, cx, |s| {
15789 s.move_offsets_with(|snapshot, selection| {
15790 let Some(enclosing_bracket_ranges) =
15791 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15792 else {
15793 return;
15794 };
15795
15796 let mut best_length = usize::MAX;
15797 let mut best_inside = false;
15798 let mut best_in_bracket_range = false;
15799 let mut best_destination = None;
15800 for (open, close) in enclosing_bracket_ranges {
15801 let close = close.to_inclusive();
15802 let length = close.end() - open.start;
15803 let inside = selection.start >= open.end && selection.end <= *close.start();
15804 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15805 || close.contains(&selection.head());
15806
15807 // If best is next to a bracket and current isn't, skip
15808 if !in_bracket_range && best_in_bracket_range {
15809 continue;
15810 }
15811
15812 // Prefer smaller lengths unless best is inside and current isn't
15813 if length > best_length && (best_inside || !inside) {
15814 continue;
15815 }
15816
15817 best_length = length;
15818 best_inside = inside;
15819 best_in_bracket_range = in_bracket_range;
15820 best_destination = Some(
15821 if close.contains(&selection.start) && close.contains(&selection.end) {
15822 if inside { open.end } else { open.start }
15823 } else if inside {
15824 *close.start()
15825 } else {
15826 *close.end()
15827 },
15828 );
15829 }
15830
15831 if let Some(destination) = best_destination {
15832 selection.collapse_to(destination, SelectionGoal::None);
15833 }
15834 })
15835 });
15836 }
15837
15838 pub fn undo_selection(
15839 &mut self,
15840 _: &UndoSelection,
15841 window: &mut Window,
15842 cx: &mut Context<Self>,
15843 ) {
15844 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15845 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15846 self.selection_history.mode = SelectionHistoryMode::Undoing;
15847 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15848 this.end_selection(window, cx);
15849 this.change_selections(
15850 SelectionEffects::scroll(Autoscroll::newest()),
15851 window,
15852 cx,
15853 |s| s.select_anchors(entry.selections.to_vec()),
15854 );
15855 });
15856 self.selection_history.mode = SelectionHistoryMode::Normal;
15857
15858 self.select_next_state = entry.select_next_state;
15859 self.select_prev_state = entry.select_prev_state;
15860 self.add_selections_state = entry.add_selections_state;
15861 }
15862 }
15863
15864 pub fn redo_selection(
15865 &mut self,
15866 _: &RedoSelection,
15867 window: &mut Window,
15868 cx: &mut Context<Self>,
15869 ) {
15870 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15871 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15872 self.selection_history.mode = SelectionHistoryMode::Redoing;
15873 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15874 this.end_selection(window, cx);
15875 this.change_selections(
15876 SelectionEffects::scroll(Autoscroll::newest()),
15877 window,
15878 cx,
15879 |s| s.select_anchors(entry.selections.to_vec()),
15880 );
15881 });
15882 self.selection_history.mode = SelectionHistoryMode::Normal;
15883
15884 self.select_next_state = entry.select_next_state;
15885 self.select_prev_state = entry.select_prev_state;
15886 self.add_selections_state = entry.add_selections_state;
15887 }
15888 }
15889
15890 pub fn expand_excerpts(
15891 &mut self,
15892 action: &ExpandExcerpts,
15893 _: &mut Window,
15894 cx: &mut Context<Self>,
15895 ) {
15896 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15897 }
15898
15899 pub fn expand_excerpts_down(
15900 &mut self,
15901 action: &ExpandExcerptsDown,
15902 _: &mut Window,
15903 cx: &mut Context<Self>,
15904 ) {
15905 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15906 }
15907
15908 pub fn expand_excerpts_up(
15909 &mut self,
15910 action: &ExpandExcerptsUp,
15911 _: &mut Window,
15912 cx: &mut Context<Self>,
15913 ) {
15914 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15915 }
15916
15917 pub fn expand_excerpts_for_direction(
15918 &mut self,
15919 lines: u32,
15920 direction: ExpandExcerptDirection,
15921
15922 cx: &mut Context<Self>,
15923 ) {
15924 let selections = self.selections.disjoint_anchors_arc();
15925
15926 let lines = if lines == 0 {
15927 EditorSettings::get_global(cx).expand_excerpt_lines
15928 } else {
15929 lines
15930 };
15931
15932 self.buffer.update(cx, |buffer, cx| {
15933 let snapshot = buffer.snapshot(cx);
15934 let mut excerpt_ids = selections
15935 .iter()
15936 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15937 .collect::<Vec<_>>();
15938 excerpt_ids.sort();
15939 excerpt_ids.dedup();
15940 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15941 })
15942 }
15943
15944 pub fn expand_excerpt(
15945 &mut self,
15946 excerpt: ExcerptId,
15947 direction: ExpandExcerptDirection,
15948 window: &mut Window,
15949 cx: &mut Context<Self>,
15950 ) {
15951 let current_scroll_position = self.scroll_position(cx);
15952 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15953 let mut should_scroll_up = false;
15954
15955 if direction == ExpandExcerptDirection::Down {
15956 let multi_buffer = self.buffer.read(cx);
15957 let snapshot = multi_buffer.snapshot(cx);
15958 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15959 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15960 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
15961 {
15962 let buffer_snapshot = buffer.read(cx).snapshot();
15963 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15964 let last_row = buffer_snapshot.max_point().row;
15965 let lines_below = last_row.saturating_sub(excerpt_end_row);
15966 should_scroll_up = lines_below >= lines_to_expand;
15967 }
15968 }
15969
15970 self.buffer.update(cx, |buffer, cx| {
15971 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15972 });
15973
15974 if should_scroll_up {
15975 let new_scroll_position =
15976 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as ScrollOffset);
15977 self.set_scroll_position(new_scroll_position, window, cx);
15978 }
15979 }
15980
15981 pub fn go_to_singleton_buffer_point(
15982 &mut self,
15983 point: Point,
15984 window: &mut Window,
15985 cx: &mut Context<Self>,
15986 ) {
15987 self.go_to_singleton_buffer_range(point..point, window, cx);
15988 }
15989
15990 pub fn go_to_singleton_buffer_range(
15991 &mut self,
15992 range: Range<Point>,
15993 window: &mut Window,
15994 cx: &mut Context<Self>,
15995 ) {
15996 let multibuffer = self.buffer().read(cx);
15997 let Some(buffer) = multibuffer.as_singleton() else {
15998 return;
15999 };
16000 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16001 return;
16002 };
16003 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16004 return;
16005 };
16006 self.change_selections(
16007 SelectionEffects::default().nav_history(true),
16008 window,
16009 cx,
16010 |s| s.select_anchor_ranges([start..end]),
16011 );
16012 }
16013
16014 pub fn go_to_diagnostic(
16015 &mut self,
16016 action: &GoToDiagnostic,
16017 window: &mut Window,
16018 cx: &mut Context<Self>,
16019 ) {
16020 if !self.diagnostics_enabled() {
16021 return;
16022 }
16023 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16024 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16025 }
16026
16027 pub fn go_to_prev_diagnostic(
16028 &mut self,
16029 action: &GoToPreviousDiagnostic,
16030 window: &mut Window,
16031 cx: &mut Context<Self>,
16032 ) {
16033 if !self.diagnostics_enabled() {
16034 return;
16035 }
16036 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16037 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16038 }
16039
16040 pub fn go_to_diagnostic_impl(
16041 &mut self,
16042 direction: Direction,
16043 severity: GoToDiagnosticSeverityFilter,
16044 window: &mut Window,
16045 cx: &mut Context<Self>,
16046 ) {
16047 let buffer = self.buffer.read(cx).snapshot(cx);
16048 let selection = self.selections.newest::<usize>(cx);
16049
16050 let mut active_group_id = None;
16051 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16052 && active_group.active_range.start.to_offset(&buffer) == selection.start
16053 {
16054 active_group_id = Some(active_group.group_id);
16055 }
16056
16057 fn filtered<'a>(
16058 snapshot: EditorSnapshot,
16059 severity: GoToDiagnosticSeverityFilter,
16060 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
16061 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
16062 diagnostics
16063 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16064 .filter(|entry| entry.range.start != entry.range.end)
16065 .filter(|entry| !entry.diagnostic.is_unnecessary)
16066 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
16067 }
16068
16069 let snapshot = self.snapshot(window, cx);
16070 let before = filtered(
16071 snapshot.clone(),
16072 severity,
16073 buffer
16074 .diagnostics_in_range(0..selection.start)
16075 .filter(|entry| entry.range.start <= selection.start),
16076 );
16077 let after = filtered(
16078 snapshot,
16079 severity,
16080 buffer
16081 .diagnostics_in_range(selection.start..buffer.len())
16082 .filter(|entry| entry.range.start >= selection.start),
16083 );
16084
16085 let mut found: Option<DiagnosticEntryRef<usize>> = None;
16086 if direction == Direction::Prev {
16087 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16088 {
16089 for diagnostic in prev_diagnostics.into_iter().rev() {
16090 if diagnostic.range.start != selection.start
16091 || active_group_id
16092 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16093 {
16094 found = Some(diagnostic);
16095 break 'outer;
16096 }
16097 }
16098 }
16099 } else {
16100 for diagnostic in after.chain(before) {
16101 if diagnostic.range.start != selection.start
16102 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16103 {
16104 found = Some(diagnostic);
16105 break;
16106 }
16107 }
16108 }
16109 let Some(next_diagnostic) = found else {
16110 return;
16111 };
16112
16113 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16114 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16115 return;
16116 };
16117 self.change_selections(Default::default(), window, cx, |s| {
16118 s.select_ranges(vec![
16119 next_diagnostic.range.start..next_diagnostic.range.start,
16120 ])
16121 });
16122 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16123 self.refresh_edit_prediction(false, true, window, cx);
16124 }
16125
16126 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16127 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16128 let snapshot = self.snapshot(window, cx);
16129 let selection = self.selections.newest::<Point>(cx);
16130 self.go_to_hunk_before_or_after_position(
16131 &snapshot,
16132 selection.head(),
16133 Direction::Next,
16134 window,
16135 cx,
16136 );
16137 }
16138
16139 pub fn go_to_hunk_before_or_after_position(
16140 &mut self,
16141 snapshot: &EditorSnapshot,
16142 position: Point,
16143 direction: Direction,
16144 window: &mut Window,
16145 cx: &mut Context<Editor>,
16146 ) {
16147 let row = if direction == Direction::Next {
16148 self.hunk_after_position(snapshot, position)
16149 .map(|hunk| hunk.row_range.start)
16150 } else {
16151 self.hunk_before_position(snapshot, position)
16152 };
16153
16154 if let Some(row) = row {
16155 let destination = Point::new(row.0, 0);
16156 let autoscroll = Autoscroll::center();
16157
16158 self.unfold_ranges(&[destination..destination], false, false, cx);
16159 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16160 s.select_ranges([destination..destination]);
16161 });
16162 }
16163 }
16164
16165 fn hunk_after_position(
16166 &mut self,
16167 snapshot: &EditorSnapshot,
16168 position: Point,
16169 ) -> Option<MultiBufferDiffHunk> {
16170 snapshot
16171 .buffer_snapshot()
16172 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16173 .find(|hunk| hunk.row_range.start.0 > position.row)
16174 .or_else(|| {
16175 snapshot
16176 .buffer_snapshot()
16177 .diff_hunks_in_range(Point::zero()..position)
16178 .find(|hunk| hunk.row_range.end.0 < position.row)
16179 })
16180 }
16181
16182 fn go_to_prev_hunk(
16183 &mut self,
16184 _: &GoToPreviousHunk,
16185 window: &mut Window,
16186 cx: &mut Context<Self>,
16187 ) {
16188 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16189 let snapshot = self.snapshot(window, cx);
16190 let selection = self.selections.newest::<Point>(cx);
16191 self.go_to_hunk_before_or_after_position(
16192 &snapshot,
16193 selection.head(),
16194 Direction::Prev,
16195 window,
16196 cx,
16197 );
16198 }
16199
16200 fn hunk_before_position(
16201 &mut self,
16202 snapshot: &EditorSnapshot,
16203 position: Point,
16204 ) -> Option<MultiBufferRow> {
16205 snapshot
16206 .buffer_snapshot()
16207 .diff_hunk_before(position)
16208 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16209 }
16210
16211 fn go_to_next_change(
16212 &mut self,
16213 _: &GoToNextChange,
16214 window: &mut Window,
16215 cx: &mut Context<Self>,
16216 ) {
16217 if let Some(selections) = self
16218 .change_list
16219 .next_change(1, Direction::Next)
16220 .map(|s| s.to_vec())
16221 {
16222 self.change_selections(Default::default(), window, cx, |s| {
16223 let map = s.display_map();
16224 s.select_display_ranges(selections.iter().map(|a| {
16225 let point = a.to_display_point(&map);
16226 point..point
16227 }))
16228 })
16229 }
16230 }
16231
16232 fn go_to_previous_change(
16233 &mut self,
16234 _: &GoToPreviousChange,
16235 window: &mut Window,
16236 cx: &mut Context<Self>,
16237 ) {
16238 if let Some(selections) = self
16239 .change_list
16240 .next_change(1, Direction::Prev)
16241 .map(|s| s.to_vec())
16242 {
16243 self.change_selections(Default::default(), window, cx, |s| {
16244 let map = s.display_map();
16245 s.select_display_ranges(selections.iter().map(|a| {
16246 let point = a.to_display_point(&map);
16247 point..point
16248 }))
16249 })
16250 }
16251 }
16252
16253 pub fn go_to_next_document_highlight(
16254 &mut self,
16255 _: &GoToNextDocumentHighlight,
16256 window: &mut Window,
16257 cx: &mut Context<Self>,
16258 ) {
16259 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16260 }
16261
16262 pub fn go_to_prev_document_highlight(
16263 &mut self,
16264 _: &GoToPreviousDocumentHighlight,
16265 window: &mut Window,
16266 cx: &mut Context<Self>,
16267 ) {
16268 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16269 }
16270
16271 pub fn go_to_document_highlight_before_or_after_position(
16272 &mut self,
16273 direction: Direction,
16274 window: &mut Window,
16275 cx: &mut Context<Editor>,
16276 ) {
16277 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16278 let snapshot = self.snapshot(window, cx);
16279 let buffer = &snapshot.buffer_snapshot();
16280 let position = self.selections.newest::<Point>(cx).head();
16281 let anchor_position = buffer.anchor_after(position);
16282
16283 // Get all document highlights (both read and write)
16284 let mut all_highlights = Vec::new();
16285
16286 if let Some((_, read_highlights)) = self
16287 .background_highlights
16288 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16289 {
16290 all_highlights.extend(read_highlights.iter());
16291 }
16292
16293 if let Some((_, write_highlights)) = self
16294 .background_highlights
16295 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16296 {
16297 all_highlights.extend(write_highlights.iter());
16298 }
16299
16300 if all_highlights.is_empty() {
16301 return;
16302 }
16303
16304 // Sort highlights by position
16305 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16306
16307 let target_highlight = match direction {
16308 Direction::Next => {
16309 // Find the first highlight after the current position
16310 all_highlights
16311 .iter()
16312 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16313 }
16314 Direction::Prev => {
16315 // Find the last highlight before the current position
16316 all_highlights
16317 .iter()
16318 .rev()
16319 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16320 }
16321 };
16322
16323 if let Some(highlight) = target_highlight {
16324 let destination = highlight.start.to_point(buffer);
16325 let autoscroll = Autoscroll::center();
16326
16327 self.unfold_ranges(&[destination..destination], false, false, cx);
16328 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16329 s.select_ranges([destination..destination]);
16330 });
16331 }
16332 }
16333
16334 fn go_to_line<T: 'static>(
16335 &mut self,
16336 position: Anchor,
16337 highlight_color: Option<Hsla>,
16338 window: &mut Window,
16339 cx: &mut Context<Self>,
16340 ) {
16341 let snapshot = self.snapshot(window, cx).display_snapshot;
16342 let position = position.to_point(&snapshot.buffer_snapshot());
16343 let start = snapshot
16344 .buffer_snapshot()
16345 .clip_point(Point::new(position.row, 0), Bias::Left);
16346 let end = start + Point::new(1, 0);
16347 let start = snapshot.buffer_snapshot().anchor_before(start);
16348 let end = snapshot.buffer_snapshot().anchor_before(end);
16349
16350 self.highlight_rows::<T>(
16351 start..end,
16352 highlight_color
16353 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16354 Default::default(),
16355 cx,
16356 );
16357
16358 if self.buffer.read(cx).is_singleton() {
16359 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16360 }
16361 }
16362
16363 pub fn go_to_definition(
16364 &mut self,
16365 _: &GoToDefinition,
16366 window: &mut Window,
16367 cx: &mut Context<Self>,
16368 ) -> Task<Result<Navigated>> {
16369 let definition =
16370 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16371 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16372 cx.spawn_in(window, async move |editor, cx| {
16373 if definition.await? == Navigated::Yes {
16374 return Ok(Navigated::Yes);
16375 }
16376 match fallback_strategy {
16377 GoToDefinitionFallback::None => Ok(Navigated::No),
16378 GoToDefinitionFallback::FindAllReferences => {
16379 match editor.update_in(cx, |editor, window, cx| {
16380 editor.find_all_references(&FindAllReferences, window, cx)
16381 })? {
16382 Some(references) => references.await,
16383 None => Ok(Navigated::No),
16384 }
16385 }
16386 }
16387 })
16388 }
16389
16390 pub fn go_to_declaration(
16391 &mut self,
16392 _: &GoToDeclaration,
16393 window: &mut Window,
16394 cx: &mut Context<Self>,
16395 ) -> Task<Result<Navigated>> {
16396 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16397 }
16398
16399 pub fn go_to_declaration_split(
16400 &mut self,
16401 _: &GoToDeclaration,
16402 window: &mut Window,
16403 cx: &mut Context<Self>,
16404 ) -> Task<Result<Navigated>> {
16405 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16406 }
16407
16408 pub fn go_to_implementation(
16409 &mut self,
16410 _: &GoToImplementation,
16411 window: &mut Window,
16412 cx: &mut Context<Self>,
16413 ) -> Task<Result<Navigated>> {
16414 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16415 }
16416
16417 pub fn go_to_implementation_split(
16418 &mut self,
16419 _: &GoToImplementationSplit,
16420 window: &mut Window,
16421 cx: &mut Context<Self>,
16422 ) -> Task<Result<Navigated>> {
16423 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16424 }
16425
16426 pub fn go_to_type_definition(
16427 &mut self,
16428 _: &GoToTypeDefinition,
16429 window: &mut Window,
16430 cx: &mut Context<Self>,
16431 ) -> Task<Result<Navigated>> {
16432 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16433 }
16434
16435 pub fn go_to_definition_split(
16436 &mut self,
16437 _: &GoToDefinitionSplit,
16438 window: &mut Window,
16439 cx: &mut Context<Self>,
16440 ) -> Task<Result<Navigated>> {
16441 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16442 }
16443
16444 pub fn go_to_type_definition_split(
16445 &mut self,
16446 _: &GoToTypeDefinitionSplit,
16447 window: &mut Window,
16448 cx: &mut Context<Self>,
16449 ) -> Task<Result<Navigated>> {
16450 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16451 }
16452
16453 fn go_to_definition_of_kind(
16454 &mut self,
16455 kind: GotoDefinitionKind,
16456 split: bool,
16457 window: &mut Window,
16458 cx: &mut Context<Self>,
16459 ) -> Task<Result<Navigated>> {
16460 let Some(provider) = self.semantics_provider.clone() else {
16461 return Task::ready(Ok(Navigated::No));
16462 };
16463 let head = self.selections.newest::<usize>(cx).head();
16464 let buffer = self.buffer.read(cx);
16465 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16466 return Task::ready(Ok(Navigated::No));
16467 };
16468 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16469 return Task::ready(Ok(Navigated::No));
16470 };
16471
16472 cx.spawn_in(window, async move |editor, cx| {
16473 let Some(definitions) = definitions.await? else {
16474 return Ok(Navigated::No);
16475 };
16476 let navigated = editor
16477 .update_in(cx, |editor, window, cx| {
16478 editor.navigate_to_hover_links(
16479 Some(kind),
16480 definitions
16481 .into_iter()
16482 .filter(|location| {
16483 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16484 })
16485 .map(HoverLink::Text)
16486 .collect::<Vec<_>>(),
16487 split,
16488 window,
16489 cx,
16490 )
16491 })?
16492 .await?;
16493 anyhow::Ok(navigated)
16494 })
16495 }
16496
16497 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16498 let selection = self.selections.newest_anchor();
16499 let head = selection.head();
16500 let tail = selection.tail();
16501
16502 let Some((buffer, start_position)) =
16503 self.buffer.read(cx).text_anchor_for_position(head, cx)
16504 else {
16505 return;
16506 };
16507
16508 let end_position = if head != tail {
16509 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16510 return;
16511 };
16512 Some(pos)
16513 } else {
16514 None
16515 };
16516
16517 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16518 let url = if let Some(end_pos) = end_position {
16519 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16520 } else {
16521 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16522 };
16523
16524 if let Some(url) = url {
16525 cx.update(|window, cx| {
16526 if parse_zed_link(&url, cx).is_some() {
16527 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16528 } else {
16529 cx.open_url(&url);
16530 }
16531 })?;
16532 }
16533
16534 anyhow::Ok(())
16535 });
16536
16537 url_finder.detach();
16538 }
16539
16540 pub fn open_selected_filename(
16541 &mut self,
16542 _: &OpenSelectedFilename,
16543 window: &mut Window,
16544 cx: &mut Context<Self>,
16545 ) {
16546 let Some(workspace) = self.workspace() else {
16547 return;
16548 };
16549
16550 let position = self.selections.newest_anchor().head();
16551
16552 let Some((buffer, buffer_position)) =
16553 self.buffer.read(cx).text_anchor_for_position(position, cx)
16554 else {
16555 return;
16556 };
16557
16558 let project = self.project.clone();
16559
16560 cx.spawn_in(window, async move |_, cx| {
16561 let result = find_file(&buffer, project, buffer_position, cx).await;
16562
16563 if let Some((_, path)) = result {
16564 workspace
16565 .update_in(cx, |workspace, window, cx| {
16566 workspace.open_resolved_path(path, window, cx)
16567 })?
16568 .await?;
16569 }
16570 anyhow::Ok(())
16571 })
16572 .detach();
16573 }
16574
16575 pub(crate) fn navigate_to_hover_links(
16576 &mut self,
16577 kind: Option<GotoDefinitionKind>,
16578 definitions: Vec<HoverLink>,
16579 split: bool,
16580 window: &mut Window,
16581 cx: &mut Context<Editor>,
16582 ) -> Task<Result<Navigated>> {
16583 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16584 let mut first_url_or_file = None;
16585 let definitions: Vec<_> = definitions
16586 .into_iter()
16587 .filter_map(|def| match def {
16588 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16589 HoverLink::InlayHint(lsp_location, server_id) => {
16590 let computation =
16591 self.compute_target_location(lsp_location, server_id, window, cx);
16592 Some(cx.background_spawn(computation))
16593 }
16594 HoverLink::Url(url) => {
16595 first_url_or_file = Some(Either::Left(url));
16596 None
16597 }
16598 HoverLink::File(path) => {
16599 first_url_or_file = Some(Either::Right(path));
16600 None
16601 }
16602 })
16603 .collect();
16604
16605 let workspace = self.workspace();
16606
16607 cx.spawn_in(window, async move |editor, cx| {
16608 let locations: Vec<Location> = future::join_all(definitions)
16609 .await
16610 .into_iter()
16611 .filter_map(|location| location.transpose())
16612 .collect::<Result<_>>()
16613 .context("location tasks")?;
16614 let mut locations = cx.update(|_, cx| {
16615 locations
16616 .into_iter()
16617 .map(|location| {
16618 let buffer = location.buffer.read(cx);
16619 (location.buffer, location.range.to_point(buffer))
16620 })
16621 .into_group_map()
16622 })?;
16623 let mut num_locations = 0;
16624 for ranges in locations.values_mut() {
16625 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16626 ranges.dedup();
16627 num_locations += ranges.len();
16628 }
16629
16630 if num_locations > 1 {
16631 let Some(workspace) = workspace else {
16632 return Ok(Navigated::No);
16633 };
16634
16635 let tab_kind = match kind {
16636 Some(GotoDefinitionKind::Implementation) => "Implementations",
16637 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16638 Some(GotoDefinitionKind::Declaration) => "Declarations",
16639 Some(GotoDefinitionKind::Type) => "Types",
16640 };
16641 let title = editor
16642 .update_in(cx, |_, _, cx| {
16643 let target = locations
16644 .iter()
16645 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16646 .map(|(buffer, location)| {
16647 buffer
16648 .read(cx)
16649 .text_for_range(location.clone())
16650 .collect::<String>()
16651 })
16652 .filter(|text| !text.contains('\n'))
16653 .unique()
16654 .take(3)
16655 .join(", ");
16656 if target.is_empty() {
16657 tab_kind.to_owned()
16658 } else {
16659 format!("{tab_kind} for {target}")
16660 }
16661 })
16662 .context("buffer title")?;
16663
16664 let opened = workspace
16665 .update_in(cx, |workspace, window, cx| {
16666 Self::open_locations_in_multibuffer(
16667 workspace,
16668 locations,
16669 title,
16670 split,
16671 MultibufferSelectionMode::First,
16672 window,
16673 cx,
16674 )
16675 })
16676 .is_ok();
16677
16678 anyhow::Ok(Navigated::from_bool(opened))
16679 } else if num_locations == 0 {
16680 // If there is one url or file, open it directly
16681 match first_url_or_file {
16682 Some(Either::Left(url)) => {
16683 cx.update(|_, cx| cx.open_url(&url))?;
16684 Ok(Navigated::Yes)
16685 }
16686 Some(Either::Right(path)) => {
16687 let Some(workspace) = workspace else {
16688 return Ok(Navigated::No);
16689 };
16690
16691 workspace
16692 .update_in(cx, |workspace, window, cx| {
16693 workspace.open_resolved_path(path, window, cx)
16694 })?
16695 .await?;
16696 Ok(Navigated::Yes)
16697 }
16698 None => Ok(Navigated::No),
16699 }
16700 } else {
16701 let Some(workspace) = workspace else {
16702 return Ok(Navigated::No);
16703 };
16704
16705 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16706 let target_range = target_ranges.first().unwrap().clone();
16707
16708 editor.update_in(cx, |editor, window, cx| {
16709 let range = target_range.to_point(target_buffer.read(cx));
16710 let range = editor.range_for_match(&range);
16711 let range = collapse_multiline_range(range);
16712
16713 if !split
16714 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16715 {
16716 editor.go_to_singleton_buffer_range(range, window, cx);
16717 } else {
16718 let pane = workspace.read(cx).active_pane().clone();
16719 window.defer(cx, move |window, cx| {
16720 let target_editor: Entity<Self> =
16721 workspace.update(cx, |workspace, cx| {
16722 let pane = if split {
16723 workspace.adjacent_pane(window, cx)
16724 } else {
16725 workspace.active_pane().clone()
16726 };
16727
16728 workspace.open_project_item(
16729 pane,
16730 target_buffer.clone(),
16731 true,
16732 true,
16733 window,
16734 cx,
16735 )
16736 });
16737 target_editor.update(cx, |target_editor, cx| {
16738 // When selecting a definition in a different buffer, disable the nav history
16739 // to avoid creating a history entry at the previous cursor location.
16740 pane.update(cx, |pane, _| pane.disable_history());
16741 target_editor.go_to_singleton_buffer_range(range, window, cx);
16742 pane.update(cx, |pane, _| pane.enable_history());
16743 });
16744 });
16745 }
16746 Navigated::Yes
16747 })
16748 }
16749 })
16750 }
16751
16752 fn compute_target_location(
16753 &self,
16754 lsp_location: lsp::Location,
16755 server_id: LanguageServerId,
16756 window: &mut Window,
16757 cx: &mut Context<Self>,
16758 ) -> Task<anyhow::Result<Option<Location>>> {
16759 let Some(project) = self.project.clone() else {
16760 return Task::ready(Ok(None));
16761 };
16762
16763 cx.spawn_in(window, async move |editor, cx| {
16764 let location_task = editor.update(cx, |_, cx| {
16765 project.update(cx, |project, cx| {
16766 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16767 })
16768 })?;
16769 let location = Some({
16770 let target_buffer_handle = location_task.await.context("open local buffer")?;
16771 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16772 let target_start = target_buffer
16773 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16774 let target_end = target_buffer
16775 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16776 target_buffer.anchor_after(target_start)
16777 ..target_buffer.anchor_before(target_end)
16778 })?;
16779 Location {
16780 buffer: target_buffer_handle,
16781 range,
16782 }
16783 });
16784 Ok(location)
16785 })
16786 }
16787
16788 pub fn find_all_references(
16789 &mut self,
16790 _: &FindAllReferences,
16791 window: &mut Window,
16792 cx: &mut Context<Self>,
16793 ) -> Option<Task<Result<Navigated>>> {
16794 let selection = self.selections.newest::<usize>(cx);
16795 let multi_buffer = self.buffer.read(cx);
16796 let head = selection.head();
16797
16798 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16799 let head_anchor = multi_buffer_snapshot.anchor_at(
16800 head,
16801 if head < selection.tail() {
16802 Bias::Right
16803 } else {
16804 Bias::Left
16805 },
16806 );
16807
16808 match self
16809 .find_all_references_task_sources
16810 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16811 {
16812 Ok(_) => {
16813 log::info!(
16814 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16815 );
16816 return None;
16817 }
16818 Err(i) => {
16819 self.find_all_references_task_sources.insert(i, head_anchor);
16820 }
16821 }
16822
16823 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16824 let workspace = self.workspace()?;
16825 let project = workspace.read(cx).project().clone();
16826 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16827 Some(cx.spawn_in(window, async move |editor, cx| {
16828 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16829 if let Ok(i) = editor
16830 .find_all_references_task_sources
16831 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16832 {
16833 editor.find_all_references_task_sources.remove(i);
16834 }
16835 });
16836
16837 let Some(locations) = references.await? else {
16838 return anyhow::Ok(Navigated::No);
16839 };
16840 let mut locations = cx.update(|_, cx| {
16841 locations
16842 .into_iter()
16843 .map(|location| {
16844 let buffer = location.buffer.read(cx);
16845 (location.buffer, location.range.to_point(buffer))
16846 })
16847 .into_group_map()
16848 })?;
16849 if locations.is_empty() {
16850 return anyhow::Ok(Navigated::No);
16851 }
16852 for ranges in locations.values_mut() {
16853 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16854 ranges.dedup();
16855 }
16856
16857 workspace.update_in(cx, |workspace, window, cx| {
16858 let target = locations
16859 .iter()
16860 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16861 .map(|(buffer, location)| {
16862 buffer
16863 .read(cx)
16864 .text_for_range(location.clone())
16865 .collect::<String>()
16866 })
16867 .filter(|text| !text.contains('\n'))
16868 .unique()
16869 .take(3)
16870 .join(", ");
16871 let title = if target.is_empty() {
16872 "References".to_owned()
16873 } else {
16874 format!("References to {target}")
16875 };
16876 Self::open_locations_in_multibuffer(
16877 workspace,
16878 locations,
16879 title,
16880 false,
16881 MultibufferSelectionMode::First,
16882 window,
16883 cx,
16884 );
16885 Navigated::Yes
16886 })
16887 }))
16888 }
16889
16890 /// Opens a multibuffer with the given project locations in it
16891 pub fn open_locations_in_multibuffer(
16892 workspace: &mut Workspace,
16893 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
16894 title: String,
16895 split: bool,
16896 multibuffer_selection_mode: MultibufferSelectionMode,
16897 window: &mut Window,
16898 cx: &mut Context<Workspace>,
16899 ) {
16900 if locations.is_empty() {
16901 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16902 return;
16903 }
16904
16905 let capability = workspace.project().read(cx).capability();
16906 let mut ranges = <Vec<Range<Anchor>>>::new();
16907
16908 // a key to find existing multibuffer editors with the same set of locations
16909 // to prevent us from opening more and more multibuffer tabs for searches and the like
16910 let mut key = (title.clone(), vec![]);
16911 let excerpt_buffer = cx.new(|cx| {
16912 let key = &mut key.1;
16913 let mut multibuffer = MultiBuffer::new(capability);
16914 for (buffer, mut ranges_for_buffer) in locations {
16915 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16916 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
16917 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16918 PathKey::for_buffer(&buffer, cx),
16919 buffer.clone(),
16920 ranges_for_buffer,
16921 multibuffer_context_lines(cx),
16922 cx,
16923 );
16924 ranges.extend(new_ranges)
16925 }
16926
16927 multibuffer.with_title(title)
16928 });
16929 let existing = workspace.active_pane().update(cx, |pane, cx| {
16930 pane.items()
16931 .filter_map(|item| item.downcast::<Editor>())
16932 .find(|editor| {
16933 editor
16934 .read(cx)
16935 .lookup_key
16936 .as_ref()
16937 .and_then(|it| {
16938 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
16939 })
16940 .is_some_and(|it| *it == key)
16941 })
16942 });
16943 let editor = existing.unwrap_or_else(|| {
16944 cx.new(|cx| {
16945 let mut editor = Editor::for_multibuffer(
16946 excerpt_buffer,
16947 Some(workspace.project().clone()),
16948 window,
16949 cx,
16950 );
16951 editor.lookup_key = Some(Box::new(key));
16952 editor
16953 })
16954 });
16955 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
16956 MultibufferSelectionMode::First => {
16957 if let Some(first_range) = ranges.first() {
16958 editor.change_selections(
16959 SelectionEffects::no_scroll(),
16960 window,
16961 cx,
16962 |selections| {
16963 selections.clear_disjoint();
16964 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
16965 },
16966 );
16967 }
16968 editor.highlight_background::<Self>(
16969 &ranges,
16970 |theme| theme.colors().editor_highlighted_line_background,
16971 cx,
16972 );
16973 }
16974 MultibufferSelectionMode::All => {
16975 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
16976 selections.clear_disjoint();
16977 selections.select_anchor_ranges(ranges);
16978 });
16979 }
16980 });
16981
16982 let item = Box::new(editor);
16983 let item_id = item.item_id();
16984
16985 if split {
16986 let pane = workspace.adjacent_pane(window, cx);
16987 workspace.add_item(pane, item, None, true, true, window, cx);
16988 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16989 let (preview_item_id, preview_item_idx) =
16990 workspace.active_pane().read_with(cx, |pane, _| {
16991 (pane.preview_item_id(), pane.preview_item_idx())
16992 });
16993
16994 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16995
16996 if let Some(preview_item_id) = preview_item_id {
16997 workspace.active_pane().update(cx, |pane, cx| {
16998 pane.remove_item(preview_item_id, false, false, window, cx);
16999 });
17000 }
17001 } else {
17002 workspace.add_item_to_active_pane(item, None, true, window, cx);
17003 }
17004 workspace.active_pane().update(cx, |pane, cx| {
17005 pane.set_preview_item_id(Some(item_id), cx);
17006 });
17007 }
17008
17009 pub fn rename(
17010 &mut self,
17011 _: &Rename,
17012 window: &mut Window,
17013 cx: &mut Context<Self>,
17014 ) -> Option<Task<Result<()>>> {
17015 use language::ToOffset as _;
17016
17017 let provider = self.semantics_provider.clone()?;
17018 let selection = self.selections.newest_anchor().clone();
17019 let (cursor_buffer, cursor_buffer_position) = self
17020 .buffer
17021 .read(cx)
17022 .text_anchor_for_position(selection.head(), cx)?;
17023 let (tail_buffer, cursor_buffer_position_end) = self
17024 .buffer
17025 .read(cx)
17026 .text_anchor_for_position(selection.tail(), cx)?;
17027 if tail_buffer != cursor_buffer {
17028 return None;
17029 }
17030
17031 let snapshot = cursor_buffer.read(cx).snapshot();
17032 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17033 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17034 let prepare_rename = provider
17035 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17036 .unwrap_or_else(|| Task::ready(Ok(None)));
17037 drop(snapshot);
17038
17039 Some(cx.spawn_in(window, async move |this, cx| {
17040 let rename_range = if let Some(range) = prepare_rename.await? {
17041 Some(range)
17042 } else {
17043 this.update(cx, |this, cx| {
17044 let buffer = this.buffer.read(cx).snapshot(cx);
17045 let mut buffer_highlights = this
17046 .document_highlights_for_position(selection.head(), &buffer)
17047 .filter(|highlight| {
17048 highlight.start.excerpt_id == selection.head().excerpt_id
17049 && highlight.end.excerpt_id == selection.head().excerpt_id
17050 });
17051 buffer_highlights
17052 .next()
17053 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17054 })?
17055 };
17056 if let Some(rename_range) = rename_range {
17057 this.update_in(cx, |this, window, cx| {
17058 let snapshot = cursor_buffer.read(cx).snapshot();
17059 let rename_buffer_range = rename_range.to_offset(&snapshot);
17060 let cursor_offset_in_rename_range =
17061 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17062 let cursor_offset_in_rename_range_end =
17063 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17064
17065 this.take_rename(false, window, cx);
17066 let buffer = this.buffer.read(cx).read(cx);
17067 let cursor_offset = selection.head().to_offset(&buffer);
17068 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17069 let rename_end = rename_start + rename_buffer_range.len();
17070 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17071 let mut old_highlight_id = None;
17072 let old_name: Arc<str> = buffer
17073 .chunks(rename_start..rename_end, true)
17074 .map(|chunk| {
17075 if old_highlight_id.is_none() {
17076 old_highlight_id = chunk.syntax_highlight_id;
17077 }
17078 chunk.text
17079 })
17080 .collect::<String>()
17081 .into();
17082
17083 drop(buffer);
17084
17085 // Position the selection in the rename editor so that it matches the current selection.
17086 this.show_local_selections = false;
17087 let rename_editor = cx.new(|cx| {
17088 let mut editor = Editor::single_line(window, cx);
17089 editor.buffer.update(cx, |buffer, cx| {
17090 buffer.edit([(0..0, old_name.clone())], None, cx)
17091 });
17092 let rename_selection_range = match cursor_offset_in_rename_range
17093 .cmp(&cursor_offset_in_rename_range_end)
17094 {
17095 Ordering::Equal => {
17096 editor.select_all(&SelectAll, window, cx);
17097 return editor;
17098 }
17099 Ordering::Less => {
17100 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17101 }
17102 Ordering::Greater => {
17103 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17104 }
17105 };
17106 if rename_selection_range.end > old_name.len() {
17107 editor.select_all(&SelectAll, window, cx);
17108 } else {
17109 editor.change_selections(Default::default(), window, cx, |s| {
17110 s.select_ranges([rename_selection_range]);
17111 });
17112 }
17113 editor
17114 });
17115 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17116 if e == &EditorEvent::Focused {
17117 cx.emit(EditorEvent::FocusedIn)
17118 }
17119 })
17120 .detach();
17121
17122 let write_highlights =
17123 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17124 let read_highlights =
17125 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17126 let ranges = write_highlights
17127 .iter()
17128 .flat_map(|(_, ranges)| ranges.iter())
17129 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17130 .cloned()
17131 .collect();
17132
17133 this.highlight_text::<Rename>(
17134 ranges,
17135 HighlightStyle {
17136 fade_out: Some(0.6),
17137 ..Default::default()
17138 },
17139 cx,
17140 );
17141 let rename_focus_handle = rename_editor.focus_handle(cx);
17142 window.focus(&rename_focus_handle);
17143 let block_id = this.insert_blocks(
17144 [BlockProperties {
17145 style: BlockStyle::Flex,
17146 placement: BlockPlacement::Below(range.start),
17147 height: Some(1),
17148 render: Arc::new({
17149 let rename_editor = rename_editor.clone();
17150 move |cx: &mut BlockContext| {
17151 let mut text_style = cx.editor_style.text.clone();
17152 if let Some(highlight_style) = old_highlight_id
17153 .and_then(|h| h.style(&cx.editor_style.syntax))
17154 {
17155 text_style = text_style.highlight(highlight_style);
17156 }
17157 div()
17158 .block_mouse_except_scroll()
17159 .pl(cx.anchor_x)
17160 .child(EditorElement::new(
17161 &rename_editor,
17162 EditorStyle {
17163 background: cx.theme().system().transparent,
17164 local_player: cx.editor_style.local_player,
17165 text: text_style,
17166 scrollbar_width: cx.editor_style.scrollbar_width,
17167 syntax: cx.editor_style.syntax.clone(),
17168 status: cx.editor_style.status.clone(),
17169 inlay_hints_style: HighlightStyle {
17170 font_weight: Some(FontWeight::BOLD),
17171 ..make_inlay_hints_style(cx.app)
17172 },
17173 edit_prediction_styles: make_suggestion_styles(
17174 cx.app,
17175 ),
17176 ..EditorStyle::default()
17177 },
17178 ))
17179 .into_any_element()
17180 }
17181 }),
17182 priority: 0,
17183 }],
17184 Some(Autoscroll::fit()),
17185 cx,
17186 )[0];
17187 this.pending_rename = Some(RenameState {
17188 range,
17189 old_name,
17190 editor: rename_editor,
17191 block_id,
17192 });
17193 })?;
17194 }
17195
17196 Ok(())
17197 }))
17198 }
17199
17200 pub fn confirm_rename(
17201 &mut self,
17202 _: &ConfirmRename,
17203 window: &mut Window,
17204 cx: &mut Context<Self>,
17205 ) -> Option<Task<Result<()>>> {
17206 let rename = self.take_rename(false, window, cx)?;
17207 let workspace = self.workspace()?.downgrade();
17208 let (buffer, start) = self
17209 .buffer
17210 .read(cx)
17211 .text_anchor_for_position(rename.range.start, cx)?;
17212 let (end_buffer, _) = self
17213 .buffer
17214 .read(cx)
17215 .text_anchor_for_position(rename.range.end, cx)?;
17216 if buffer != end_buffer {
17217 return None;
17218 }
17219
17220 let old_name = rename.old_name;
17221 let new_name = rename.editor.read(cx).text(cx);
17222
17223 let rename = self.semantics_provider.as_ref()?.perform_rename(
17224 &buffer,
17225 start,
17226 new_name.clone(),
17227 cx,
17228 )?;
17229
17230 Some(cx.spawn_in(window, async move |editor, cx| {
17231 let project_transaction = rename.await?;
17232 Self::open_project_transaction(
17233 &editor,
17234 workspace,
17235 project_transaction,
17236 format!("Rename: {} → {}", old_name, new_name),
17237 cx,
17238 )
17239 .await?;
17240
17241 editor.update(cx, |editor, cx| {
17242 editor.refresh_document_highlights(cx);
17243 })?;
17244 Ok(())
17245 }))
17246 }
17247
17248 fn take_rename(
17249 &mut self,
17250 moving_cursor: bool,
17251 window: &mut Window,
17252 cx: &mut Context<Self>,
17253 ) -> Option<RenameState> {
17254 let rename = self.pending_rename.take()?;
17255 if rename.editor.focus_handle(cx).is_focused(window) {
17256 window.focus(&self.focus_handle);
17257 }
17258
17259 self.remove_blocks(
17260 [rename.block_id].into_iter().collect(),
17261 Some(Autoscroll::fit()),
17262 cx,
17263 );
17264 self.clear_highlights::<Rename>(cx);
17265 self.show_local_selections = true;
17266
17267 if moving_cursor {
17268 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17269 editor.selections.newest::<usize>(cx).head()
17270 });
17271
17272 // Update the selection to match the position of the selection inside
17273 // the rename editor.
17274 let snapshot = self.buffer.read(cx).read(cx);
17275 let rename_range = rename.range.to_offset(&snapshot);
17276 let cursor_in_editor = snapshot
17277 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17278 .min(rename_range.end);
17279 drop(snapshot);
17280
17281 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17282 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17283 });
17284 } else {
17285 self.refresh_document_highlights(cx);
17286 }
17287
17288 Some(rename)
17289 }
17290
17291 pub fn pending_rename(&self) -> Option<&RenameState> {
17292 self.pending_rename.as_ref()
17293 }
17294
17295 fn format(
17296 &mut self,
17297 _: &Format,
17298 window: &mut Window,
17299 cx: &mut Context<Self>,
17300 ) -> Option<Task<Result<()>>> {
17301 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17302
17303 let project = match &self.project {
17304 Some(project) => project.clone(),
17305 None => return None,
17306 };
17307
17308 Some(self.perform_format(
17309 project,
17310 FormatTrigger::Manual,
17311 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17312 window,
17313 cx,
17314 ))
17315 }
17316
17317 fn format_selections(
17318 &mut self,
17319 _: &FormatSelections,
17320 window: &mut Window,
17321 cx: &mut Context<Self>,
17322 ) -> Option<Task<Result<()>>> {
17323 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17324
17325 let project = match &self.project {
17326 Some(project) => project.clone(),
17327 None => return None,
17328 };
17329
17330 let ranges = self
17331 .selections
17332 .all_adjusted(cx)
17333 .into_iter()
17334 .map(|selection| selection.range())
17335 .collect_vec();
17336
17337 Some(self.perform_format(
17338 project,
17339 FormatTrigger::Manual,
17340 FormatTarget::Ranges(ranges),
17341 window,
17342 cx,
17343 ))
17344 }
17345
17346 fn perform_format(
17347 &mut self,
17348 project: Entity<Project>,
17349 trigger: FormatTrigger,
17350 target: FormatTarget,
17351 window: &mut Window,
17352 cx: &mut Context<Self>,
17353 ) -> Task<Result<()>> {
17354 let buffer = self.buffer.clone();
17355 let (buffers, target) = match target {
17356 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17357 FormatTarget::Ranges(selection_ranges) => {
17358 let multi_buffer = buffer.read(cx);
17359 let snapshot = multi_buffer.read(cx);
17360 let mut buffers = HashSet::default();
17361 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17362 BTreeMap::new();
17363 for selection_range in selection_ranges {
17364 for (buffer, buffer_range, _) in
17365 snapshot.range_to_buffer_ranges(selection_range)
17366 {
17367 let buffer_id = buffer.remote_id();
17368 let start = buffer.anchor_before(buffer_range.start);
17369 let end = buffer.anchor_after(buffer_range.end);
17370 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17371 buffer_id_to_ranges
17372 .entry(buffer_id)
17373 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17374 .or_insert_with(|| vec![start..end]);
17375 }
17376 }
17377 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17378 }
17379 };
17380
17381 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17382 let selections_prev = transaction_id_prev
17383 .and_then(|transaction_id_prev| {
17384 // default to selections as they were after the last edit, if we have them,
17385 // instead of how they are now.
17386 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17387 // will take you back to where you made the last edit, instead of staying where you scrolled
17388 self.selection_history
17389 .transaction(transaction_id_prev)
17390 .map(|t| t.0.clone())
17391 })
17392 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17393
17394 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17395 let format = project.update(cx, |project, cx| {
17396 project.format(buffers, target, true, trigger, cx)
17397 });
17398
17399 cx.spawn_in(window, async move |editor, cx| {
17400 let transaction = futures::select_biased! {
17401 transaction = format.log_err().fuse() => transaction,
17402 () = timeout => {
17403 log::warn!("timed out waiting for formatting");
17404 None
17405 }
17406 };
17407
17408 buffer
17409 .update(cx, |buffer, cx| {
17410 if let Some(transaction) = transaction
17411 && !buffer.is_singleton()
17412 {
17413 buffer.push_transaction(&transaction.0, cx);
17414 }
17415 cx.notify();
17416 })
17417 .ok();
17418
17419 if let Some(transaction_id_now) =
17420 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17421 {
17422 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17423 if has_new_transaction {
17424 _ = editor.update(cx, |editor, _| {
17425 editor
17426 .selection_history
17427 .insert_transaction(transaction_id_now, selections_prev);
17428 });
17429 }
17430 }
17431
17432 Ok(())
17433 })
17434 }
17435
17436 fn organize_imports(
17437 &mut self,
17438 _: &OrganizeImports,
17439 window: &mut Window,
17440 cx: &mut Context<Self>,
17441 ) -> Option<Task<Result<()>>> {
17442 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17443 let project = match &self.project {
17444 Some(project) => project.clone(),
17445 None => return None,
17446 };
17447 Some(self.perform_code_action_kind(
17448 project,
17449 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17450 window,
17451 cx,
17452 ))
17453 }
17454
17455 fn perform_code_action_kind(
17456 &mut self,
17457 project: Entity<Project>,
17458 kind: CodeActionKind,
17459 window: &mut Window,
17460 cx: &mut Context<Self>,
17461 ) -> Task<Result<()>> {
17462 let buffer = self.buffer.clone();
17463 let buffers = buffer.read(cx).all_buffers();
17464 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17465 let apply_action = project.update(cx, |project, cx| {
17466 project.apply_code_action_kind(buffers, kind, true, cx)
17467 });
17468 cx.spawn_in(window, async move |_, cx| {
17469 let transaction = futures::select_biased! {
17470 () = timeout => {
17471 log::warn!("timed out waiting for executing code action");
17472 None
17473 }
17474 transaction = apply_action.log_err().fuse() => transaction,
17475 };
17476 buffer
17477 .update(cx, |buffer, cx| {
17478 // check if we need this
17479 if let Some(transaction) = transaction
17480 && !buffer.is_singleton()
17481 {
17482 buffer.push_transaction(&transaction.0, cx);
17483 }
17484 cx.notify();
17485 })
17486 .ok();
17487 Ok(())
17488 })
17489 }
17490
17491 pub fn restart_language_server(
17492 &mut self,
17493 _: &RestartLanguageServer,
17494 _: &mut Window,
17495 cx: &mut Context<Self>,
17496 ) {
17497 if let Some(project) = self.project.clone() {
17498 self.buffer.update(cx, |multi_buffer, cx| {
17499 project.update(cx, |project, cx| {
17500 project.restart_language_servers_for_buffers(
17501 multi_buffer.all_buffers().into_iter().collect(),
17502 HashSet::default(),
17503 cx,
17504 );
17505 });
17506 })
17507 }
17508 }
17509
17510 pub fn stop_language_server(
17511 &mut self,
17512 _: &StopLanguageServer,
17513 _: &mut Window,
17514 cx: &mut Context<Self>,
17515 ) {
17516 if let Some(project) = self.project.clone() {
17517 self.buffer.update(cx, |multi_buffer, cx| {
17518 project.update(cx, |project, cx| {
17519 project.stop_language_servers_for_buffers(
17520 multi_buffer.all_buffers().into_iter().collect(),
17521 HashSet::default(),
17522 cx,
17523 );
17524 cx.emit(project::Event::RefreshInlayHints);
17525 });
17526 });
17527 }
17528 }
17529
17530 fn cancel_language_server_work(
17531 workspace: &mut Workspace,
17532 _: &actions::CancelLanguageServerWork,
17533 _: &mut Window,
17534 cx: &mut Context<Workspace>,
17535 ) {
17536 let project = workspace.project();
17537 let buffers = workspace
17538 .active_item(cx)
17539 .and_then(|item| item.act_as::<Editor>(cx))
17540 .map_or(HashSet::default(), |editor| {
17541 editor.read(cx).buffer.read(cx).all_buffers()
17542 });
17543 project.update(cx, |project, cx| {
17544 project.cancel_language_server_work_for_buffers(buffers, cx);
17545 });
17546 }
17547
17548 fn show_character_palette(
17549 &mut self,
17550 _: &ShowCharacterPalette,
17551 window: &mut Window,
17552 _: &mut Context<Self>,
17553 ) {
17554 window.show_character_palette();
17555 }
17556
17557 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17558 if !self.diagnostics_enabled() {
17559 return;
17560 }
17561
17562 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17563 let buffer = self.buffer.read(cx).snapshot(cx);
17564 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17565 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17566 let is_valid = buffer
17567 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17568 .any(|entry| {
17569 entry.diagnostic.is_primary
17570 && !entry.range.is_empty()
17571 && entry.range.start == primary_range_start
17572 && entry.diagnostic.message == active_diagnostics.active_message
17573 });
17574
17575 if !is_valid {
17576 self.dismiss_diagnostics(cx);
17577 }
17578 }
17579 }
17580
17581 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17582 match &self.active_diagnostics {
17583 ActiveDiagnostic::Group(group) => Some(group),
17584 _ => None,
17585 }
17586 }
17587
17588 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17589 if !self.diagnostics_enabled() {
17590 return;
17591 }
17592 self.dismiss_diagnostics(cx);
17593 self.active_diagnostics = ActiveDiagnostic::All;
17594 }
17595
17596 fn activate_diagnostics(
17597 &mut self,
17598 buffer_id: BufferId,
17599 diagnostic: DiagnosticEntryRef<'_, usize>,
17600 window: &mut Window,
17601 cx: &mut Context<Self>,
17602 ) {
17603 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17604 return;
17605 }
17606 self.dismiss_diagnostics(cx);
17607 let snapshot = self.snapshot(window, cx);
17608 let buffer = self.buffer.read(cx).snapshot(cx);
17609 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17610 return;
17611 };
17612
17613 let diagnostic_group = buffer
17614 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17615 .collect::<Vec<_>>();
17616
17617 let blocks =
17618 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17619
17620 let blocks = self.display_map.update(cx, |display_map, cx| {
17621 display_map.insert_blocks(blocks, cx).into_iter().collect()
17622 });
17623 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17624 active_range: buffer.anchor_before(diagnostic.range.start)
17625 ..buffer.anchor_after(diagnostic.range.end),
17626 active_message: diagnostic.diagnostic.message.clone(),
17627 group_id: diagnostic.diagnostic.group_id,
17628 blocks,
17629 });
17630 cx.notify();
17631 }
17632
17633 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17634 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17635 return;
17636 };
17637
17638 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17639 if let ActiveDiagnostic::Group(group) = prev {
17640 self.display_map.update(cx, |display_map, cx| {
17641 display_map.remove_blocks(group.blocks, cx);
17642 });
17643 cx.notify();
17644 }
17645 }
17646
17647 /// Disable inline diagnostics rendering for this editor.
17648 pub fn disable_inline_diagnostics(&mut self) {
17649 self.inline_diagnostics_enabled = false;
17650 self.inline_diagnostics_update = Task::ready(());
17651 self.inline_diagnostics.clear();
17652 }
17653
17654 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17655 self.diagnostics_enabled = false;
17656 self.dismiss_diagnostics(cx);
17657 self.inline_diagnostics_update = Task::ready(());
17658 self.inline_diagnostics.clear();
17659 }
17660
17661 pub fn disable_word_completions(&mut self) {
17662 self.word_completions_enabled = false;
17663 }
17664
17665 pub fn diagnostics_enabled(&self) -> bool {
17666 self.diagnostics_enabled && self.mode.is_full()
17667 }
17668
17669 pub fn inline_diagnostics_enabled(&self) -> bool {
17670 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17671 }
17672
17673 pub fn show_inline_diagnostics(&self) -> bool {
17674 self.show_inline_diagnostics
17675 }
17676
17677 pub fn toggle_inline_diagnostics(
17678 &mut self,
17679 _: &ToggleInlineDiagnostics,
17680 window: &mut Window,
17681 cx: &mut Context<Editor>,
17682 ) {
17683 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17684 self.refresh_inline_diagnostics(false, window, cx);
17685 }
17686
17687 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17688 self.diagnostics_max_severity = severity;
17689 self.display_map.update(cx, |display_map, _| {
17690 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17691 });
17692 }
17693
17694 pub fn toggle_diagnostics(
17695 &mut self,
17696 _: &ToggleDiagnostics,
17697 window: &mut Window,
17698 cx: &mut Context<Editor>,
17699 ) {
17700 if !self.diagnostics_enabled() {
17701 return;
17702 }
17703
17704 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17705 EditorSettings::get_global(cx)
17706 .diagnostics_max_severity
17707 .filter(|severity| severity != &DiagnosticSeverity::Off)
17708 .unwrap_or(DiagnosticSeverity::Hint)
17709 } else {
17710 DiagnosticSeverity::Off
17711 };
17712 self.set_max_diagnostics_severity(new_severity, cx);
17713 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17714 self.active_diagnostics = ActiveDiagnostic::None;
17715 self.inline_diagnostics_update = Task::ready(());
17716 self.inline_diagnostics.clear();
17717 } else {
17718 self.refresh_inline_diagnostics(false, window, cx);
17719 }
17720
17721 cx.notify();
17722 }
17723
17724 pub fn toggle_minimap(
17725 &mut self,
17726 _: &ToggleMinimap,
17727 window: &mut Window,
17728 cx: &mut Context<Editor>,
17729 ) {
17730 if self.supports_minimap(cx) {
17731 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17732 }
17733 }
17734
17735 fn refresh_inline_diagnostics(
17736 &mut self,
17737 debounce: bool,
17738 window: &mut Window,
17739 cx: &mut Context<Self>,
17740 ) {
17741 let max_severity = ProjectSettings::get_global(cx)
17742 .diagnostics
17743 .inline
17744 .max_severity
17745 .unwrap_or(self.diagnostics_max_severity);
17746
17747 if !self.inline_diagnostics_enabled()
17748 || !self.show_inline_diagnostics
17749 || max_severity == DiagnosticSeverity::Off
17750 {
17751 self.inline_diagnostics_update = Task::ready(());
17752 self.inline_diagnostics.clear();
17753 return;
17754 }
17755
17756 let debounce_ms = ProjectSettings::get_global(cx)
17757 .diagnostics
17758 .inline
17759 .update_debounce_ms;
17760 let debounce = if debounce && debounce_ms > 0 {
17761 Some(Duration::from_millis(debounce_ms))
17762 } else {
17763 None
17764 };
17765 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17766 if let Some(debounce) = debounce {
17767 cx.background_executor().timer(debounce).await;
17768 }
17769 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17770 editor
17771 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17772 .ok()
17773 }) else {
17774 return;
17775 };
17776
17777 let new_inline_diagnostics = cx
17778 .background_spawn(async move {
17779 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17780 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17781 let message = diagnostic_entry
17782 .diagnostic
17783 .message
17784 .split_once('\n')
17785 .map(|(line, _)| line)
17786 .map(SharedString::new)
17787 .unwrap_or_else(|| {
17788 SharedString::new(&*diagnostic_entry.diagnostic.message)
17789 });
17790 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17791 let (Ok(i) | Err(i)) = inline_diagnostics
17792 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17793 inline_diagnostics.insert(
17794 i,
17795 (
17796 start_anchor,
17797 InlineDiagnostic {
17798 message,
17799 group_id: diagnostic_entry.diagnostic.group_id,
17800 start: diagnostic_entry.range.start.to_point(&snapshot),
17801 is_primary: diagnostic_entry.diagnostic.is_primary,
17802 severity: diagnostic_entry.diagnostic.severity,
17803 },
17804 ),
17805 );
17806 }
17807 inline_diagnostics
17808 })
17809 .await;
17810
17811 editor
17812 .update(cx, |editor, cx| {
17813 editor.inline_diagnostics = new_inline_diagnostics;
17814 cx.notify();
17815 })
17816 .ok();
17817 });
17818 }
17819
17820 fn pull_diagnostics(
17821 &mut self,
17822 buffer_id: Option<BufferId>,
17823 window: &Window,
17824 cx: &mut Context<Self>,
17825 ) -> Option<()> {
17826 if self.ignore_lsp_data() {
17827 return None;
17828 }
17829 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17830 .diagnostics
17831 .lsp_pull_diagnostics;
17832 if !pull_diagnostics_settings.enabled {
17833 return None;
17834 }
17835 let project = self.project()?.downgrade();
17836 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17837 let mut buffers = self.buffer.read(cx).all_buffers();
17838 buffers.retain(|buffer| {
17839 let buffer_id_to_retain = buffer.read(cx).remote_id();
17840 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
17841 && self.registered_buffers.contains_key(&buffer_id_to_retain)
17842 });
17843 if buffers.is_empty() {
17844 self.pull_diagnostics_task = Task::ready(());
17845 return None;
17846 }
17847
17848 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17849 cx.background_executor().timer(debounce).await;
17850
17851 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17852 buffers
17853 .into_iter()
17854 .filter_map(|buffer| {
17855 project
17856 .update(cx, |project, cx| {
17857 project.lsp_store().update(cx, |lsp_store, cx| {
17858 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17859 })
17860 })
17861 .ok()
17862 })
17863 .collect::<FuturesUnordered<_>>()
17864 }) else {
17865 return;
17866 };
17867
17868 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17869 match pull_task {
17870 Ok(()) => {
17871 if editor
17872 .update_in(cx, |editor, window, cx| {
17873 editor.update_diagnostics_state(window, cx);
17874 })
17875 .is_err()
17876 {
17877 return;
17878 }
17879 }
17880 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17881 }
17882 }
17883 });
17884
17885 Some(())
17886 }
17887
17888 pub fn set_selections_from_remote(
17889 &mut self,
17890 selections: Vec<Selection<Anchor>>,
17891 pending_selection: Option<Selection<Anchor>>,
17892 window: &mut Window,
17893 cx: &mut Context<Self>,
17894 ) {
17895 let old_cursor_position = self.selections.newest_anchor().head();
17896 self.selections.change_with(cx, |s| {
17897 s.select_anchors(selections);
17898 if let Some(pending_selection) = pending_selection {
17899 s.set_pending(pending_selection, SelectMode::Character);
17900 } else {
17901 s.clear_pending();
17902 }
17903 });
17904 self.selections_did_change(
17905 false,
17906 &old_cursor_position,
17907 SelectionEffects::default(),
17908 window,
17909 cx,
17910 );
17911 }
17912
17913 pub fn transact(
17914 &mut self,
17915 window: &mut Window,
17916 cx: &mut Context<Self>,
17917 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17918 ) -> Option<TransactionId> {
17919 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17920 this.start_transaction_at(Instant::now(), window, cx);
17921 update(this, window, cx);
17922 this.end_transaction_at(Instant::now(), cx)
17923 })
17924 }
17925
17926 pub fn start_transaction_at(
17927 &mut self,
17928 now: Instant,
17929 window: &mut Window,
17930 cx: &mut Context<Self>,
17931 ) -> Option<TransactionId> {
17932 self.end_selection(window, cx);
17933 if let Some(tx_id) = self
17934 .buffer
17935 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17936 {
17937 self.selection_history
17938 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
17939 cx.emit(EditorEvent::TransactionBegun {
17940 transaction_id: tx_id,
17941 });
17942 Some(tx_id)
17943 } else {
17944 None
17945 }
17946 }
17947
17948 pub fn end_transaction_at(
17949 &mut self,
17950 now: Instant,
17951 cx: &mut Context<Self>,
17952 ) -> Option<TransactionId> {
17953 if let Some(transaction_id) = self
17954 .buffer
17955 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17956 {
17957 if let Some((_, end_selections)) =
17958 self.selection_history.transaction_mut(transaction_id)
17959 {
17960 *end_selections = Some(self.selections.disjoint_anchors_arc());
17961 } else {
17962 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17963 }
17964
17965 cx.emit(EditorEvent::Edited { transaction_id });
17966 Some(transaction_id)
17967 } else {
17968 None
17969 }
17970 }
17971
17972 pub fn modify_transaction_selection_history(
17973 &mut self,
17974 transaction_id: TransactionId,
17975 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17976 ) -> bool {
17977 self.selection_history
17978 .transaction_mut(transaction_id)
17979 .map(modify)
17980 .is_some()
17981 }
17982
17983 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17984 if self.selection_mark_mode {
17985 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17986 s.move_with(|_, sel| {
17987 sel.collapse_to(sel.head(), SelectionGoal::None);
17988 });
17989 })
17990 }
17991 self.selection_mark_mode = true;
17992 cx.notify();
17993 }
17994
17995 pub fn swap_selection_ends(
17996 &mut self,
17997 _: &actions::SwapSelectionEnds,
17998 window: &mut Window,
17999 cx: &mut Context<Self>,
18000 ) {
18001 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18002 s.move_with(|_, sel| {
18003 if sel.start != sel.end {
18004 sel.reversed = !sel.reversed
18005 }
18006 });
18007 });
18008 self.request_autoscroll(Autoscroll::newest(), cx);
18009 cx.notify();
18010 }
18011
18012 pub fn toggle_focus(
18013 workspace: &mut Workspace,
18014 _: &actions::ToggleFocus,
18015 window: &mut Window,
18016 cx: &mut Context<Workspace>,
18017 ) {
18018 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18019 return;
18020 };
18021 workspace.activate_item(&item, true, true, window, cx);
18022 }
18023
18024 pub fn toggle_fold(
18025 &mut self,
18026 _: &actions::ToggleFold,
18027 window: &mut Window,
18028 cx: &mut Context<Self>,
18029 ) {
18030 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18031 let selection = self.selections.newest::<Point>(cx);
18032
18033 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18034 let range = if selection.is_empty() {
18035 let point = selection.head().to_display_point(&display_map);
18036 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18037 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18038 .to_point(&display_map);
18039 start..end
18040 } else {
18041 selection.range()
18042 };
18043 if display_map.folds_in_range(range).next().is_some() {
18044 self.unfold_lines(&Default::default(), window, cx)
18045 } else {
18046 self.fold(&Default::default(), window, cx)
18047 }
18048 } else {
18049 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18050 let buffer_ids: HashSet<_> = self
18051 .selections
18052 .disjoint_anchor_ranges()
18053 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18054 .collect();
18055
18056 let should_unfold = buffer_ids
18057 .iter()
18058 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18059
18060 for buffer_id in buffer_ids {
18061 if should_unfold {
18062 self.unfold_buffer(buffer_id, cx);
18063 } else {
18064 self.fold_buffer(buffer_id, cx);
18065 }
18066 }
18067 }
18068 }
18069
18070 pub fn toggle_fold_recursive(
18071 &mut self,
18072 _: &actions::ToggleFoldRecursive,
18073 window: &mut Window,
18074 cx: &mut Context<Self>,
18075 ) {
18076 let selection = self.selections.newest::<Point>(cx);
18077
18078 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18079 let range = if selection.is_empty() {
18080 let point = selection.head().to_display_point(&display_map);
18081 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18082 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18083 .to_point(&display_map);
18084 start..end
18085 } else {
18086 selection.range()
18087 };
18088 if display_map.folds_in_range(range).next().is_some() {
18089 self.unfold_recursive(&Default::default(), window, cx)
18090 } else {
18091 self.fold_recursive(&Default::default(), window, cx)
18092 }
18093 }
18094
18095 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18096 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18097 let mut to_fold = Vec::new();
18098 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18099 let selections = self.selections.all_adjusted(cx);
18100
18101 for selection in selections {
18102 let range = selection.range().sorted();
18103 let buffer_start_row = range.start.row;
18104
18105 if range.start.row != range.end.row {
18106 let mut found = false;
18107 let mut row = range.start.row;
18108 while row <= range.end.row {
18109 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18110 {
18111 found = true;
18112 row = crease.range().end.row + 1;
18113 to_fold.push(crease);
18114 } else {
18115 row += 1
18116 }
18117 }
18118 if found {
18119 continue;
18120 }
18121 }
18122
18123 for row in (0..=range.start.row).rev() {
18124 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18125 && crease.range().end.row >= buffer_start_row
18126 {
18127 to_fold.push(crease);
18128 if row <= range.start.row {
18129 break;
18130 }
18131 }
18132 }
18133 }
18134
18135 self.fold_creases(to_fold, true, window, cx);
18136 } else {
18137 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18138 let buffer_ids = self
18139 .selections
18140 .disjoint_anchor_ranges()
18141 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18142 .collect::<HashSet<_>>();
18143 for buffer_id in buffer_ids {
18144 self.fold_buffer(buffer_id, cx);
18145 }
18146 }
18147 }
18148
18149 pub fn toggle_fold_all(
18150 &mut self,
18151 _: &actions::ToggleFoldAll,
18152 window: &mut Window,
18153 cx: &mut Context<Self>,
18154 ) {
18155 if self.buffer.read(cx).is_singleton() {
18156 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18157 let has_folds = display_map
18158 .folds_in_range(0..display_map.buffer_snapshot().len())
18159 .next()
18160 .is_some();
18161
18162 if has_folds {
18163 self.unfold_all(&actions::UnfoldAll, window, cx);
18164 } else {
18165 self.fold_all(&actions::FoldAll, window, cx);
18166 }
18167 } else {
18168 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18169 let should_unfold = buffer_ids
18170 .iter()
18171 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18172
18173 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18174 editor
18175 .update_in(cx, |editor, _, cx| {
18176 for buffer_id in buffer_ids {
18177 if should_unfold {
18178 editor.unfold_buffer(buffer_id, cx);
18179 } else {
18180 editor.fold_buffer(buffer_id, cx);
18181 }
18182 }
18183 })
18184 .ok();
18185 });
18186 }
18187 }
18188
18189 fn fold_at_level(
18190 &mut self,
18191 fold_at: &FoldAtLevel,
18192 window: &mut Window,
18193 cx: &mut Context<Self>,
18194 ) {
18195 if !self.buffer.read(cx).is_singleton() {
18196 return;
18197 }
18198
18199 let fold_at_level = fold_at.0;
18200 let snapshot = self.buffer.read(cx).snapshot(cx);
18201 let mut to_fold = Vec::new();
18202 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18203
18204 let row_ranges_to_keep: Vec<Range<u32>> = self
18205 .selections
18206 .all::<Point>(cx)
18207 .into_iter()
18208 .map(|sel| sel.start.row..sel.end.row)
18209 .collect();
18210
18211 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18212 while start_row < end_row {
18213 match self
18214 .snapshot(window, cx)
18215 .crease_for_buffer_row(MultiBufferRow(start_row))
18216 {
18217 Some(crease) => {
18218 let nested_start_row = crease.range().start.row + 1;
18219 let nested_end_row = crease.range().end.row;
18220
18221 if current_level < fold_at_level {
18222 stack.push((nested_start_row, nested_end_row, current_level + 1));
18223 } else if current_level == fold_at_level {
18224 // Fold iff there is no selection completely contained within the fold region
18225 if !row_ranges_to_keep.iter().any(|selection| {
18226 selection.end >= nested_start_row
18227 && selection.start <= nested_end_row
18228 }) {
18229 to_fold.push(crease);
18230 }
18231 }
18232
18233 start_row = nested_end_row + 1;
18234 }
18235 None => start_row += 1,
18236 }
18237 }
18238 }
18239
18240 self.fold_creases(to_fold, true, window, cx);
18241 }
18242
18243 pub fn fold_at_level_1(
18244 &mut self,
18245 _: &actions::FoldAtLevel1,
18246 window: &mut Window,
18247 cx: &mut Context<Self>,
18248 ) {
18249 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18250 }
18251
18252 pub fn fold_at_level_2(
18253 &mut self,
18254 _: &actions::FoldAtLevel2,
18255 window: &mut Window,
18256 cx: &mut Context<Self>,
18257 ) {
18258 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18259 }
18260
18261 pub fn fold_at_level_3(
18262 &mut self,
18263 _: &actions::FoldAtLevel3,
18264 window: &mut Window,
18265 cx: &mut Context<Self>,
18266 ) {
18267 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18268 }
18269
18270 pub fn fold_at_level_4(
18271 &mut self,
18272 _: &actions::FoldAtLevel4,
18273 window: &mut Window,
18274 cx: &mut Context<Self>,
18275 ) {
18276 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18277 }
18278
18279 pub fn fold_at_level_5(
18280 &mut self,
18281 _: &actions::FoldAtLevel5,
18282 window: &mut Window,
18283 cx: &mut Context<Self>,
18284 ) {
18285 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18286 }
18287
18288 pub fn fold_at_level_6(
18289 &mut self,
18290 _: &actions::FoldAtLevel6,
18291 window: &mut Window,
18292 cx: &mut Context<Self>,
18293 ) {
18294 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18295 }
18296
18297 pub fn fold_at_level_7(
18298 &mut self,
18299 _: &actions::FoldAtLevel7,
18300 window: &mut Window,
18301 cx: &mut Context<Self>,
18302 ) {
18303 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18304 }
18305
18306 pub fn fold_at_level_8(
18307 &mut self,
18308 _: &actions::FoldAtLevel8,
18309 window: &mut Window,
18310 cx: &mut Context<Self>,
18311 ) {
18312 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18313 }
18314
18315 pub fn fold_at_level_9(
18316 &mut self,
18317 _: &actions::FoldAtLevel9,
18318 window: &mut Window,
18319 cx: &mut Context<Self>,
18320 ) {
18321 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18322 }
18323
18324 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18325 if self.buffer.read(cx).is_singleton() {
18326 let mut fold_ranges = Vec::new();
18327 let snapshot = self.buffer.read(cx).snapshot(cx);
18328
18329 for row in 0..snapshot.max_row().0 {
18330 if let Some(foldable_range) = self
18331 .snapshot(window, cx)
18332 .crease_for_buffer_row(MultiBufferRow(row))
18333 {
18334 fold_ranges.push(foldable_range);
18335 }
18336 }
18337
18338 self.fold_creases(fold_ranges, true, window, cx);
18339 } else {
18340 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18341 editor
18342 .update_in(cx, |editor, _, cx| {
18343 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18344 editor.fold_buffer(buffer_id, cx);
18345 }
18346 })
18347 .ok();
18348 });
18349 }
18350 }
18351
18352 pub fn fold_function_bodies(
18353 &mut self,
18354 _: &actions::FoldFunctionBodies,
18355 window: &mut Window,
18356 cx: &mut Context<Self>,
18357 ) {
18358 let snapshot = self.buffer.read(cx).snapshot(cx);
18359
18360 let ranges = snapshot
18361 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18362 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18363 .collect::<Vec<_>>();
18364
18365 let creases = ranges
18366 .into_iter()
18367 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18368 .collect();
18369
18370 self.fold_creases(creases, true, window, cx);
18371 }
18372
18373 pub fn fold_recursive(
18374 &mut self,
18375 _: &actions::FoldRecursive,
18376 window: &mut Window,
18377 cx: &mut Context<Self>,
18378 ) {
18379 let mut to_fold = Vec::new();
18380 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18381 let selections = self.selections.all_adjusted(cx);
18382
18383 for selection in selections {
18384 let range = selection.range().sorted();
18385 let buffer_start_row = range.start.row;
18386
18387 if range.start.row != range.end.row {
18388 let mut found = false;
18389 for row in range.start.row..=range.end.row {
18390 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18391 found = true;
18392 to_fold.push(crease);
18393 }
18394 }
18395 if found {
18396 continue;
18397 }
18398 }
18399
18400 for row in (0..=range.start.row).rev() {
18401 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18402 if crease.range().end.row >= buffer_start_row {
18403 to_fold.push(crease);
18404 } else {
18405 break;
18406 }
18407 }
18408 }
18409 }
18410
18411 self.fold_creases(to_fold, true, window, cx);
18412 }
18413
18414 pub fn fold_at(
18415 &mut self,
18416 buffer_row: MultiBufferRow,
18417 window: &mut Window,
18418 cx: &mut Context<Self>,
18419 ) {
18420 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18421
18422 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18423 let autoscroll = self
18424 .selections
18425 .all::<Point>(cx)
18426 .iter()
18427 .any(|selection| crease.range().overlaps(&selection.range()));
18428
18429 self.fold_creases(vec![crease], autoscroll, window, cx);
18430 }
18431 }
18432
18433 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18434 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18435 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18436 let buffer = display_map.buffer_snapshot();
18437 let selections = self.selections.all::<Point>(cx);
18438 let ranges = selections
18439 .iter()
18440 .map(|s| {
18441 let range = s.display_range(&display_map).sorted();
18442 let mut start = range.start.to_point(&display_map);
18443 let mut end = range.end.to_point(&display_map);
18444 start.column = 0;
18445 end.column = buffer.line_len(MultiBufferRow(end.row));
18446 start..end
18447 })
18448 .collect::<Vec<_>>();
18449
18450 self.unfold_ranges(&ranges, true, true, cx);
18451 } else {
18452 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18453 let buffer_ids = self
18454 .selections
18455 .disjoint_anchor_ranges()
18456 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18457 .collect::<HashSet<_>>();
18458 for buffer_id in buffer_ids {
18459 self.unfold_buffer(buffer_id, cx);
18460 }
18461 }
18462 }
18463
18464 pub fn unfold_recursive(
18465 &mut self,
18466 _: &UnfoldRecursive,
18467 _window: &mut Window,
18468 cx: &mut Context<Self>,
18469 ) {
18470 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18471 let selections = self.selections.all::<Point>(cx);
18472 let ranges = selections
18473 .iter()
18474 .map(|s| {
18475 let mut range = s.display_range(&display_map).sorted();
18476 *range.start.column_mut() = 0;
18477 *range.end.column_mut() = display_map.line_len(range.end.row());
18478 let start = range.start.to_point(&display_map);
18479 let end = range.end.to_point(&display_map);
18480 start..end
18481 })
18482 .collect::<Vec<_>>();
18483
18484 self.unfold_ranges(&ranges, true, true, cx);
18485 }
18486
18487 pub fn unfold_at(
18488 &mut self,
18489 buffer_row: MultiBufferRow,
18490 _window: &mut Window,
18491 cx: &mut Context<Self>,
18492 ) {
18493 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18494
18495 let intersection_range = Point::new(buffer_row.0, 0)
18496 ..Point::new(
18497 buffer_row.0,
18498 display_map.buffer_snapshot().line_len(buffer_row),
18499 );
18500
18501 let autoscroll = self
18502 .selections
18503 .all::<Point>(cx)
18504 .iter()
18505 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18506
18507 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18508 }
18509
18510 pub fn unfold_all(
18511 &mut self,
18512 _: &actions::UnfoldAll,
18513 _window: &mut Window,
18514 cx: &mut Context<Self>,
18515 ) {
18516 if self.buffer.read(cx).is_singleton() {
18517 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18518 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18519 } else {
18520 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18521 editor
18522 .update(cx, |editor, cx| {
18523 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18524 editor.unfold_buffer(buffer_id, cx);
18525 }
18526 })
18527 .ok();
18528 });
18529 }
18530 }
18531
18532 pub fn fold_selected_ranges(
18533 &mut self,
18534 _: &FoldSelectedRanges,
18535 window: &mut Window,
18536 cx: &mut Context<Self>,
18537 ) {
18538 let selections = self.selections.all_adjusted(cx);
18539 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18540 let ranges = selections
18541 .into_iter()
18542 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18543 .collect::<Vec<_>>();
18544 self.fold_creases(ranges, true, window, cx);
18545 }
18546
18547 pub fn fold_ranges<T: ToOffset + Clone>(
18548 &mut self,
18549 ranges: Vec<Range<T>>,
18550 auto_scroll: bool,
18551 window: &mut Window,
18552 cx: &mut Context<Self>,
18553 ) {
18554 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18555 let ranges = ranges
18556 .into_iter()
18557 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18558 .collect::<Vec<_>>();
18559 self.fold_creases(ranges, auto_scroll, window, cx);
18560 }
18561
18562 pub fn fold_creases<T: ToOffset + Clone>(
18563 &mut self,
18564 creases: Vec<Crease<T>>,
18565 auto_scroll: bool,
18566 _window: &mut Window,
18567 cx: &mut Context<Self>,
18568 ) {
18569 if creases.is_empty() {
18570 return;
18571 }
18572
18573 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18574
18575 if auto_scroll {
18576 self.request_autoscroll(Autoscroll::fit(), cx);
18577 }
18578
18579 cx.notify();
18580
18581 self.scrollbar_marker_state.dirty = true;
18582 self.folds_did_change(cx);
18583 }
18584
18585 /// Removes any folds whose ranges intersect any of the given ranges.
18586 pub fn unfold_ranges<T: ToOffset + Clone>(
18587 &mut self,
18588 ranges: &[Range<T>],
18589 inclusive: bool,
18590 auto_scroll: bool,
18591 cx: &mut Context<Self>,
18592 ) {
18593 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18594 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18595 });
18596 self.folds_did_change(cx);
18597 }
18598
18599 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18600 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18601 return;
18602 }
18603 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18604 self.display_map.update(cx, |display_map, cx| {
18605 display_map.fold_buffers([buffer_id], cx)
18606 });
18607 cx.emit(EditorEvent::BufferFoldToggled {
18608 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18609 folded: true,
18610 });
18611 cx.notify();
18612 }
18613
18614 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18615 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18616 return;
18617 }
18618 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18619 self.display_map.update(cx, |display_map, cx| {
18620 display_map.unfold_buffers([buffer_id], cx);
18621 });
18622 cx.emit(EditorEvent::BufferFoldToggled {
18623 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18624 folded: false,
18625 });
18626 cx.notify();
18627 }
18628
18629 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18630 self.display_map.read(cx).is_buffer_folded(buffer)
18631 }
18632
18633 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18634 self.display_map.read(cx).folded_buffers()
18635 }
18636
18637 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18638 self.display_map.update(cx, |display_map, cx| {
18639 display_map.disable_header_for_buffer(buffer_id, cx);
18640 });
18641 cx.notify();
18642 }
18643
18644 /// Removes any folds with the given ranges.
18645 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18646 &mut self,
18647 ranges: &[Range<T>],
18648 type_id: TypeId,
18649 auto_scroll: bool,
18650 cx: &mut Context<Self>,
18651 ) {
18652 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18653 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18654 });
18655 self.folds_did_change(cx);
18656 }
18657
18658 fn remove_folds_with<T: ToOffset + Clone>(
18659 &mut self,
18660 ranges: &[Range<T>],
18661 auto_scroll: bool,
18662 cx: &mut Context<Self>,
18663 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18664 ) {
18665 if ranges.is_empty() {
18666 return;
18667 }
18668
18669 let mut buffers_affected = HashSet::default();
18670 let multi_buffer = self.buffer().read(cx);
18671 for range in ranges {
18672 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18673 buffers_affected.insert(buffer.read(cx).remote_id());
18674 };
18675 }
18676
18677 self.display_map.update(cx, update);
18678
18679 if auto_scroll {
18680 self.request_autoscroll(Autoscroll::fit(), cx);
18681 }
18682
18683 cx.notify();
18684 self.scrollbar_marker_state.dirty = true;
18685 self.active_indent_guides_state.dirty = true;
18686 }
18687
18688 pub fn update_renderer_widths(
18689 &mut self,
18690 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18691 cx: &mut Context<Self>,
18692 ) -> bool {
18693 self.display_map
18694 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18695 }
18696
18697 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18698 self.display_map.read(cx).fold_placeholder.clone()
18699 }
18700
18701 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18702 self.buffer.update(cx, |buffer, cx| {
18703 buffer.set_all_diff_hunks_expanded(cx);
18704 });
18705 }
18706
18707 pub fn expand_all_diff_hunks(
18708 &mut self,
18709 _: &ExpandAllDiffHunks,
18710 _window: &mut Window,
18711 cx: &mut Context<Self>,
18712 ) {
18713 self.buffer.update(cx, |buffer, cx| {
18714 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18715 });
18716 }
18717
18718 pub fn toggle_selected_diff_hunks(
18719 &mut self,
18720 _: &ToggleSelectedDiffHunks,
18721 _window: &mut Window,
18722 cx: &mut Context<Self>,
18723 ) {
18724 let ranges: Vec<_> = self
18725 .selections
18726 .disjoint_anchors()
18727 .iter()
18728 .map(|s| s.range())
18729 .collect();
18730 self.toggle_diff_hunks_in_ranges(ranges, cx);
18731 }
18732
18733 pub fn diff_hunks_in_ranges<'a>(
18734 &'a self,
18735 ranges: &'a [Range<Anchor>],
18736 buffer: &'a MultiBufferSnapshot,
18737 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18738 ranges.iter().flat_map(move |range| {
18739 let end_excerpt_id = range.end.excerpt_id;
18740 let range = range.to_point(buffer);
18741 let mut peek_end = range.end;
18742 if range.end.row < buffer.max_row().0 {
18743 peek_end = Point::new(range.end.row + 1, 0);
18744 }
18745 buffer
18746 .diff_hunks_in_range(range.start..peek_end)
18747 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18748 })
18749 }
18750
18751 pub fn has_stageable_diff_hunks_in_ranges(
18752 &self,
18753 ranges: &[Range<Anchor>],
18754 snapshot: &MultiBufferSnapshot,
18755 ) -> bool {
18756 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18757 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18758 }
18759
18760 pub fn toggle_staged_selected_diff_hunks(
18761 &mut self,
18762 _: &::git::ToggleStaged,
18763 _: &mut Window,
18764 cx: &mut Context<Self>,
18765 ) {
18766 let snapshot = self.buffer.read(cx).snapshot(cx);
18767 let ranges: Vec<_> = self
18768 .selections
18769 .disjoint_anchors()
18770 .iter()
18771 .map(|s| s.range())
18772 .collect();
18773 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18774 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18775 }
18776
18777 pub fn set_render_diff_hunk_controls(
18778 &mut self,
18779 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18780 cx: &mut Context<Self>,
18781 ) {
18782 self.render_diff_hunk_controls = render_diff_hunk_controls;
18783 cx.notify();
18784 }
18785
18786 pub fn stage_and_next(
18787 &mut self,
18788 _: &::git::StageAndNext,
18789 window: &mut Window,
18790 cx: &mut Context<Self>,
18791 ) {
18792 self.do_stage_or_unstage_and_next(true, window, cx);
18793 }
18794
18795 pub fn unstage_and_next(
18796 &mut self,
18797 _: &::git::UnstageAndNext,
18798 window: &mut Window,
18799 cx: &mut Context<Self>,
18800 ) {
18801 self.do_stage_or_unstage_and_next(false, window, cx);
18802 }
18803
18804 pub fn stage_or_unstage_diff_hunks(
18805 &mut self,
18806 stage: bool,
18807 ranges: Vec<Range<Anchor>>,
18808 cx: &mut Context<Self>,
18809 ) {
18810 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18811 cx.spawn(async move |this, cx| {
18812 task.await?;
18813 this.update(cx, |this, cx| {
18814 let snapshot = this.buffer.read(cx).snapshot(cx);
18815 let chunk_by = this
18816 .diff_hunks_in_ranges(&ranges, &snapshot)
18817 .chunk_by(|hunk| hunk.buffer_id);
18818 for (buffer_id, hunks) in &chunk_by {
18819 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18820 }
18821 })
18822 })
18823 .detach_and_log_err(cx);
18824 }
18825
18826 fn save_buffers_for_ranges_if_needed(
18827 &mut self,
18828 ranges: &[Range<Anchor>],
18829 cx: &mut Context<Editor>,
18830 ) -> Task<Result<()>> {
18831 let multibuffer = self.buffer.read(cx);
18832 let snapshot = multibuffer.read(cx);
18833 let buffer_ids: HashSet<_> = ranges
18834 .iter()
18835 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18836 .collect();
18837 drop(snapshot);
18838
18839 let mut buffers = HashSet::default();
18840 for buffer_id in buffer_ids {
18841 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18842 let buffer = buffer_entity.read(cx);
18843 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18844 {
18845 buffers.insert(buffer_entity);
18846 }
18847 }
18848 }
18849
18850 if let Some(project) = &self.project {
18851 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18852 } else {
18853 Task::ready(Ok(()))
18854 }
18855 }
18856
18857 fn do_stage_or_unstage_and_next(
18858 &mut self,
18859 stage: bool,
18860 window: &mut Window,
18861 cx: &mut Context<Self>,
18862 ) {
18863 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18864
18865 if ranges.iter().any(|range| range.start != range.end) {
18866 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18867 return;
18868 }
18869
18870 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18871 let snapshot = self.snapshot(window, cx);
18872 let position = self.selections.newest::<Point>(cx).head();
18873 let mut row = snapshot
18874 .buffer_snapshot()
18875 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
18876 .find(|hunk| hunk.row_range.start.0 > position.row)
18877 .map(|hunk| hunk.row_range.start);
18878
18879 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18880 // Outside of the project diff editor, wrap around to the beginning.
18881 if !all_diff_hunks_expanded {
18882 row = row.or_else(|| {
18883 snapshot
18884 .buffer_snapshot()
18885 .diff_hunks_in_range(Point::zero()..position)
18886 .find(|hunk| hunk.row_range.end.0 < position.row)
18887 .map(|hunk| hunk.row_range.start)
18888 });
18889 }
18890
18891 if let Some(row) = row {
18892 let destination = Point::new(row.0, 0);
18893 let autoscroll = Autoscroll::center();
18894
18895 self.unfold_ranges(&[destination..destination], false, false, cx);
18896 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18897 s.select_ranges([destination..destination]);
18898 });
18899 }
18900 }
18901
18902 fn do_stage_or_unstage(
18903 &self,
18904 stage: bool,
18905 buffer_id: BufferId,
18906 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18907 cx: &mut App,
18908 ) -> Option<()> {
18909 let project = self.project()?;
18910 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18911 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18912 let buffer_snapshot = buffer.read(cx).snapshot();
18913 let file_exists = buffer_snapshot
18914 .file()
18915 .is_some_and(|file| file.disk_state().exists());
18916 diff.update(cx, |diff, cx| {
18917 diff.stage_or_unstage_hunks(
18918 stage,
18919 &hunks
18920 .map(|hunk| buffer_diff::DiffHunk {
18921 buffer_range: hunk.buffer_range,
18922 diff_base_byte_range: hunk.diff_base_byte_range,
18923 secondary_status: hunk.secondary_status,
18924 range: Point::zero()..Point::zero(), // unused
18925 })
18926 .collect::<Vec<_>>(),
18927 &buffer_snapshot,
18928 file_exists,
18929 cx,
18930 )
18931 });
18932 None
18933 }
18934
18935 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18936 let ranges: Vec<_> = self
18937 .selections
18938 .disjoint_anchors()
18939 .iter()
18940 .map(|s| s.range())
18941 .collect();
18942 self.buffer
18943 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18944 }
18945
18946 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18947 self.buffer.update(cx, |buffer, cx| {
18948 let ranges = vec![Anchor::min()..Anchor::max()];
18949 if !buffer.all_diff_hunks_expanded()
18950 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18951 {
18952 buffer.collapse_diff_hunks(ranges, cx);
18953 true
18954 } else {
18955 false
18956 }
18957 })
18958 }
18959
18960 fn toggle_diff_hunks_in_ranges(
18961 &mut self,
18962 ranges: Vec<Range<Anchor>>,
18963 cx: &mut Context<Editor>,
18964 ) {
18965 self.buffer.update(cx, |buffer, cx| {
18966 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18967 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18968 })
18969 }
18970
18971 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18972 self.buffer.update(cx, |buffer, cx| {
18973 let snapshot = buffer.snapshot(cx);
18974 let excerpt_id = range.end.excerpt_id;
18975 let point_range = range.to_point(&snapshot);
18976 let expand = !buffer.single_hunk_is_expanded(range, cx);
18977 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18978 })
18979 }
18980
18981 pub(crate) fn apply_all_diff_hunks(
18982 &mut self,
18983 _: &ApplyAllDiffHunks,
18984 window: &mut Window,
18985 cx: &mut Context<Self>,
18986 ) {
18987 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18988
18989 let buffers = self.buffer.read(cx).all_buffers();
18990 for branch_buffer in buffers {
18991 branch_buffer.update(cx, |branch_buffer, cx| {
18992 branch_buffer.merge_into_base(Vec::new(), cx);
18993 });
18994 }
18995
18996 if let Some(project) = self.project.clone() {
18997 self.save(
18998 SaveOptions {
18999 format: true,
19000 autosave: false,
19001 },
19002 project,
19003 window,
19004 cx,
19005 )
19006 .detach_and_log_err(cx);
19007 }
19008 }
19009
19010 pub(crate) fn apply_selected_diff_hunks(
19011 &mut self,
19012 _: &ApplyDiffHunk,
19013 window: &mut Window,
19014 cx: &mut Context<Self>,
19015 ) {
19016 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19017 let snapshot = self.snapshot(window, cx);
19018 let hunks = snapshot.hunks_for_ranges(
19019 self.selections
19020 .all(cx)
19021 .into_iter()
19022 .map(|selection| selection.range()),
19023 );
19024 let mut ranges_by_buffer = HashMap::default();
19025 self.transact(window, cx, |editor, _window, cx| {
19026 for hunk in hunks {
19027 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19028 ranges_by_buffer
19029 .entry(buffer.clone())
19030 .or_insert_with(Vec::new)
19031 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19032 }
19033 }
19034
19035 for (buffer, ranges) in ranges_by_buffer {
19036 buffer.update(cx, |buffer, cx| {
19037 buffer.merge_into_base(ranges, cx);
19038 });
19039 }
19040 });
19041
19042 if let Some(project) = self.project.clone() {
19043 self.save(
19044 SaveOptions {
19045 format: true,
19046 autosave: false,
19047 },
19048 project,
19049 window,
19050 cx,
19051 )
19052 .detach_and_log_err(cx);
19053 }
19054 }
19055
19056 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19057 if hovered != self.gutter_hovered {
19058 self.gutter_hovered = hovered;
19059 cx.notify();
19060 }
19061 }
19062
19063 pub fn insert_blocks(
19064 &mut self,
19065 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19066 autoscroll: Option<Autoscroll>,
19067 cx: &mut Context<Self>,
19068 ) -> Vec<CustomBlockId> {
19069 let blocks = self
19070 .display_map
19071 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19072 if let Some(autoscroll) = autoscroll {
19073 self.request_autoscroll(autoscroll, cx);
19074 }
19075 cx.notify();
19076 blocks
19077 }
19078
19079 pub fn resize_blocks(
19080 &mut self,
19081 heights: HashMap<CustomBlockId, u32>,
19082 autoscroll: Option<Autoscroll>,
19083 cx: &mut Context<Self>,
19084 ) {
19085 self.display_map
19086 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19087 if let Some(autoscroll) = autoscroll {
19088 self.request_autoscroll(autoscroll, cx);
19089 }
19090 cx.notify();
19091 }
19092
19093 pub fn replace_blocks(
19094 &mut self,
19095 renderers: HashMap<CustomBlockId, RenderBlock>,
19096 autoscroll: Option<Autoscroll>,
19097 cx: &mut Context<Self>,
19098 ) {
19099 self.display_map
19100 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19101 if let Some(autoscroll) = autoscroll {
19102 self.request_autoscroll(autoscroll, cx);
19103 }
19104 cx.notify();
19105 }
19106
19107 pub fn remove_blocks(
19108 &mut self,
19109 block_ids: HashSet<CustomBlockId>,
19110 autoscroll: Option<Autoscroll>,
19111 cx: &mut Context<Self>,
19112 ) {
19113 self.display_map.update(cx, |display_map, cx| {
19114 display_map.remove_blocks(block_ids, cx)
19115 });
19116 if let Some(autoscroll) = autoscroll {
19117 self.request_autoscroll(autoscroll, cx);
19118 }
19119 cx.notify();
19120 }
19121
19122 pub fn row_for_block(
19123 &self,
19124 block_id: CustomBlockId,
19125 cx: &mut Context<Self>,
19126 ) -> Option<DisplayRow> {
19127 self.display_map
19128 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19129 }
19130
19131 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19132 self.focused_block = Some(focused_block);
19133 }
19134
19135 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19136 self.focused_block.take()
19137 }
19138
19139 pub fn insert_creases(
19140 &mut self,
19141 creases: impl IntoIterator<Item = Crease<Anchor>>,
19142 cx: &mut Context<Self>,
19143 ) -> Vec<CreaseId> {
19144 self.display_map
19145 .update(cx, |map, cx| map.insert_creases(creases, cx))
19146 }
19147
19148 pub fn remove_creases(
19149 &mut self,
19150 ids: impl IntoIterator<Item = CreaseId>,
19151 cx: &mut Context<Self>,
19152 ) -> Vec<(CreaseId, Range<Anchor>)> {
19153 self.display_map
19154 .update(cx, |map, cx| map.remove_creases(ids, cx))
19155 }
19156
19157 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19158 self.display_map
19159 .update(cx, |map, cx| map.snapshot(cx))
19160 .longest_row()
19161 }
19162
19163 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19164 self.display_map
19165 .update(cx, |map, cx| map.snapshot(cx))
19166 .max_point()
19167 }
19168
19169 pub fn text(&self, cx: &App) -> String {
19170 self.buffer.read(cx).read(cx).text()
19171 }
19172
19173 pub fn is_empty(&self, cx: &App) -> bool {
19174 self.buffer.read(cx).read(cx).is_empty()
19175 }
19176
19177 pub fn text_option(&self, cx: &App) -> Option<String> {
19178 let text = self.text(cx);
19179 let text = text.trim();
19180
19181 if text.is_empty() {
19182 return None;
19183 }
19184
19185 Some(text.to_string())
19186 }
19187
19188 pub fn set_text(
19189 &mut self,
19190 text: impl Into<Arc<str>>,
19191 window: &mut Window,
19192 cx: &mut Context<Self>,
19193 ) {
19194 self.transact(window, cx, |this, _, cx| {
19195 this.buffer
19196 .read(cx)
19197 .as_singleton()
19198 .expect("you can only call set_text on editors for singleton buffers")
19199 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19200 });
19201 }
19202
19203 pub fn display_text(&self, cx: &mut App) -> String {
19204 self.display_map
19205 .update(cx, |map, cx| map.snapshot(cx))
19206 .text()
19207 }
19208
19209 fn create_minimap(
19210 &self,
19211 minimap_settings: MinimapSettings,
19212 window: &mut Window,
19213 cx: &mut Context<Self>,
19214 ) -> Option<Entity<Self>> {
19215 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19216 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19217 }
19218
19219 fn initialize_new_minimap(
19220 &self,
19221 minimap_settings: MinimapSettings,
19222 window: &mut Window,
19223 cx: &mut Context<Self>,
19224 ) -> Entity<Self> {
19225 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19226
19227 let mut minimap = Editor::new_internal(
19228 EditorMode::Minimap {
19229 parent: cx.weak_entity(),
19230 },
19231 self.buffer.clone(),
19232 None,
19233 Some(self.display_map.clone()),
19234 window,
19235 cx,
19236 );
19237 minimap.scroll_manager.clone_state(&self.scroll_manager);
19238 minimap.set_text_style_refinement(TextStyleRefinement {
19239 font_size: Some(MINIMAP_FONT_SIZE),
19240 font_weight: Some(MINIMAP_FONT_WEIGHT),
19241 ..Default::default()
19242 });
19243 minimap.update_minimap_configuration(minimap_settings, cx);
19244 cx.new(|_| minimap)
19245 }
19246
19247 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19248 let current_line_highlight = minimap_settings
19249 .current_line_highlight
19250 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19251 self.set_current_line_highlight(Some(current_line_highlight));
19252 }
19253
19254 pub fn minimap(&self) -> Option<&Entity<Self>> {
19255 self.minimap
19256 .as_ref()
19257 .filter(|_| self.minimap_visibility.visible())
19258 }
19259
19260 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19261 let mut wrap_guides = smallvec![];
19262
19263 if self.show_wrap_guides == Some(false) {
19264 return wrap_guides;
19265 }
19266
19267 let settings = self.buffer.read(cx).language_settings(cx);
19268 if settings.show_wrap_guides {
19269 match self.soft_wrap_mode(cx) {
19270 SoftWrap::Column(soft_wrap) => {
19271 wrap_guides.push((soft_wrap as usize, true));
19272 }
19273 SoftWrap::Bounded(soft_wrap) => {
19274 wrap_guides.push((soft_wrap as usize, true));
19275 }
19276 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19277 }
19278 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19279 }
19280
19281 wrap_guides
19282 }
19283
19284 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19285 let settings = self.buffer.read(cx).language_settings(cx);
19286 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19287 match mode {
19288 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19289 SoftWrap::None
19290 }
19291 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19292 language_settings::SoftWrap::PreferredLineLength => {
19293 SoftWrap::Column(settings.preferred_line_length)
19294 }
19295 language_settings::SoftWrap::Bounded => {
19296 SoftWrap::Bounded(settings.preferred_line_length)
19297 }
19298 }
19299 }
19300
19301 pub fn set_soft_wrap_mode(
19302 &mut self,
19303 mode: language_settings::SoftWrap,
19304
19305 cx: &mut Context<Self>,
19306 ) {
19307 self.soft_wrap_mode_override = Some(mode);
19308 cx.notify();
19309 }
19310
19311 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19312 self.hard_wrap = hard_wrap;
19313 cx.notify();
19314 }
19315
19316 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19317 self.text_style_refinement = Some(style);
19318 }
19319
19320 /// called by the Element so we know what style we were most recently rendered with.
19321 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19322 // We intentionally do not inform the display map about the minimap style
19323 // so that wrapping is not recalculated and stays consistent for the editor
19324 // and its linked minimap.
19325 if !self.mode.is_minimap() {
19326 let font = style.text.font();
19327 let font_size = style.text.font_size.to_pixels(window.rem_size());
19328 let display_map = self
19329 .placeholder_display_map
19330 .as_ref()
19331 .filter(|_| self.is_empty(cx))
19332 .unwrap_or(&self.display_map);
19333
19334 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19335 }
19336 self.style = Some(style);
19337 }
19338
19339 pub fn style(&self) -> Option<&EditorStyle> {
19340 self.style.as_ref()
19341 }
19342
19343 // Called by the element. This method is not designed to be called outside of the editor
19344 // element's layout code because it does not notify when rewrapping is computed synchronously.
19345 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19346 if self.is_empty(cx) {
19347 self.placeholder_display_map
19348 .as_ref()
19349 .map_or(false, |display_map| {
19350 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19351 })
19352 } else {
19353 self.display_map
19354 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19355 }
19356 }
19357
19358 pub fn set_soft_wrap(&mut self) {
19359 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19360 }
19361
19362 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19363 if self.soft_wrap_mode_override.is_some() {
19364 self.soft_wrap_mode_override.take();
19365 } else {
19366 let soft_wrap = match self.soft_wrap_mode(cx) {
19367 SoftWrap::GitDiff => return,
19368 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19369 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19370 language_settings::SoftWrap::None
19371 }
19372 };
19373 self.soft_wrap_mode_override = Some(soft_wrap);
19374 }
19375 cx.notify();
19376 }
19377
19378 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19379 let Some(workspace) = self.workspace() else {
19380 return;
19381 };
19382 let fs = workspace.read(cx).app_state().fs.clone();
19383 let current_show = TabBarSettings::get_global(cx).show;
19384 update_settings_file(fs, cx, move |setting, _| {
19385 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19386 });
19387 }
19388
19389 pub fn toggle_indent_guides(
19390 &mut self,
19391 _: &ToggleIndentGuides,
19392 _: &mut Window,
19393 cx: &mut Context<Self>,
19394 ) {
19395 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19396 self.buffer
19397 .read(cx)
19398 .language_settings(cx)
19399 .indent_guides
19400 .enabled
19401 });
19402 self.show_indent_guides = Some(!currently_enabled);
19403 cx.notify();
19404 }
19405
19406 fn should_show_indent_guides(&self) -> Option<bool> {
19407 self.show_indent_guides
19408 }
19409
19410 pub fn toggle_line_numbers(
19411 &mut self,
19412 _: &ToggleLineNumbers,
19413 _: &mut Window,
19414 cx: &mut Context<Self>,
19415 ) {
19416 let mut editor_settings = EditorSettings::get_global(cx).clone();
19417 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19418 EditorSettings::override_global(editor_settings, cx);
19419 }
19420
19421 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19422 if let Some(show_line_numbers) = self.show_line_numbers {
19423 return show_line_numbers;
19424 }
19425 EditorSettings::get_global(cx).gutter.line_numbers
19426 }
19427
19428 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19429 self.use_relative_line_numbers
19430 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19431 }
19432
19433 pub fn toggle_relative_line_numbers(
19434 &mut self,
19435 _: &ToggleRelativeLineNumbers,
19436 _: &mut Window,
19437 cx: &mut Context<Self>,
19438 ) {
19439 let is_relative = self.should_use_relative_line_numbers(cx);
19440 self.set_relative_line_number(Some(!is_relative), cx)
19441 }
19442
19443 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19444 self.use_relative_line_numbers = is_relative;
19445 cx.notify();
19446 }
19447
19448 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19449 self.show_gutter = show_gutter;
19450 cx.notify();
19451 }
19452
19453 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19454 self.show_scrollbars = ScrollbarAxes {
19455 horizontal: show,
19456 vertical: show,
19457 };
19458 cx.notify();
19459 }
19460
19461 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19462 self.show_scrollbars.vertical = show;
19463 cx.notify();
19464 }
19465
19466 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19467 self.show_scrollbars.horizontal = show;
19468 cx.notify();
19469 }
19470
19471 pub fn set_minimap_visibility(
19472 &mut self,
19473 minimap_visibility: MinimapVisibility,
19474 window: &mut Window,
19475 cx: &mut Context<Self>,
19476 ) {
19477 if self.minimap_visibility != minimap_visibility {
19478 if minimap_visibility.visible() && self.minimap.is_none() {
19479 let minimap_settings = EditorSettings::get_global(cx).minimap;
19480 self.minimap =
19481 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19482 }
19483 self.minimap_visibility = minimap_visibility;
19484 cx.notify();
19485 }
19486 }
19487
19488 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19489 self.set_show_scrollbars(false, cx);
19490 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19491 }
19492
19493 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19494 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19495 }
19496
19497 /// Normally the text in full mode and auto height editors is padded on the
19498 /// left side by roughly half a character width for improved hit testing.
19499 ///
19500 /// Use this method to disable this for cases where this is not wanted (e.g.
19501 /// if you want to align the editor text with some other text above or below)
19502 /// or if you want to add this padding to single-line editors.
19503 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19504 self.offset_content = offset_content;
19505 cx.notify();
19506 }
19507
19508 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19509 self.show_line_numbers = Some(show_line_numbers);
19510 cx.notify();
19511 }
19512
19513 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19514 self.disable_expand_excerpt_buttons = true;
19515 cx.notify();
19516 }
19517
19518 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19519 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19520 cx.notify();
19521 }
19522
19523 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19524 self.show_code_actions = Some(show_code_actions);
19525 cx.notify();
19526 }
19527
19528 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19529 self.show_runnables = Some(show_runnables);
19530 cx.notify();
19531 }
19532
19533 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19534 self.show_breakpoints = Some(show_breakpoints);
19535 cx.notify();
19536 }
19537
19538 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19539 if self.display_map.read(cx).masked != masked {
19540 self.display_map.update(cx, |map, _| map.masked = masked);
19541 }
19542 cx.notify()
19543 }
19544
19545 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19546 self.show_wrap_guides = Some(show_wrap_guides);
19547 cx.notify();
19548 }
19549
19550 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19551 self.show_indent_guides = Some(show_indent_guides);
19552 cx.notify();
19553 }
19554
19555 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19556 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19557 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19558 && let Some(dir) = file.abs_path(cx).parent()
19559 {
19560 return Some(dir.to_owned());
19561 }
19562 }
19563
19564 None
19565 }
19566
19567 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19568 self.active_excerpt(cx)?
19569 .1
19570 .read(cx)
19571 .file()
19572 .and_then(|f| f.as_local())
19573 }
19574
19575 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19576 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19577 let buffer = buffer.read(cx);
19578 if let Some(project_path) = buffer.project_path(cx) {
19579 let project = self.project()?.read(cx);
19580 project.absolute_path(&project_path, cx)
19581 } else {
19582 buffer
19583 .file()
19584 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19585 }
19586 })
19587 }
19588
19589 pub fn reveal_in_finder(
19590 &mut self,
19591 _: &RevealInFileManager,
19592 _window: &mut Window,
19593 cx: &mut Context<Self>,
19594 ) {
19595 if let Some(target) = self.target_file(cx) {
19596 cx.reveal_path(&target.abs_path(cx));
19597 }
19598 }
19599
19600 pub fn copy_path(
19601 &mut self,
19602 _: &zed_actions::workspace::CopyPath,
19603 _window: &mut Window,
19604 cx: &mut Context<Self>,
19605 ) {
19606 if let Some(path) = self.target_file_abs_path(cx)
19607 && let Some(path) = path.to_str()
19608 {
19609 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19610 } else {
19611 cx.propagate();
19612 }
19613 }
19614
19615 pub fn copy_relative_path(
19616 &mut self,
19617 _: &zed_actions::workspace::CopyRelativePath,
19618 _window: &mut Window,
19619 cx: &mut Context<Self>,
19620 ) {
19621 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19622 let project = self.project()?.read(cx);
19623 let path = buffer.read(cx).file()?.path();
19624 let path = path.display(project.path_style(cx));
19625 Some(path)
19626 }) {
19627 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19628 } else {
19629 cx.propagate();
19630 }
19631 }
19632
19633 /// Returns the project path for the editor's buffer, if any buffer is
19634 /// opened in the editor.
19635 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19636 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19637 buffer.read(cx).project_path(cx)
19638 } else {
19639 None
19640 }
19641 }
19642
19643 // Returns true if the editor handled a go-to-line request
19644 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19645 maybe!({
19646 let breakpoint_store = self.breakpoint_store.as_ref()?;
19647
19648 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19649 else {
19650 self.clear_row_highlights::<ActiveDebugLine>();
19651 return None;
19652 };
19653
19654 let position = active_stack_frame.position;
19655 let buffer_id = position.buffer_id?;
19656 let snapshot = self
19657 .project
19658 .as_ref()?
19659 .read(cx)
19660 .buffer_for_id(buffer_id, cx)?
19661 .read(cx)
19662 .snapshot();
19663
19664 let mut handled = false;
19665 for (id, ExcerptRange { context, .. }) in
19666 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19667 {
19668 if context.start.cmp(&position, &snapshot).is_ge()
19669 || context.end.cmp(&position, &snapshot).is_lt()
19670 {
19671 continue;
19672 }
19673 let snapshot = self.buffer.read(cx).snapshot(cx);
19674 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19675
19676 handled = true;
19677 self.clear_row_highlights::<ActiveDebugLine>();
19678
19679 self.go_to_line::<ActiveDebugLine>(
19680 multibuffer_anchor,
19681 Some(cx.theme().colors().editor_debugger_active_line_background),
19682 window,
19683 cx,
19684 );
19685
19686 cx.notify();
19687 }
19688
19689 handled.then_some(())
19690 })
19691 .is_some()
19692 }
19693
19694 pub fn copy_file_name_without_extension(
19695 &mut self,
19696 _: &CopyFileNameWithoutExtension,
19697 _: &mut Window,
19698 cx: &mut Context<Self>,
19699 ) {
19700 if let Some(file) = self.target_file(cx)
19701 && let Some(file_stem) = file.path().file_stem()
19702 {
19703 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19704 }
19705 }
19706
19707 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19708 if let Some(file) = self.target_file(cx)
19709 && let Some(name) = file.path().file_name()
19710 {
19711 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19712 }
19713 }
19714
19715 pub fn toggle_git_blame(
19716 &mut self,
19717 _: &::git::Blame,
19718 window: &mut Window,
19719 cx: &mut Context<Self>,
19720 ) {
19721 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19722
19723 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19724 self.start_git_blame(true, window, cx);
19725 }
19726
19727 cx.notify();
19728 }
19729
19730 pub fn toggle_git_blame_inline(
19731 &mut self,
19732 _: &ToggleGitBlameInline,
19733 window: &mut Window,
19734 cx: &mut Context<Self>,
19735 ) {
19736 self.toggle_git_blame_inline_internal(true, window, cx);
19737 cx.notify();
19738 }
19739
19740 pub fn open_git_blame_commit(
19741 &mut self,
19742 _: &OpenGitBlameCommit,
19743 window: &mut Window,
19744 cx: &mut Context<Self>,
19745 ) {
19746 self.open_git_blame_commit_internal(window, cx);
19747 }
19748
19749 fn open_git_blame_commit_internal(
19750 &mut self,
19751 window: &mut Window,
19752 cx: &mut Context<Self>,
19753 ) -> Option<()> {
19754 let blame = self.blame.as_ref()?;
19755 let snapshot = self.snapshot(window, cx);
19756 let cursor = self.selections.newest::<Point>(cx).head();
19757 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
19758 let (_, blame_entry) = blame
19759 .update(cx, |blame, cx| {
19760 blame
19761 .blame_for_rows(
19762 &[RowInfo {
19763 buffer_id: Some(buffer.remote_id()),
19764 buffer_row: Some(point.row),
19765 ..Default::default()
19766 }],
19767 cx,
19768 )
19769 .next()
19770 })
19771 .flatten()?;
19772 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19773 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19774 let workspace = self.workspace()?.downgrade();
19775 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19776 None
19777 }
19778
19779 pub fn git_blame_inline_enabled(&self) -> bool {
19780 self.git_blame_inline_enabled
19781 }
19782
19783 pub fn toggle_selection_menu(
19784 &mut self,
19785 _: &ToggleSelectionMenu,
19786 _: &mut Window,
19787 cx: &mut Context<Self>,
19788 ) {
19789 self.show_selection_menu = self
19790 .show_selection_menu
19791 .map(|show_selections_menu| !show_selections_menu)
19792 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19793
19794 cx.notify();
19795 }
19796
19797 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19798 self.show_selection_menu
19799 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19800 }
19801
19802 fn start_git_blame(
19803 &mut self,
19804 user_triggered: bool,
19805 window: &mut Window,
19806 cx: &mut Context<Self>,
19807 ) {
19808 if let Some(project) = self.project() {
19809 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19810 && buffer.read(cx).file().is_none()
19811 {
19812 return;
19813 }
19814
19815 let focused = self.focus_handle(cx).contains_focused(window, cx);
19816
19817 let project = project.clone();
19818 let blame = cx
19819 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19820 self.blame_subscription =
19821 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19822 self.blame = Some(blame);
19823 }
19824 }
19825
19826 fn toggle_git_blame_inline_internal(
19827 &mut self,
19828 user_triggered: bool,
19829 window: &mut Window,
19830 cx: &mut Context<Self>,
19831 ) {
19832 if self.git_blame_inline_enabled {
19833 self.git_blame_inline_enabled = false;
19834 self.show_git_blame_inline = false;
19835 self.show_git_blame_inline_delay_task.take();
19836 } else {
19837 self.git_blame_inline_enabled = true;
19838 self.start_git_blame_inline(user_triggered, window, cx);
19839 }
19840
19841 cx.notify();
19842 }
19843
19844 fn start_git_blame_inline(
19845 &mut self,
19846 user_triggered: bool,
19847 window: &mut Window,
19848 cx: &mut Context<Self>,
19849 ) {
19850 self.start_git_blame(user_triggered, window, cx);
19851
19852 if ProjectSettings::get_global(cx)
19853 .git
19854 .inline_blame_delay()
19855 .is_some()
19856 {
19857 self.start_inline_blame_timer(window, cx);
19858 } else {
19859 self.show_git_blame_inline = true
19860 }
19861 }
19862
19863 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19864 self.blame.as_ref()
19865 }
19866
19867 pub fn show_git_blame_gutter(&self) -> bool {
19868 self.show_git_blame_gutter
19869 }
19870
19871 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19872 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19873 }
19874
19875 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19876 self.show_git_blame_inline
19877 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19878 && !self.newest_selection_head_on_empty_line(cx)
19879 && self.has_blame_entries(cx)
19880 }
19881
19882 fn has_blame_entries(&self, cx: &App) -> bool {
19883 self.blame()
19884 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19885 }
19886
19887 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19888 let cursor_anchor = self.selections.newest_anchor().head();
19889
19890 let snapshot = self.buffer.read(cx).snapshot(cx);
19891 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19892
19893 snapshot.line_len(buffer_row) == 0
19894 }
19895
19896 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19897 let buffer_and_selection = maybe!({
19898 let selection = self.selections.newest::<Point>(cx);
19899 let selection_range = selection.range();
19900
19901 let multi_buffer = self.buffer().read(cx);
19902 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19903 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19904
19905 let (buffer, range, _) = if selection.reversed {
19906 buffer_ranges.first()
19907 } else {
19908 buffer_ranges.last()
19909 }?;
19910
19911 let selection = text::ToPoint::to_point(&range.start, buffer).row
19912 ..text::ToPoint::to_point(&range.end, buffer).row;
19913 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19914 });
19915
19916 let Some((buffer, selection)) = buffer_and_selection else {
19917 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19918 };
19919
19920 let Some(project) = self.project() else {
19921 return Task::ready(Err(anyhow!("editor does not have project")));
19922 };
19923
19924 project.update(cx, |project, cx| {
19925 project.get_permalink_to_line(&buffer, selection, cx)
19926 })
19927 }
19928
19929 pub fn copy_permalink_to_line(
19930 &mut self,
19931 _: &CopyPermalinkToLine,
19932 window: &mut Window,
19933 cx: &mut Context<Self>,
19934 ) {
19935 let permalink_task = self.get_permalink_to_line(cx);
19936 let workspace = self.workspace();
19937
19938 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19939 Ok(permalink) => {
19940 cx.update(|_, cx| {
19941 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19942 })
19943 .ok();
19944 }
19945 Err(err) => {
19946 let message = format!("Failed to copy permalink: {err}");
19947
19948 anyhow::Result::<()>::Err(err).log_err();
19949
19950 if let Some(workspace) = workspace {
19951 workspace
19952 .update_in(cx, |workspace, _, cx| {
19953 struct CopyPermalinkToLine;
19954
19955 workspace.show_toast(
19956 Toast::new(
19957 NotificationId::unique::<CopyPermalinkToLine>(),
19958 message,
19959 ),
19960 cx,
19961 )
19962 })
19963 .ok();
19964 }
19965 }
19966 })
19967 .detach();
19968 }
19969
19970 pub fn copy_file_location(
19971 &mut self,
19972 _: &CopyFileLocation,
19973 _: &mut Window,
19974 cx: &mut Context<Self>,
19975 ) {
19976 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19977 if let Some(file) = self.target_file(cx) {
19978 let path = file.path().display(file.path_style(cx));
19979 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19980 }
19981 }
19982
19983 pub fn open_permalink_to_line(
19984 &mut self,
19985 _: &OpenPermalinkToLine,
19986 window: &mut Window,
19987 cx: &mut Context<Self>,
19988 ) {
19989 let permalink_task = self.get_permalink_to_line(cx);
19990 let workspace = self.workspace();
19991
19992 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19993 Ok(permalink) => {
19994 cx.update(|_, cx| {
19995 cx.open_url(permalink.as_ref());
19996 })
19997 .ok();
19998 }
19999 Err(err) => {
20000 let message = format!("Failed to open permalink: {err}");
20001
20002 anyhow::Result::<()>::Err(err).log_err();
20003
20004 if let Some(workspace) = workspace {
20005 workspace
20006 .update(cx, |workspace, cx| {
20007 struct OpenPermalinkToLine;
20008
20009 workspace.show_toast(
20010 Toast::new(
20011 NotificationId::unique::<OpenPermalinkToLine>(),
20012 message,
20013 ),
20014 cx,
20015 )
20016 })
20017 .ok();
20018 }
20019 }
20020 })
20021 .detach();
20022 }
20023
20024 pub fn insert_uuid_v4(
20025 &mut self,
20026 _: &InsertUuidV4,
20027 window: &mut Window,
20028 cx: &mut Context<Self>,
20029 ) {
20030 self.insert_uuid(UuidVersion::V4, window, cx);
20031 }
20032
20033 pub fn insert_uuid_v7(
20034 &mut self,
20035 _: &InsertUuidV7,
20036 window: &mut Window,
20037 cx: &mut Context<Self>,
20038 ) {
20039 self.insert_uuid(UuidVersion::V7, window, cx);
20040 }
20041
20042 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20043 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20044 self.transact(window, cx, |this, window, cx| {
20045 let edits = this
20046 .selections
20047 .all::<Point>(cx)
20048 .into_iter()
20049 .map(|selection| {
20050 let uuid = match version {
20051 UuidVersion::V4 => uuid::Uuid::new_v4(),
20052 UuidVersion::V7 => uuid::Uuid::now_v7(),
20053 };
20054
20055 (selection.range(), uuid.to_string())
20056 });
20057 this.edit(edits, cx);
20058 this.refresh_edit_prediction(true, false, window, cx);
20059 });
20060 }
20061
20062 pub fn open_selections_in_multibuffer(
20063 &mut self,
20064 _: &OpenSelectionsInMultibuffer,
20065 window: &mut Window,
20066 cx: &mut Context<Self>,
20067 ) {
20068 let multibuffer = self.buffer.read(cx);
20069
20070 let Some(buffer) = multibuffer.as_singleton() else {
20071 return;
20072 };
20073
20074 let Some(workspace) = self.workspace() else {
20075 return;
20076 };
20077
20078 let title = multibuffer.title(cx).to_string();
20079
20080 let locations = self
20081 .selections
20082 .all_anchors(cx)
20083 .iter()
20084 .map(|selection| {
20085 (
20086 buffer.clone(),
20087 (selection.start.text_anchor..selection.end.text_anchor)
20088 .to_point(buffer.read(cx)),
20089 )
20090 })
20091 .into_group_map();
20092
20093 cx.spawn_in(window, async move |_, cx| {
20094 workspace.update_in(cx, |workspace, window, cx| {
20095 Self::open_locations_in_multibuffer(
20096 workspace,
20097 locations,
20098 format!("Selections for '{title}'"),
20099 false,
20100 MultibufferSelectionMode::All,
20101 window,
20102 cx,
20103 );
20104 })
20105 })
20106 .detach();
20107 }
20108
20109 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20110 /// last highlight added will be used.
20111 ///
20112 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20113 pub fn highlight_rows<T: 'static>(
20114 &mut self,
20115 range: Range<Anchor>,
20116 color: Hsla,
20117 options: RowHighlightOptions,
20118 cx: &mut Context<Self>,
20119 ) {
20120 let snapshot = self.buffer().read(cx).snapshot(cx);
20121 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20122 let ix = row_highlights.binary_search_by(|highlight| {
20123 Ordering::Equal
20124 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20125 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20126 });
20127
20128 if let Err(mut ix) = ix {
20129 let index = post_inc(&mut self.highlight_order);
20130
20131 // If this range intersects with the preceding highlight, then merge it with
20132 // the preceding highlight. Otherwise insert a new highlight.
20133 let mut merged = false;
20134 if ix > 0 {
20135 let prev_highlight = &mut row_highlights[ix - 1];
20136 if prev_highlight
20137 .range
20138 .end
20139 .cmp(&range.start, &snapshot)
20140 .is_ge()
20141 {
20142 ix -= 1;
20143 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20144 prev_highlight.range.end = range.end;
20145 }
20146 merged = true;
20147 prev_highlight.index = index;
20148 prev_highlight.color = color;
20149 prev_highlight.options = options;
20150 }
20151 }
20152
20153 if !merged {
20154 row_highlights.insert(
20155 ix,
20156 RowHighlight {
20157 range,
20158 index,
20159 color,
20160 options,
20161 type_id: TypeId::of::<T>(),
20162 },
20163 );
20164 }
20165
20166 // If any of the following highlights intersect with this one, merge them.
20167 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20168 let highlight = &row_highlights[ix];
20169 if next_highlight
20170 .range
20171 .start
20172 .cmp(&highlight.range.end, &snapshot)
20173 .is_le()
20174 {
20175 if next_highlight
20176 .range
20177 .end
20178 .cmp(&highlight.range.end, &snapshot)
20179 .is_gt()
20180 {
20181 row_highlights[ix].range.end = next_highlight.range.end;
20182 }
20183 row_highlights.remove(ix + 1);
20184 } else {
20185 break;
20186 }
20187 }
20188 }
20189 }
20190
20191 /// Remove any highlighted row ranges of the given type that intersect the
20192 /// given ranges.
20193 pub fn remove_highlighted_rows<T: 'static>(
20194 &mut self,
20195 ranges_to_remove: Vec<Range<Anchor>>,
20196 cx: &mut Context<Self>,
20197 ) {
20198 let snapshot = self.buffer().read(cx).snapshot(cx);
20199 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20200 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20201 row_highlights.retain(|highlight| {
20202 while let Some(range_to_remove) = ranges_to_remove.peek() {
20203 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20204 Ordering::Less | Ordering::Equal => {
20205 ranges_to_remove.next();
20206 }
20207 Ordering::Greater => {
20208 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20209 Ordering::Less | Ordering::Equal => {
20210 return false;
20211 }
20212 Ordering::Greater => break,
20213 }
20214 }
20215 }
20216 }
20217
20218 true
20219 })
20220 }
20221
20222 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20223 pub fn clear_row_highlights<T: 'static>(&mut self) {
20224 self.highlighted_rows.remove(&TypeId::of::<T>());
20225 }
20226
20227 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20228 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20229 self.highlighted_rows
20230 .get(&TypeId::of::<T>())
20231 .map_or(&[] as &[_], |vec| vec.as_slice())
20232 .iter()
20233 .map(|highlight| (highlight.range.clone(), highlight.color))
20234 }
20235
20236 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20237 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20238 /// Allows to ignore certain kinds of highlights.
20239 pub fn highlighted_display_rows(
20240 &self,
20241 window: &mut Window,
20242 cx: &mut App,
20243 ) -> BTreeMap<DisplayRow, LineHighlight> {
20244 let snapshot = self.snapshot(window, cx);
20245 let mut used_highlight_orders = HashMap::default();
20246 self.highlighted_rows
20247 .iter()
20248 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20249 .fold(
20250 BTreeMap::<DisplayRow, LineHighlight>::new(),
20251 |mut unique_rows, highlight| {
20252 let start = highlight.range.start.to_display_point(&snapshot);
20253 let end = highlight.range.end.to_display_point(&snapshot);
20254 let start_row = start.row().0;
20255 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20256 && end.column() == 0
20257 {
20258 end.row().0.saturating_sub(1)
20259 } else {
20260 end.row().0
20261 };
20262 for row in start_row..=end_row {
20263 let used_index =
20264 used_highlight_orders.entry(row).or_insert(highlight.index);
20265 if highlight.index >= *used_index {
20266 *used_index = highlight.index;
20267 unique_rows.insert(
20268 DisplayRow(row),
20269 LineHighlight {
20270 include_gutter: highlight.options.include_gutter,
20271 border: None,
20272 background: highlight.color.into(),
20273 type_id: Some(highlight.type_id),
20274 },
20275 );
20276 }
20277 }
20278 unique_rows
20279 },
20280 )
20281 }
20282
20283 pub fn highlighted_display_row_for_autoscroll(
20284 &self,
20285 snapshot: &DisplaySnapshot,
20286 ) -> Option<DisplayRow> {
20287 self.highlighted_rows
20288 .values()
20289 .flat_map(|highlighted_rows| highlighted_rows.iter())
20290 .filter_map(|highlight| {
20291 if highlight.options.autoscroll {
20292 Some(highlight.range.start.to_display_point(snapshot).row())
20293 } else {
20294 None
20295 }
20296 })
20297 .min()
20298 }
20299
20300 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20301 self.highlight_background::<SearchWithinRange>(
20302 ranges,
20303 |colors| colors.colors().editor_document_highlight_read_background,
20304 cx,
20305 )
20306 }
20307
20308 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20309 self.breadcrumb_header = Some(new_header);
20310 }
20311
20312 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20313 self.clear_background_highlights::<SearchWithinRange>(cx);
20314 }
20315
20316 pub fn highlight_background<T: 'static>(
20317 &mut self,
20318 ranges: &[Range<Anchor>],
20319 color_fetcher: fn(&Theme) -> Hsla,
20320 cx: &mut Context<Self>,
20321 ) {
20322 self.background_highlights.insert(
20323 HighlightKey::Type(TypeId::of::<T>()),
20324 (color_fetcher, Arc::from(ranges)),
20325 );
20326 self.scrollbar_marker_state.dirty = true;
20327 cx.notify();
20328 }
20329
20330 pub fn highlight_background_key<T: 'static>(
20331 &mut self,
20332 key: usize,
20333 ranges: &[Range<Anchor>],
20334 color_fetcher: fn(&Theme) -> Hsla,
20335 cx: &mut Context<Self>,
20336 ) {
20337 self.background_highlights.insert(
20338 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20339 (color_fetcher, Arc::from(ranges)),
20340 );
20341 self.scrollbar_marker_state.dirty = true;
20342 cx.notify();
20343 }
20344
20345 pub fn clear_background_highlights<T: 'static>(
20346 &mut self,
20347 cx: &mut Context<Self>,
20348 ) -> Option<BackgroundHighlight> {
20349 let text_highlights = self
20350 .background_highlights
20351 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20352 if !text_highlights.1.is_empty() {
20353 self.scrollbar_marker_state.dirty = true;
20354 cx.notify();
20355 }
20356 Some(text_highlights)
20357 }
20358
20359 pub fn highlight_gutter<T: 'static>(
20360 &mut self,
20361 ranges: impl Into<Vec<Range<Anchor>>>,
20362 color_fetcher: fn(&App) -> Hsla,
20363 cx: &mut Context<Self>,
20364 ) {
20365 self.gutter_highlights
20366 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20367 cx.notify();
20368 }
20369
20370 pub fn clear_gutter_highlights<T: 'static>(
20371 &mut self,
20372 cx: &mut Context<Self>,
20373 ) -> Option<GutterHighlight> {
20374 cx.notify();
20375 self.gutter_highlights.remove(&TypeId::of::<T>())
20376 }
20377
20378 pub fn insert_gutter_highlight<T: 'static>(
20379 &mut self,
20380 range: Range<Anchor>,
20381 color_fetcher: fn(&App) -> Hsla,
20382 cx: &mut Context<Self>,
20383 ) {
20384 let snapshot = self.buffer().read(cx).snapshot(cx);
20385 let mut highlights = self
20386 .gutter_highlights
20387 .remove(&TypeId::of::<T>())
20388 .map(|(_, highlights)| highlights)
20389 .unwrap_or_default();
20390 let ix = highlights.binary_search_by(|highlight| {
20391 Ordering::Equal
20392 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20393 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20394 });
20395 if let Err(ix) = ix {
20396 highlights.insert(ix, range);
20397 }
20398 self.gutter_highlights
20399 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20400 }
20401
20402 pub fn remove_gutter_highlights<T: 'static>(
20403 &mut self,
20404 ranges_to_remove: Vec<Range<Anchor>>,
20405 cx: &mut Context<Self>,
20406 ) {
20407 let snapshot = self.buffer().read(cx).snapshot(cx);
20408 let Some((color_fetcher, mut gutter_highlights)) =
20409 self.gutter_highlights.remove(&TypeId::of::<T>())
20410 else {
20411 return;
20412 };
20413 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20414 gutter_highlights.retain(|highlight| {
20415 while let Some(range_to_remove) = ranges_to_remove.peek() {
20416 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20417 Ordering::Less | Ordering::Equal => {
20418 ranges_to_remove.next();
20419 }
20420 Ordering::Greater => {
20421 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20422 Ordering::Less | Ordering::Equal => {
20423 return false;
20424 }
20425 Ordering::Greater => break,
20426 }
20427 }
20428 }
20429 }
20430
20431 true
20432 });
20433 self.gutter_highlights
20434 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20435 }
20436
20437 #[cfg(feature = "test-support")]
20438 pub fn all_text_highlights(
20439 &self,
20440 window: &mut Window,
20441 cx: &mut Context<Self>,
20442 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20443 let snapshot = self.snapshot(window, cx);
20444 self.display_map.update(cx, |display_map, _| {
20445 display_map
20446 .all_text_highlights()
20447 .map(|highlight| {
20448 let (style, ranges) = highlight.as_ref();
20449 (
20450 *style,
20451 ranges
20452 .iter()
20453 .map(|range| range.clone().to_display_points(&snapshot))
20454 .collect(),
20455 )
20456 })
20457 .collect()
20458 })
20459 }
20460
20461 #[cfg(feature = "test-support")]
20462 pub fn all_text_background_highlights(
20463 &self,
20464 window: &mut Window,
20465 cx: &mut Context<Self>,
20466 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20467 let snapshot = self.snapshot(window, cx);
20468 let buffer = &snapshot.buffer_snapshot();
20469 let start = buffer.anchor_before(0);
20470 let end = buffer.anchor_after(buffer.len());
20471 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20472 }
20473
20474 #[cfg(any(test, feature = "test-support"))]
20475 pub fn sorted_background_highlights_in_range(
20476 &self,
20477 search_range: Range<Anchor>,
20478 display_snapshot: &DisplaySnapshot,
20479 theme: &Theme,
20480 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20481 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20482 res.sort_by(|a, b| {
20483 a.0.start
20484 .cmp(&b.0.start)
20485 .then_with(|| a.0.end.cmp(&b.0.end))
20486 .then_with(|| a.1.cmp(&b.1))
20487 });
20488 res
20489 }
20490
20491 #[cfg(feature = "test-support")]
20492 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20493 let snapshot = self.buffer().read(cx).snapshot(cx);
20494
20495 let highlights = self
20496 .background_highlights
20497 .get(&HighlightKey::Type(TypeId::of::<
20498 items::BufferSearchHighlights,
20499 >()));
20500
20501 if let Some((_color, ranges)) = highlights {
20502 ranges
20503 .iter()
20504 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20505 .collect_vec()
20506 } else {
20507 vec![]
20508 }
20509 }
20510
20511 fn document_highlights_for_position<'a>(
20512 &'a self,
20513 position: Anchor,
20514 buffer: &'a MultiBufferSnapshot,
20515 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20516 let read_highlights = self
20517 .background_highlights
20518 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20519 .map(|h| &h.1);
20520 let write_highlights = self
20521 .background_highlights
20522 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20523 .map(|h| &h.1);
20524 let left_position = position.bias_left(buffer);
20525 let right_position = position.bias_right(buffer);
20526 read_highlights
20527 .into_iter()
20528 .chain(write_highlights)
20529 .flat_map(move |ranges| {
20530 let start_ix = match ranges.binary_search_by(|probe| {
20531 let cmp = probe.end.cmp(&left_position, buffer);
20532 if cmp.is_ge() {
20533 Ordering::Greater
20534 } else {
20535 Ordering::Less
20536 }
20537 }) {
20538 Ok(i) | Err(i) => i,
20539 };
20540
20541 ranges[start_ix..]
20542 .iter()
20543 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20544 })
20545 }
20546
20547 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20548 self.background_highlights
20549 .get(&HighlightKey::Type(TypeId::of::<T>()))
20550 .is_some_and(|(_, highlights)| !highlights.is_empty())
20551 }
20552
20553 /// Returns all background highlights for a given range.
20554 ///
20555 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20556 pub fn background_highlights_in_range(
20557 &self,
20558 search_range: Range<Anchor>,
20559 display_snapshot: &DisplaySnapshot,
20560 theme: &Theme,
20561 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20562 let mut results = Vec::new();
20563 for (color_fetcher, ranges) in self.background_highlights.values() {
20564 let color = color_fetcher(theme);
20565 let start_ix = match ranges.binary_search_by(|probe| {
20566 let cmp = probe
20567 .end
20568 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20569 if cmp.is_gt() {
20570 Ordering::Greater
20571 } else {
20572 Ordering::Less
20573 }
20574 }) {
20575 Ok(i) | Err(i) => i,
20576 };
20577 for range in &ranges[start_ix..] {
20578 if range
20579 .start
20580 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20581 .is_ge()
20582 {
20583 break;
20584 }
20585
20586 let start = range.start.to_display_point(display_snapshot);
20587 let end = range.end.to_display_point(display_snapshot);
20588 results.push((start..end, color))
20589 }
20590 }
20591 results
20592 }
20593
20594 pub fn gutter_highlights_in_range(
20595 &self,
20596 search_range: Range<Anchor>,
20597 display_snapshot: &DisplaySnapshot,
20598 cx: &App,
20599 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20600 let mut results = Vec::new();
20601 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20602 let color = color_fetcher(cx);
20603 let start_ix = match ranges.binary_search_by(|probe| {
20604 let cmp = probe
20605 .end
20606 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20607 if cmp.is_gt() {
20608 Ordering::Greater
20609 } else {
20610 Ordering::Less
20611 }
20612 }) {
20613 Ok(i) | Err(i) => i,
20614 };
20615 for range in &ranges[start_ix..] {
20616 if range
20617 .start
20618 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20619 .is_ge()
20620 {
20621 break;
20622 }
20623
20624 let start = range.start.to_display_point(display_snapshot);
20625 let end = range.end.to_display_point(display_snapshot);
20626 results.push((start..end, color))
20627 }
20628 }
20629 results
20630 }
20631
20632 /// Get the text ranges corresponding to the redaction query
20633 pub fn redacted_ranges(
20634 &self,
20635 search_range: Range<Anchor>,
20636 display_snapshot: &DisplaySnapshot,
20637 cx: &App,
20638 ) -> Vec<Range<DisplayPoint>> {
20639 display_snapshot
20640 .buffer_snapshot()
20641 .redacted_ranges(search_range, |file| {
20642 if let Some(file) = file {
20643 file.is_private()
20644 && EditorSettings::get(
20645 Some(SettingsLocation {
20646 worktree_id: file.worktree_id(cx),
20647 path: file.path().as_ref(),
20648 }),
20649 cx,
20650 )
20651 .redact_private_values
20652 } else {
20653 false
20654 }
20655 })
20656 .map(|range| {
20657 range.start.to_display_point(display_snapshot)
20658 ..range.end.to_display_point(display_snapshot)
20659 })
20660 .collect()
20661 }
20662
20663 pub fn highlight_text_key<T: 'static>(
20664 &mut self,
20665 key: usize,
20666 ranges: Vec<Range<Anchor>>,
20667 style: HighlightStyle,
20668 cx: &mut Context<Self>,
20669 ) {
20670 self.display_map.update(cx, |map, _| {
20671 map.highlight_text(
20672 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20673 ranges,
20674 style,
20675 );
20676 });
20677 cx.notify();
20678 }
20679
20680 pub fn highlight_text<T: 'static>(
20681 &mut self,
20682 ranges: Vec<Range<Anchor>>,
20683 style: HighlightStyle,
20684 cx: &mut Context<Self>,
20685 ) {
20686 self.display_map.update(cx, |map, _| {
20687 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20688 });
20689 cx.notify();
20690 }
20691
20692 pub(crate) fn highlight_inlays<T: 'static>(
20693 &mut self,
20694 highlights: Vec<InlayHighlight>,
20695 style: HighlightStyle,
20696 cx: &mut Context<Self>,
20697 ) {
20698 self.display_map.update(cx, |map, _| {
20699 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20700 });
20701 cx.notify();
20702 }
20703
20704 pub fn text_highlights<'a, T: 'static>(
20705 &'a self,
20706 cx: &'a App,
20707 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20708 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20709 }
20710
20711 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20712 let cleared = self
20713 .display_map
20714 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20715 if cleared {
20716 cx.notify();
20717 }
20718 }
20719
20720 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20721 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20722 && self.focus_handle.is_focused(window)
20723 }
20724
20725 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20726 self.show_cursor_when_unfocused = is_enabled;
20727 cx.notify();
20728 }
20729
20730 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20731 cx.notify();
20732 }
20733
20734 fn on_debug_session_event(
20735 &mut self,
20736 _session: Entity<Session>,
20737 event: &SessionEvent,
20738 cx: &mut Context<Self>,
20739 ) {
20740 if let SessionEvent::InvalidateInlineValue = event {
20741 self.refresh_inline_values(cx);
20742 }
20743 }
20744
20745 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20746 let Some(project) = self.project.clone() else {
20747 return;
20748 };
20749
20750 if !self.inline_value_cache.enabled {
20751 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20752 self.splice_inlays(&inlays, Vec::new(), cx);
20753 return;
20754 }
20755
20756 let current_execution_position = self
20757 .highlighted_rows
20758 .get(&TypeId::of::<ActiveDebugLine>())
20759 .and_then(|lines| lines.last().map(|line| line.range.end));
20760
20761 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20762 let inline_values = editor
20763 .update(cx, |editor, cx| {
20764 let Some(current_execution_position) = current_execution_position else {
20765 return Some(Task::ready(Ok(Vec::new())));
20766 };
20767
20768 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20769 let snapshot = buffer.snapshot(cx);
20770
20771 let excerpt = snapshot.excerpt_containing(
20772 current_execution_position..current_execution_position,
20773 )?;
20774
20775 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20776 })?;
20777
20778 let range =
20779 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20780
20781 project.inline_values(buffer, range, cx)
20782 })
20783 .ok()
20784 .flatten()?
20785 .await
20786 .context("refreshing debugger inlays")
20787 .log_err()?;
20788
20789 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20790
20791 for (buffer_id, inline_value) in inline_values
20792 .into_iter()
20793 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20794 {
20795 buffer_inline_values
20796 .entry(buffer_id)
20797 .or_default()
20798 .push(inline_value);
20799 }
20800
20801 editor
20802 .update(cx, |editor, cx| {
20803 let snapshot = editor.buffer.read(cx).snapshot(cx);
20804 let mut new_inlays = Vec::default();
20805
20806 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20807 let buffer_id = buffer_snapshot.remote_id();
20808 buffer_inline_values
20809 .get(&buffer_id)
20810 .into_iter()
20811 .flatten()
20812 .for_each(|hint| {
20813 let inlay = Inlay::debugger(
20814 post_inc(&mut editor.next_inlay_id),
20815 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20816 hint.text(),
20817 );
20818 if !inlay.text().chars().contains(&'\n') {
20819 new_inlays.push(inlay);
20820 }
20821 });
20822 }
20823
20824 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20825 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20826
20827 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20828 })
20829 .ok()?;
20830 Some(())
20831 });
20832 }
20833
20834 fn on_buffer_event(
20835 &mut self,
20836 multibuffer: &Entity<MultiBuffer>,
20837 event: &multi_buffer::Event,
20838 window: &mut Window,
20839 cx: &mut Context<Self>,
20840 ) {
20841 match event {
20842 multi_buffer::Event::Edited { edited_buffer } => {
20843 self.scrollbar_marker_state.dirty = true;
20844 self.active_indent_guides_state.dirty = true;
20845 self.refresh_active_diagnostics(cx);
20846 self.refresh_code_actions(window, cx);
20847 self.refresh_selected_text_highlights(true, window, cx);
20848 self.refresh_single_line_folds(window, cx);
20849 refresh_matching_bracket_highlights(self, cx);
20850 if self.has_active_edit_prediction() {
20851 self.update_visible_edit_prediction(window, cx);
20852 }
20853
20854 if let Some(edited_buffer) = edited_buffer {
20855 if edited_buffer.read(cx).file().is_none() {
20856 cx.emit(EditorEvent::TitleChanged);
20857 }
20858
20859 let buffer_id = edited_buffer.read(cx).remote_id();
20860 if let Some(project) = self.project.clone() {
20861 self.register_buffer(buffer_id, cx);
20862 self.update_lsp_data(Some(buffer_id), window, cx);
20863 #[allow(clippy::mutable_key_type)]
20864 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20865 multibuffer
20866 .all_buffers()
20867 .into_iter()
20868 .filter_map(|buffer| {
20869 buffer.update(cx, |buffer, cx| {
20870 let language = buffer.language()?;
20871 let should_discard = project.update(cx, |project, cx| {
20872 project.is_local()
20873 && !project.has_language_servers_for(buffer, cx)
20874 });
20875 should_discard.not().then_some(language.clone())
20876 })
20877 })
20878 .collect::<HashSet<_>>()
20879 });
20880 if !languages_affected.is_empty() {
20881 self.refresh_inlay_hints(
20882 InlayHintRefreshReason::BufferEdited(languages_affected),
20883 cx,
20884 );
20885 }
20886 }
20887 }
20888
20889 cx.emit(EditorEvent::BufferEdited);
20890 cx.emit(SearchEvent::MatchesInvalidated);
20891
20892 let Some(project) = &self.project else { return };
20893 let (telemetry, is_via_ssh) = {
20894 let project = project.read(cx);
20895 let telemetry = project.client().telemetry().clone();
20896 let is_via_ssh = project.is_via_remote_server();
20897 (telemetry, is_via_ssh)
20898 };
20899 telemetry.log_edit_event("editor", is_via_ssh);
20900 }
20901 multi_buffer::Event::ExcerptsAdded {
20902 buffer,
20903 predecessor,
20904 excerpts,
20905 } => {
20906 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20907 let buffer_id = buffer.read(cx).remote_id();
20908 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20909 && let Some(project) = &self.project
20910 {
20911 update_uncommitted_diff_for_buffer(
20912 cx.entity(),
20913 project,
20914 [buffer.clone()],
20915 self.buffer.clone(),
20916 cx,
20917 )
20918 .detach();
20919 }
20920 self.update_lsp_data(Some(buffer_id), window, cx);
20921 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20922 cx.emit(EditorEvent::ExcerptsAdded {
20923 buffer: buffer.clone(),
20924 predecessor: *predecessor,
20925 excerpts: excerpts.clone(),
20926 });
20927 }
20928 multi_buffer::Event::ExcerptsRemoved {
20929 ids,
20930 removed_buffer_ids,
20931 } => {
20932 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20933 for buffer_id in removed_buffer_ids {
20934 self.registered_buffers.remove(buffer_id);
20935 }
20936 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20937 cx.emit(EditorEvent::ExcerptsRemoved {
20938 ids: ids.clone(),
20939 removed_buffer_ids: removed_buffer_ids.clone(),
20940 });
20941 }
20942 multi_buffer::Event::ExcerptsEdited {
20943 excerpt_ids,
20944 buffer_ids,
20945 } => {
20946 self.display_map.update(cx, |map, cx| {
20947 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20948 });
20949 cx.emit(EditorEvent::ExcerptsEdited {
20950 ids: excerpt_ids.clone(),
20951 });
20952 }
20953 multi_buffer::Event::ExcerptsExpanded { ids } => {
20954 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20955 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20956 }
20957 multi_buffer::Event::Reparsed(buffer_id) => {
20958 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20959 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20960
20961 cx.emit(EditorEvent::Reparsed(*buffer_id));
20962 }
20963 multi_buffer::Event::DiffHunksToggled => {
20964 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20965 }
20966 multi_buffer::Event::LanguageChanged(buffer_id) => {
20967 self.registered_buffers.remove(&buffer_id);
20968 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20969 cx.emit(EditorEvent::Reparsed(*buffer_id));
20970 cx.notify();
20971 }
20972 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20973 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20974 multi_buffer::Event::FileHandleChanged
20975 | multi_buffer::Event::Reloaded
20976 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20977 multi_buffer::Event::DiagnosticsUpdated => {
20978 self.update_diagnostics_state(window, cx);
20979 }
20980 _ => {}
20981 };
20982 }
20983
20984 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20985 if !self.diagnostics_enabled() {
20986 return;
20987 }
20988 self.refresh_active_diagnostics(cx);
20989 self.refresh_inline_diagnostics(true, window, cx);
20990 self.scrollbar_marker_state.dirty = true;
20991 cx.notify();
20992 }
20993
20994 pub fn start_temporary_diff_override(&mut self) {
20995 self.load_diff_task.take();
20996 self.temporary_diff_override = true;
20997 }
20998
20999 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21000 self.temporary_diff_override = false;
21001 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21002 self.buffer.update(cx, |buffer, cx| {
21003 buffer.set_all_diff_hunks_collapsed(cx);
21004 });
21005
21006 if let Some(project) = self.project.clone() {
21007 self.load_diff_task = Some(
21008 update_uncommitted_diff_for_buffer(
21009 cx.entity(),
21010 &project,
21011 self.buffer.read(cx).all_buffers(),
21012 self.buffer.clone(),
21013 cx,
21014 )
21015 .shared(),
21016 );
21017 }
21018 }
21019
21020 fn on_display_map_changed(
21021 &mut self,
21022 _: Entity<DisplayMap>,
21023 _: &mut Window,
21024 cx: &mut Context<Self>,
21025 ) {
21026 cx.notify();
21027 }
21028
21029 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21030 if self.diagnostics_enabled() {
21031 let new_severity = EditorSettings::get_global(cx)
21032 .diagnostics_max_severity
21033 .unwrap_or(DiagnosticSeverity::Hint);
21034 self.set_max_diagnostics_severity(new_severity, cx);
21035 }
21036 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21037 self.update_edit_prediction_settings(cx);
21038 self.refresh_edit_prediction(true, false, window, cx);
21039 self.refresh_inline_values(cx);
21040 self.refresh_inlay_hints(
21041 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21042 self.selections.newest_anchor().head(),
21043 &self.buffer.read(cx).snapshot(cx),
21044 cx,
21045 )),
21046 cx,
21047 );
21048
21049 let old_cursor_shape = self.cursor_shape;
21050 let old_show_breadcrumbs = self.show_breadcrumbs;
21051
21052 {
21053 let editor_settings = EditorSettings::get_global(cx);
21054 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21055 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21056 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21057 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21058 }
21059
21060 if old_cursor_shape != self.cursor_shape {
21061 cx.emit(EditorEvent::CursorShapeChanged);
21062 }
21063
21064 if old_show_breadcrumbs != self.show_breadcrumbs {
21065 cx.emit(EditorEvent::BreadcrumbsChanged);
21066 }
21067
21068 let project_settings = ProjectSettings::get_global(cx);
21069 self.serialize_dirty_buffers =
21070 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
21071
21072 if self.mode.is_full() {
21073 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21074 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21075 if self.show_inline_diagnostics != show_inline_diagnostics {
21076 self.show_inline_diagnostics = show_inline_diagnostics;
21077 self.refresh_inline_diagnostics(false, window, cx);
21078 }
21079
21080 if self.git_blame_inline_enabled != inline_blame_enabled {
21081 self.toggle_git_blame_inline_internal(false, window, cx);
21082 }
21083
21084 let minimap_settings = EditorSettings::get_global(cx).minimap;
21085 if self.minimap_visibility != MinimapVisibility::Disabled {
21086 if self.minimap_visibility.settings_visibility()
21087 != minimap_settings.minimap_enabled()
21088 {
21089 self.set_minimap_visibility(
21090 MinimapVisibility::for_mode(self.mode(), cx),
21091 window,
21092 cx,
21093 );
21094 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21095 minimap_entity.update(cx, |minimap_editor, cx| {
21096 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21097 })
21098 }
21099 }
21100 }
21101
21102 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21103 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21104 }) {
21105 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
21106 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21107 }
21108 self.refresh_colors_for_visible_range(None, window, cx);
21109 }
21110
21111 cx.notify();
21112 }
21113
21114 pub fn set_searchable(&mut self, searchable: bool) {
21115 self.searchable = searchable;
21116 }
21117
21118 pub fn searchable(&self) -> bool {
21119 self.searchable
21120 }
21121
21122 fn open_proposed_changes_editor(
21123 &mut self,
21124 _: &OpenProposedChangesEditor,
21125 window: &mut Window,
21126 cx: &mut Context<Self>,
21127 ) {
21128 let Some(workspace) = self.workspace() else {
21129 cx.propagate();
21130 return;
21131 };
21132
21133 let selections = self.selections.all::<usize>(cx);
21134 let multi_buffer = self.buffer.read(cx);
21135 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
21136 let mut new_selections_by_buffer = HashMap::default();
21137 for selection in selections {
21138 for (buffer, range, _) in
21139 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
21140 {
21141 let mut range = range.to_point(buffer);
21142 range.start.column = 0;
21143 range.end.column = buffer.line_len(range.end.row);
21144 new_selections_by_buffer
21145 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
21146 .or_insert(Vec::new())
21147 .push(range)
21148 }
21149 }
21150
21151 let proposed_changes_buffers = new_selections_by_buffer
21152 .into_iter()
21153 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
21154 .collect::<Vec<_>>();
21155 let proposed_changes_editor = cx.new(|cx| {
21156 ProposedChangesEditor::new(
21157 "Proposed changes",
21158 proposed_changes_buffers,
21159 self.project.clone(),
21160 window,
21161 cx,
21162 )
21163 });
21164
21165 window.defer(cx, move |window, cx| {
21166 workspace.update(cx, |workspace, cx| {
21167 workspace.active_pane().update(cx, |pane, cx| {
21168 pane.add_item(
21169 Box::new(proposed_changes_editor),
21170 true,
21171 true,
21172 None,
21173 window,
21174 cx,
21175 );
21176 });
21177 });
21178 });
21179 }
21180
21181 pub fn open_excerpts_in_split(
21182 &mut self,
21183 _: &OpenExcerptsSplit,
21184 window: &mut Window,
21185 cx: &mut Context<Self>,
21186 ) {
21187 self.open_excerpts_common(None, true, window, cx)
21188 }
21189
21190 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21191 self.open_excerpts_common(None, false, window, cx)
21192 }
21193
21194 fn open_excerpts_common(
21195 &mut self,
21196 jump_data: Option<JumpData>,
21197 split: bool,
21198 window: &mut Window,
21199 cx: &mut Context<Self>,
21200 ) {
21201 let Some(workspace) = self.workspace() else {
21202 cx.propagate();
21203 return;
21204 };
21205
21206 if self.buffer.read(cx).is_singleton() {
21207 cx.propagate();
21208 return;
21209 }
21210
21211 let mut new_selections_by_buffer = HashMap::default();
21212 match &jump_data {
21213 Some(JumpData::MultiBufferPoint {
21214 excerpt_id,
21215 position,
21216 anchor,
21217 line_offset_from_top,
21218 }) => {
21219 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21220 if let Some(buffer) = multi_buffer_snapshot
21221 .buffer_id_for_excerpt(*excerpt_id)
21222 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21223 {
21224 let buffer_snapshot = buffer.read(cx).snapshot();
21225 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21226 language::ToPoint::to_point(anchor, &buffer_snapshot)
21227 } else {
21228 buffer_snapshot.clip_point(*position, Bias::Left)
21229 };
21230 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21231 new_selections_by_buffer.insert(
21232 buffer,
21233 (
21234 vec![jump_to_offset..jump_to_offset],
21235 Some(*line_offset_from_top),
21236 ),
21237 );
21238 }
21239 }
21240 Some(JumpData::MultiBufferRow {
21241 row,
21242 line_offset_from_top,
21243 }) => {
21244 let point = MultiBufferPoint::new(row.0, 0);
21245 if let Some((buffer, buffer_point, _)) =
21246 self.buffer.read(cx).point_to_buffer_point(point, cx)
21247 {
21248 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21249 new_selections_by_buffer
21250 .entry(buffer)
21251 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21252 .0
21253 .push(buffer_offset..buffer_offset)
21254 }
21255 }
21256 None => {
21257 let selections = self.selections.all::<usize>(cx);
21258 let multi_buffer = self.buffer.read(cx);
21259 for selection in selections {
21260 for (snapshot, range, _, anchor) in multi_buffer
21261 .snapshot(cx)
21262 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21263 {
21264 if let Some(anchor) = anchor {
21265 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21266 else {
21267 continue;
21268 };
21269 let offset = text::ToOffset::to_offset(
21270 &anchor.text_anchor,
21271 &buffer_handle.read(cx).snapshot(),
21272 );
21273 let range = offset..offset;
21274 new_selections_by_buffer
21275 .entry(buffer_handle)
21276 .or_insert((Vec::new(), None))
21277 .0
21278 .push(range)
21279 } else {
21280 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21281 else {
21282 continue;
21283 };
21284 new_selections_by_buffer
21285 .entry(buffer_handle)
21286 .or_insert((Vec::new(), None))
21287 .0
21288 .push(range)
21289 }
21290 }
21291 }
21292 }
21293 }
21294
21295 new_selections_by_buffer
21296 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21297
21298 if new_selections_by_buffer.is_empty() {
21299 return;
21300 }
21301
21302 // We defer the pane interaction because we ourselves are a workspace item
21303 // and activating a new item causes the pane to call a method on us reentrantly,
21304 // which panics if we're on the stack.
21305 window.defer(cx, move |window, cx| {
21306 workspace.update(cx, |workspace, cx| {
21307 let pane = if split {
21308 workspace.adjacent_pane(window, cx)
21309 } else {
21310 workspace.active_pane().clone()
21311 };
21312
21313 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21314 let editor = buffer
21315 .read(cx)
21316 .file()
21317 .is_none()
21318 .then(|| {
21319 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21320 // so `workspace.open_project_item` will never find them, always opening a new editor.
21321 // Instead, we try to activate the existing editor in the pane first.
21322 let (editor, pane_item_index) =
21323 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21324 let editor = item.downcast::<Editor>()?;
21325 let singleton_buffer =
21326 editor.read(cx).buffer().read(cx).as_singleton()?;
21327 if singleton_buffer == buffer {
21328 Some((editor, i))
21329 } else {
21330 None
21331 }
21332 })?;
21333 pane.update(cx, |pane, cx| {
21334 pane.activate_item(pane_item_index, true, true, window, cx)
21335 });
21336 Some(editor)
21337 })
21338 .flatten()
21339 .unwrap_or_else(|| {
21340 workspace.open_project_item::<Self>(
21341 pane.clone(),
21342 buffer,
21343 true,
21344 true,
21345 window,
21346 cx,
21347 )
21348 });
21349
21350 editor.update(cx, |editor, cx| {
21351 let autoscroll = match scroll_offset {
21352 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21353 None => Autoscroll::newest(),
21354 };
21355 let nav_history = editor.nav_history.take();
21356 editor.change_selections(
21357 SelectionEffects::scroll(autoscroll),
21358 window,
21359 cx,
21360 |s| {
21361 s.select_ranges(ranges);
21362 },
21363 );
21364 editor.nav_history = nav_history;
21365 });
21366 }
21367 })
21368 });
21369 }
21370
21371 // For now, don't allow opening excerpts in buffers that aren't backed by
21372 // regular project files.
21373 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21374 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21375 }
21376
21377 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21378 let snapshot = self.buffer.read(cx).read(cx);
21379 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21380 Some(
21381 ranges
21382 .iter()
21383 .map(move |range| {
21384 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21385 })
21386 .collect(),
21387 )
21388 }
21389
21390 fn selection_replacement_ranges(
21391 &self,
21392 range: Range<OffsetUtf16>,
21393 cx: &mut App,
21394 ) -> Vec<Range<OffsetUtf16>> {
21395 let selections = self.selections.all::<OffsetUtf16>(cx);
21396 let newest_selection = selections
21397 .iter()
21398 .max_by_key(|selection| selection.id)
21399 .unwrap();
21400 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21401 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21402 let snapshot = self.buffer.read(cx).read(cx);
21403 selections
21404 .into_iter()
21405 .map(|mut selection| {
21406 selection.start.0 =
21407 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21408 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21409 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21410 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21411 })
21412 .collect()
21413 }
21414
21415 fn report_editor_event(
21416 &self,
21417 reported_event: ReportEditorEvent,
21418 file_extension: Option<String>,
21419 cx: &App,
21420 ) {
21421 if cfg!(any(test, feature = "test-support")) {
21422 return;
21423 }
21424
21425 let Some(project) = &self.project else { return };
21426
21427 // If None, we are in a file without an extension
21428 let file = self
21429 .buffer
21430 .read(cx)
21431 .as_singleton()
21432 .and_then(|b| b.read(cx).file());
21433 let file_extension = file_extension.or(file
21434 .as_ref()
21435 .and_then(|file| Path::new(file.file_name(cx)).extension())
21436 .and_then(|e| e.to_str())
21437 .map(|a| a.to_string()));
21438
21439 let vim_mode = vim_enabled(cx);
21440
21441 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21442 let copilot_enabled = edit_predictions_provider
21443 == language::language_settings::EditPredictionProvider::Copilot;
21444 let copilot_enabled_for_language = self
21445 .buffer
21446 .read(cx)
21447 .language_settings(cx)
21448 .show_edit_predictions;
21449
21450 let project = project.read(cx);
21451 let event_type = reported_event.event_type();
21452
21453 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21454 telemetry::event!(
21455 event_type,
21456 type = if auto_saved {"autosave"} else {"manual"},
21457 file_extension,
21458 vim_mode,
21459 copilot_enabled,
21460 copilot_enabled_for_language,
21461 edit_predictions_provider,
21462 is_via_ssh = project.is_via_remote_server(),
21463 );
21464 } else {
21465 telemetry::event!(
21466 event_type,
21467 file_extension,
21468 vim_mode,
21469 copilot_enabled,
21470 copilot_enabled_for_language,
21471 edit_predictions_provider,
21472 is_via_ssh = project.is_via_remote_server(),
21473 );
21474 };
21475 }
21476
21477 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21478 /// with each line being an array of {text, highlight} objects.
21479 fn copy_highlight_json(
21480 &mut self,
21481 _: &CopyHighlightJson,
21482 window: &mut Window,
21483 cx: &mut Context<Self>,
21484 ) {
21485 #[derive(Serialize)]
21486 struct Chunk<'a> {
21487 text: String,
21488 highlight: Option<&'a str>,
21489 }
21490
21491 let snapshot = self.buffer.read(cx).snapshot(cx);
21492 let range = self
21493 .selected_text_range(false, window, cx)
21494 .and_then(|selection| {
21495 if selection.range.is_empty() {
21496 None
21497 } else {
21498 Some(selection.range)
21499 }
21500 })
21501 .unwrap_or_else(|| 0..snapshot.len());
21502
21503 let chunks = snapshot.chunks(range, true);
21504 let mut lines = Vec::new();
21505 let mut line: VecDeque<Chunk> = VecDeque::new();
21506
21507 let Some(style) = self.style.as_ref() else {
21508 return;
21509 };
21510
21511 for chunk in chunks {
21512 let highlight = chunk
21513 .syntax_highlight_id
21514 .and_then(|id| id.name(&style.syntax));
21515 let mut chunk_lines = chunk.text.split('\n').peekable();
21516 while let Some(text) = chunk_lines.next() {
21517 let mut merged_with_last_token = false;
21518 if let Some(last_token) = line.back_mut()
21519 && last_token.highlight == highlight
21520 {
21521 last_token.text.push_str(text);
21522 merged_with_last_token = true;
21523 }
21524
21525 if !merged_with_last_token {
21526 line.push_back(Chunk {
21527 text: text.into(),
21528 highlight,
21529 });
21530 }
21531
21532 if chunk_lines.peek().is_some() {
21533 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21534 line.pop_front();
21535 }
21536 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21537 line.pop_back();
21538 }
21539
21540 lines.push(mem::take(&mut line));
21541 }
21542 }
21543 }
21544
21545 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21546 return;
21547 };
21548 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21549 }
21550
21551 pub fn open_context_menu(
21552 &mut self,
21553 _: &OpenContextMenu,
21554 window: &mut Window,
21555 cx: &mut Context<Self>,
21556 ) {
21557 self.request_autoscroll(Autoscroll::newest(), cx);
21558 let position = self.selections.newest_display(cx).start;
21559 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21560 }
21561
21562 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
21563 &self.inlay_hint_cache
21564 }
21565
21566 pub fn replay_insert_event(
21567 &mut self,
21568 text: &str,
21569 relative_utf16_range: Option<Range<isize>>,
21570 window: &mut Window,
21571 cx: &mut Context<Self>,
21572 ) {
21573 if !self.input_enabled {
21574 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21575 return;
21576 }
21577 if let Some(relative_utf16_range) = relative_utf16_range {
21578 let selections = self.selections.all::<OffsetUtf16>(cx);
21579 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21580 let new_ranges = selections.into_iter().map(|range| {
21581 let start = OffsetUtf16(
21582 range
21583 .head()
21584 .0
21585 .saturating_add_signed(relative_utf16_range.start),
21586 );
21587 let end = OffsetUtf16(
21588 range
21589 .head()
21590 .0
21591 .saturating_add_signed(relative_utf16_range.end),
21592 );
21593 start..end
21594 });
21595 s.select_ranges(new_ranges);
21596 });
21597 }
21598
21599 self.handle_input(text, window, cx);
21600 }
21601
21602 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
21603 let Some(provider) = self.semantics_provider.as_ref() else {
21604 return false;
21605 };
21606
21607 let mut supports = false;
21608 self.buffer().update(cx, |this, cx| {
21609 this.for_each_buffer(|buffer| {
21610 supports |= provider.supports_inlay_hints(buffer, cx);
21611 });
21612 });
21613
21614 supports
21615 }
21616
21617 pub fn is_focused(&self, window: &Window) -> bool {
21618 self.focus_handle.is_focused(window)
21619 }
21620
21621 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21622 cx.emit(EditorEvent::Focused);
21623
21624 if let Some(descendant) = self
21625 .last_focused_descendant
21626 .take()
21627 .and_then(|descendant| descendant.upgrade())
21628 {
21629 window.focus(&descendant);
21630 } else {
21631 if let Some(blame) = self.blame.as_ref() {
21632 blame.update(cx, GitBlame::focus)
21633 }
21634
21635 self.blink_manager.update(cx, BlinkManager::enable);
21636 self.show_cursor_names(window, cx);
21637 self.buffer.update(cx, |buffer, cx| {
21638 buffer.finalize_last_transaction(cx);
21639 if self.leader_id.is_none() {
21640 buffer.set_active_selections(
21641 &self.selections.disjoint_anchors_arc(),
21642 self.selections.line_mode(),
21643 self.cursor_shape,
21644 cx,
21645 );
21646 }
21647 });
21648 }
21649 }
21650
21651 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21652 cx.emit(EditorEvent::FocusedIn)
21653 }
21654
21655 fn handle_focus_out(
21656 &mut self,
21657 event: FocusOutEvent,
21658 _window: &mut Window,
21659 cx: &mut Context<Self>,
21660 ) {
21661 if event.blurred != self.focus_handle {
21662 self.last_focused_descendant = Some(event.blurred);
21663 }
21664 self.selection_drag_state = SelectionDragState::None;
21665 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21666 }
21667
21668 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21669 self.blink_manager.update(cx, BlinkManager::disable);
21670 self.buffer
21671 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21672
21673 if let Some(blame) = self.blame.as_ref() {
21674 blame.update(cx, GitBlame::blur)
21675 }
21676 if !self.hover_state.focused(window, cx) {
21677 hide_hover(self, cx);
21678 }
21679 if !self
21680 .context_menu
21681 .borrow()
21682 .as_ref()
21683 .is_some_and(|context_menu| context_menu.focused(window, cx))
21684 {
21685 self.hide_context_menu(window, cx);
21686 }
21687 self.take_active_edit_prediction(cx);
21688 cx.emit(EditorEvent::Blurred);
21689 cx.notify();
21690 }
21691
21692 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21693 let mut pending: String = window
21694 .pending_input_keystrokes()
21695 .into_iter()
21696 .flatten()
21697 .filter_map(|keystroke| {
21698 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21699 keystroke.key_char.clone()
21700 } else {
21701 None
21702 }
21703 })
21704 .collect();
21705
21706 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21707 pending = "".to_string();
21708 }
21709
21710 let existing_pending = self
21711 .text_highlights::<PendingInput>(cx)
21712 .map(|(_, ranges)| ranges.to_vec());
21713 if existing_pending.is_none() && pending.is_empty() {
21714 return;
21715 }
21716 let transaction =
21717 self.transact(window, cx, |this, window, cx| {
21718 let selections = this.selections.all::<usize>(cx);
21719 let edits = selections
21720 .iter()
21721 .map(|selection| (selection.end..selection.end, pending.clone()));
21722 this.edit(edits, cx);
21723 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21724 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21725 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21726 }));
21727 });
21728 if let Some(existing_ranges) = existing_pending {
21729 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21730 this.edit(edits, cx);
21731 }
21732 });
21733
21734 let snapshot = self.snapshot(window, cx);
21735 let ranges = self
21736 .selections
21737 .all::<usize>(cx)
21738 .into_iter()
21739 .map(|selection| {
21740 snapshot.buffer_snapshot().anchor_after(selection.end)
21741 ..snapshot
21742 .buffer_snapshot()
21743 .anchor_before(selection.end + pending.len())
21744 })
21745 .collect();
21746
21747 if pending.is_empty() {
21748 self.clear_highlights::<PendingInput>(cx);
21749 } else {
21750 self.highlight_text::<PendingInput>(
21751 ranges,
21752 HighlightStyle {
21753 underline: Some(UnderlineStyle {
21754 thickness: px(1.),
21755 color: None,
21756 wavy: false,
21757 }),
21758 ..Default::default()
21759 },
21760 cx,
21761 );
21762 }
21763
21764 self.ime_transaction = self.ime_transaction.or(transaction);
21765 if let Some(transaction) = self.ime_transaction {
21766 self.buffer.update(cx, |buffer, cx| {
21767 buffer.group_until_transaction(transaction, cx);
21768 });
21769 }
21770
21771 if self.text_highlights::<PendingInput>(cx).is_none() {
21772 self.ime_transaction.take();
21773 }
21774 }
21775
21776 pub fn register_action_renderer(
21777 &mut self,
21778 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21779 ) -> Subscription {
21780 let id = self.next_editor_action_id.post_inc();
21781 self.editor_actions
21782 .borrow_mut()
21783 .insert(id, Box::new(listener));
21784
21785 let editor_actions = self.editor_actions.clone();
21786 Subscription::new(move || {
21787 editor_actions.borrow_mut().remove(&id);
21788 })
21789 }
21790
21791 pub fn register_action<A: Action>(
21792 &mut self,
21793 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21794 ) -> Subscription {
21795 let id = self.next_editor_action_id.post_inc();
21796 let listener = Arc::new(listener);
21797 self.editor_actions.borrow_mut().insert(
21798 id,
21799 Box::new(move |_, window, _| {
21800 let listener = listener.clone();
21801 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21802 let action = action.downcast_ref().unwrap();
21803 if phase == DispatchPhase::Bubble {
21804 listener(action, window, cx)
21805 }
21806 })
21807 }),
21808 );
21809
21810 let editor_actions = self.editor_actions.clone();
21811 Subscription::new(move || {
21812 editor_actions.borrow_mut().remove(&id);
21813 })
21814 }
21815
21816 pub fn file_header_size(&self) -> u32 {
21817 FILE_HEADER_HEIGHT
21818 }
21819
21820 pub fn restore(
21821 &mut self,
21822 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21823 window: &mut Window,
21824 cx: &mut Context<Self>,
21825 ) {
21826 let workspace = self.workspace();
21827 let project = self.project();
21828 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21829 let mut tasks = Vec::new();
21830 for (buffer_id, changes) in revert_changes {
21831 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21832 buffer.update(cx, |buffer, cx| {
21833 buffer.edit(
21834 changes
21835 .into_iter()
21836 .map(|(range, text)| (range, text.to_string())),
21837 None,
21838 cx,
21839 );
21840 });
21841
21842 if let Some(project) =
21843 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21844 {
21845 project.update(cx, |project, cx| {
21846 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21847 })
21848 }
21849 }
21850 }
21851 tasks
21852 });
21853 cx.spawn_in(window, async move |_, cx| {
21854 for (buffer, task) in save_tasks {
21855 let result = task.await;
21856 if result.is_err() {
21857 let Some(path) = buffer
21858 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21859 .ok()
21860 else {
21861 continue;
21862 };
21863 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21864 let Some(task) = cx
21865 .update_window_entity(workspace, |workspace, window, cx| {
21866 workspace
21867 .open_path_preview(path, None, false, false, false, window, cx)
21868 })
21869 .ok()
21870 else {
21871 continue;
21872 };
21873 task.await.log_err();
21874 }
21875 }
21876 }
21877 })
21878 .detach();
21879 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21880 selections.refresh()
21881 });
21882 }
21883
21884 pub fn to_pixel_point(
21885 &self,
21886 source: multi_buffer::Anchor,
21887 editor_snapshot: &EditorSnapshot,
21888 window: &mut Window,
21889 ) -> Option<gpui::Point<Pixels>> {
21890 let source_point = source.to_display_point(editor_snapshot);
21891 self.display_to_pixel_point(source_point, editor_snapshot, window)
21892 }
21893
21894 pub fn display_to_pixel_point(
21895 &self,
21896 source: DisplayPoint,
21897 editor_snapshot: &EditorSnapshot,
21898 window: &mut Window,
21899 ) -> Option<gpui::Point<Pixels>> {
21900 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21901 let text_layout_details = self.text_layout_details(window);
21902 let scroll_top = text_layout_details
21903 .scroll_anchor
21904 .scroll_position(editor_snapshot)
21905 .y;
21906
21907 if source.row().as_f64() < scroll_top.floor() {
21908 return None;
21909 }
21910 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21911 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
21912 Some(gpui::Point::new(source_x, source_y))
21913 }
21914
21915 pub fn has_visible_completions_menu(&self) -> bool {
21916 !self.edit_prediction_preview_is_active()
21917 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21918 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21919 })
21920 }
21921
21922 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21923 if self.mode.is_minimap() {
21924 return;
21925 }
21926 self.addons
21927 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21928 }
21929
21930 pub fn unregister_addon<T: Addon>(&mut self) {
21931 self.addons.remove(&std::any::TypeId::of::<T>());
21932 }
21933
21934 pub fn addon<T: Addon>(&self) -> Option<&T> {
21935 let type_id = std::any::TypeId::of::<T>();
21936 self.addons
21937 .get(&type_id)
21938 .and_then(|item| item.to_any().downcast_ref::<T>())
21939 }
21940
21941 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21942 let type_id = std::any::TypeId::of::<T>();
21943 self.addons
21944 .get_mut(&type_id)
21945 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21946 }
21947
21948 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21949 let text_layout_details = self.text_layout_details(window);
21950 let style = &text_layout_details.editor_style;
21951 let font_id = window.text_system().resolve_font(&style.text.font());
21952 let font_size = style.text.font_size.to_pixels(window.rem_size());
21953 let line_height = style.text.line_height_in_pixels(window.rem_size());
21954 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21955 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21956
21957 CharacterDimensions {
21958 em_width,
21959 em_advance,
21960 line_height,
21961 }
21962 }
21963
21964 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21965 self.load_diff_task.clone()
21966 }
21967
21968 fn read_metadata_from_db(
21969 &mut self,
21970 item_id: u64,
21971 workspace_id: WorkspaceId,
21972 window: &mut Window,
21973 cx: &mut Context<Editor>,
21974 ) {
21975 if self.buffer_kind(cx) == ItemBufferKind::Singleton
21976 && !self.mode.is_minimap()
21977 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21978 {
21979 let buffer_snapshot = OnceCell::new();
21980
21981 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21982 && !folds.is_empty()
21983 {
21984 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21985 self.fold_ranges(
21986 folds
21987 .into_iter()
21988 .map(|(start, end)| {
21989 snapshot.clip_offset(start, Bias::Left)
21990 ..snapshot.clip_offset(end, Bias::Right)
21991 })
21992 .collect(),
21993 false,
21994 window,
21995 cx,
21996 );
21997 }
21998
21999 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22000 && !selections.is_empty()
22001 {
22002 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22003 // skip adding the initial selection to selection history
22004 self.selection_history.mode = SelectionHistoryMode::Skipping;
22005 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22006 s.select_ranges(selections.into_iter().map(|(start, end)| {
22007 snapshot.clip_offset(start, Bias::Left)
22008 ..snapshot.clip_offset(end, Bias::Right)
22009 }));
22010 });
22011 self.selection_history.mode = SelectionHistoryMode::Normal;
22012 };
22013 }
22014
22015 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22016 }
22017
22018 fn update_lsp_data(
22019 &mut self,
22020 for_buffer: Option<BufferId>,
22021 window: &mut Window,
22022 cx: &mut Context<'_, Self>,
22023 ) {
22024 self.pull_diagnostics(for_buffer, window, cx);
22025 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22026 }
22027
22028 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22029 // Singletons are registered on editor creation.
22030 if self.ignore_lsp_data() || self.buffer().read(cx).is_singleton() {
22031 return;
22032 }
22033 for (_, (visible_buffer, _, _)) in self.visible_excerpts(None, cx) {
22034 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22035 }
22036 }
22037
22038 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) -> bool {
22039 if !self.registered_buffers.contains_key(&buffer_id)
22040 && let Some(project) = self.project.as_ref()
22041 {
22042 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22043 project.update(cx, |project, cx| {
22044 self.registered_buffers.insert(
22045 buffer_id,
22046 project.register_buffer_with_language_servers(&buffer, cx),
22047 );
22048 });
22049 return true;
22050 } else {
22051 self.registered_buffers.remove(&buffer_id);
22052 }
22053 }
22054
22055 false
22056 }
22057
22058 fn ignore_lsp_data(&self) -> bool {
22059 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22060 // skip any LSP updates for it.
22061 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22062 }
22063}
22064
22065fn edit_for_markdown_paste<'a>(
22066 buffer: &MultiBufferSnapshot,
22067 range: Range<usize>,
22068 to_insert: &'a str,
22069 url: Option<url::Url>,
22070) -> (Range<usize>, Cow<'a, str>) {
22071 if url.is_none() {
22072 return (range, Cow::Borrowed(to_insert));
22073 };
22074
22075 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22076
22077 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22078 Cow::Borrowed(to_insert)
22079 } else {
22080 Cow::Owned(format!("[{old_text}]({to_insert})"))
22081 };
22082 (range, new_text)
22083}
22084
22085fn vim_enabled(cx: &App) -> bool {
22086 vim_mode_setting::VimModeSetting::try_get(cx)
22087 .map(|vim_mode| vim_mode.0)
22088 .unwrap_or(false)
22089}
22090
22091fn process_completion_for_edit(
22092 completion: &Completion,
22093 intent: CompletionIntent,
22094 buffer: &Entity<Buffer>,
22095 cursor_position: &text::Anchor,
22096 cx: &mut Context<Editor>,
22097) -> CompletionEdit {
22098 let buffer = buffer.read(cx);
22099 let buffer_snapshot = buffer.snapshot();
22100 let (snippet, new_text) = if completion.is_snippet() {
22101 let mut snippet_source = completion.new_text.clone();
22102 // Workaround for typescript language server issues so that methods don't expand within
22103 // strings and functions with type expressions. The previous point is used because the query
22104 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22105 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22106 let previous_point = if previous_point.column > 0 {
22107 cursor_position.to_previous_offset(&buffer_snapshot)
22108 } else {
22109 cursor_position.to_offset(&buffer_snapshot)
22110 };
22111 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22112 && scope.prefers_label_for_snippet_in_completion()
22113 && let Some(label) = completion.label()
22114 && matches!(
22115 completion.kind(),
22116 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22117 )
22118 {
22119 snippet_source = label;
22120 }
22121 match Snippet::parse(&snippet_source).log_err() {
22122 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22123 None => (None, completion.new_text.clone()),
22124 }
22125 } else {
22126 (None, completion.new_text.clone())
22127 };
22128
22129 let mut range_to_replace = {
22130 let replace_range = &completion.replace_range;
22131 if let CompletionSource::Lsp {
22132 insert_range: Some(insert_range),
22133 ..
22134 } = &completion.source
22135 {
22136 debug_assert_eq!(
22137 insert_range.start, replace_range.start,
22138 "insert_range and replace_range should start at the same position"
22139 );
22140 debug_assert!(
22141 insert_range
22142 .start
22143 .cmp(cursor_position, &buffer_snapshot)
22144 .is_le(),
22145 "insert_range should start before or at cursor position"
22146 );
22147 debug_assert!(
22148 replace_range
22149 .start
22150 .cmp(cursor_position, &buffer_snapshot)
22151 .is_le(),
22152 "replace_range should start before or at cursor position"
22153 );
22154
22155 let should_replace = match intent {
22156 CompletionIntent::CompleteWithInsert => false,
22157 CompletionIntent::CompleteWithReplace => true,
22158 CompletionIntent::Complete | CompletionIntent::Compose => {
22159 let insert_mode =
22160 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22161 .completions
22162 .lsp_insert_mode;
22163 match insert_mode {
22164 LspInsertMode::Insert => false,
22165 LspInsertMode::Replace => true,
22166 LspInsertMode::ReplaceSubsequence => {
22167 let mut text_to_replace = buffer.chars_for_range(
22168 buffer.anchor_before(replace_range.start)
22169 ..buffer.anchor_after(replace_range.end),
22170 );
22171 let mut current_needle = text_to_replace.next();
22172 for haystack_ch in completion.label.text.chars() {
22173 if let Some(needle_ch) = current_needle
22174 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22175 {
22176 current_needle = text_to_replace.next();
22177 }
22178 }
22179 current_needle.is_none()
22180 }
22181 LspInsertMode::ReplaceSuffix => {
22182 if replace_range
22183 .end
22184 .cmp(cursor_position, &buffer_snapshot)
22185 .is_gt()
22186 {
22187 let range_after_cursor = *cursor_position..replace_range.end;
22188 let text_after_cursor = buffer
22189 .text_for_range(
22190 buffer.anchor_before(range_after_cursor.start)
22191 ..buffer.anchor_after(range_after_cursor.end),
22192 )
22193 .collect::<String>()
22194 .to_ascii_lowercase();
22195 completion
22196 .label
22197 .text
22198 .to_ascii_lowercase()
22199 .ends_with(&text_after_cursor)
22200 } else {
22201 true
22202 }
22203 }
22204 }
22205 }
22206 };
22207
22208 if should_replace {
22209 replace_range.clone()
22210 } else {
22211 insert_range.clone()
22212 }
22213 } else {
22214 replace_range.clone()
22215 }
22216 };
22217
22218 if range_to_replace
22219 .end
22220 .cmp(cursor_position, &buffer_snapshot)
22221 .is_lt()
22222 {
22223 range_to_replace.end = *cursor_position;
22224 }
22225
22226 CompletionEdit {
22227 new_text,
22228 replace_range: range_to_replace.to_offset(buffer),
22229 snippet,
22230 }
22231}
22232
22233struct CompletionEdit {
22234 new_text: String,
22235 replace_range: Range<usize>,
22236 snippet: Option<Snippet>,
22237}
22238
22239fn insert_extra_newline_brackets(
22240 buffer: &MultiBufferSnapshot,
22241 range: Range<usize>,
22242 language: &language::LanguageScope,
22243) -> bool {
22244 let leading_whitespace_len = buffer
22245 .reversed_chars_at(range.start)
22246 .take_while(|c| c.is_whitespace() && *c != '\n')
22247 .map(|c| c.len_utf8())
22248 .sum::<usize>();
22249 let trailing_whitespace_len = buffer
22250 .chars_at(range.end)
22251 .take_while(|c| c.is_whitespace() && *c != '\n')
22252 .map(|c| c.len_utf8())
22253 .sum::<usize>();
22254 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22255
22256 language.brackets().any(|(pair, enabled)| {
22257 let pair_start = pair.start.trim_end();
22258 let pair_end = pair.end.trim_start();
22259
22260 enabled
22261 && pair.newline
22262 && buffer.contains_str_at(range.end, pair_end)
22263 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22264 })
22265}
22266
22267fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22268 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22269 [(buffer, range, _)] => (*buffer, range.clone()),
22270 _ => return false,
22271 };
22272 let pair = {
22273 let mut result: Option<BracketMatch> = None;
22274
22275 for pair in buffer
22276 .all_bracket_ranges(range.clone())
22277 .filter(move |pair| {
22278 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22279 })
22280 {
22281 let len = pair.close_range.end - pair.open_range.start;
22282
22283 if let Some(existing) = &result {
22284 let existing_len = existing.close_range.end - existing.open_range.start;
22285 if len > existing_len {
22286 continue;
22287 }
22288 }
22289
22290 result = Some(pair);
22291 }
22292
22293 result
22294 };
22295 let Some(pair) = pair else {
22296 return false;
22297 };
22298 pair.newline_only
22299 && buffer
22300 .chars_for_range(pair.open_range.end..range.start)
22301 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22302 .all(|c| c.is_whitespace() && c != '\n')
22303}
22304
22305fn update_uncommitted_diff_for_buffer(
22306 editor: Entity<Editor>,
22307 project: &Entity<Project>,
22308 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22309 buffer: Entity<MultiBuffer>,
22310 cx: &mut App,
22311) -> Task<()> {
22312 let mut tasks = Vec::new();
22313 project.update(cx, |project, cx| {
22314 for buffer in buffers {
22315 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22316 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22317 }
22318 }
22319 });
22320 cx.spawn(async move |cx| {
22321 let diffs = future::join_all(tasks).await;
22322 if editor
22323 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22324 .unwrap_or(false)
22325 {
22326 return;
22327 }
22328
22329 buffer
22330 .update(cx, |buffer, cx| {
22331 for diff in diffs.into_iter().flatten() {
22332 buffer.add_diff(diff, cx);
22333 }
22334 })
22335 .ok();
22336 })
22337}
22338
22339fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22340 let tab_size = tab_size.get() as usize;
22341 let mut width = offset;
22342
22343 for ch in text.chars() {
22344 width += if ch == '\t' {
22345 tab_size - (width % tab_size)
22346 } else {
22347 1
22348 };
22349 }
22350
22351 width - offset
22352}
22353
22354#[cfg(test)]
22355mod tests {
22356 use super::*;
22357
22358 #[test]
22359 fn test_string_size_with_expanded_tabs() {
22360 let nz = |val| NonZeroU32::new(val).unwrap();
22361 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22362 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22363 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22364 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22365 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22366 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22367 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22368 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22369 }
22370}
22371
22372/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22373struct WordBreakingTokenizer<'a> {
22374 input: &'a str,
22375}
22376
22377impl<'a> WordBreakingTokenizer<'a> {
22378 fn new(input: &'a str) -> Self {
22379 Self { input }
22380 }
22381}
22382
22383fn is_char_ideographic(ch: char) -> bool {
22384 use unicode_script::Script::*;
22385 use unicode_script::UnicodeScript;
22386 matches!(ch.script(), Han | Tangut | Yi)
22387}
22388
22389fn is_grapheme_ideographic(text: &str) -> bool {
22390 text.chars().any(is_char_ideographic)
22391}
22392
22393fn is_grapheme_whitespace(text: &str) -> bool {
22394 text.chars().any(|x| x.is_whitespace())
22395}
22396
22397fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22398 text.chars()
22399 .next()
22400 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22401}
22402
22403#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22404enum WordBreakToken<'a> {
22405 Word { token: &'a str, grapheme_len: usize },
22406 InlineWhitespace { token: &'a str, grapheme_len: usize },
22407 Newline,
22408}
22409
22410impl<'a> Iterator for WordBreakingTokenizer<'a> {
22411 /// Yields a span, the count of graphemes in the token, and whether it was
22412 /// whitespace. Note that it also breaks at word boundaries.
22413 type Item = WordBreakToken<'a>;
22414
22415 fn next(&mut self) -> Option<Self::Item> {
22416 use unicode_segmentation::UnicodeSegmentation;
22417 if self.input.is_empty() {
22418 return None;
22419 }
22420
22421 let mut iter = self.input.graphemes(true).peekable();
22422 let mut offset = 0;
22423 let mut grapheme_len = 0;
22424 if let Some(first_grapheme) = iter.next() {
22425 let is_newline = first_grapheme == "\n";
22426 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22427 offset += first_grapheme.len();
22428 grapheme_len += 1;
22429 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22430 if let Some(grapheme) = iter.peek().copied()
22431 && should_stay_with_preceding_ideograph(grapheme)
22432 {
22433 offset += grapheme.len();
22434 grapheme_len += 1;
22435 }
22436 } else {
22437 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22438 let mut next_word_bound = words.peek().copied();
22439 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22440 next_word_bound = words.next();
22441 }
22442 while let Some(grapheme) = iter.peek().copied() {
22443 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22444 break;
22445 };
22446 if is_grapheme_whitespace(grapheme) != is_whitespace
22447 || (grapheme == "\n") != is_newline
22448 {
22449 break;
22450 };
22451 offset += grapheme.len();
22452 grapheme_len += 1;
22453 iter.next();
22454 }
22455 }
22456 let token = &self.input[..offset];
22457 self.input = &self.input[offset..];
22458 if token == "\n" {
22459 Some(WordBreakToken::Newline)
22460 } else if is_whitespace {
22461 Some(WordBreakToken::InlineWhitespace {
22462 token,
22463 grapheme_len,
22464 })
22465 } else {
22466 Some(WordBreakToken::Word {
22467 token,
22468 grapheme_len,
22469 })
22470 }
22471 } else {
22472 None
22473 }
22474 }
22475}
22476
22477#[test]
22478fn test_word_breaking_tokenizer() {
22479 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22480 ("", &[]),
22481 (" ", &[whitespace(" ", 2)]),
22482 ("Ʒ", &[word("Ʒ", 1)]),
22483 ("Ǽ", &[word("Ǽ", 1)]),
22484 ("⋑", &[word("⋑", 1)]),
22485 ("⋑⋑", &[word("⋑⋑", 2)]),
22486 (
22487 "原理,进而",
22488 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22489 ),
22490 (
22491 "hello world",
22492 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22493 ),
22494 (
22495 "hello, world",
22496 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22497 ),
22498 (
22499 " hello world",
22500 &[
22501 whitespace(" ", 2),
22502 word("hello", 5),
22503 whitespace(" ", 1),
22504 word("world", 5),
22505 ],
22506 ),
22507 (
22508 "这是什么 \n 钢笔",
22509 &[
22510 word("这", 1),
22511 word("是", 1),
22512 word("什", 1),
22513 word("么", 1),
22514 whitespace(" ", 1),
22515 newline(),
22516 whitespace(" ", 1),
22517 word("钢", 1),
22518 word("笔", 1),
22519 ],
22520 ),
22521 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22522 ];
22523
22524 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22525 WordBreakToken::Word {
22526 token,
22527 grapheme_len,
22528 }
22529 }
22530
22531 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22532 WordBreakToken::InlineWhitespace {
22533 token,
22534 grapheme_len,
22535 }
22536 }
22537
22538 fn newline() -> WordBreakToken<'static> {
22539 WordBreakToken::Newline
22540 }
22541
22542 for (input, result) in tests {
22543 assert_eq!(
22544 WordBreakingTokenizer::new(input)
22545 .collect::<Vec<_>>()
22546 .as_slice(),
22547 *result,
22548 );
22549 }
22550}
22551
22552fn wrap_with_prefix(
22553 first_line_prefix: String,
22554 subsequent_lines_prefix: String,
22555 unwrapped_text: String,
22556 wrap_column: usize,
22557 tab_size: NonZeroU32,
22558 preserve_existing_whitespace: bool,
22559) -> String {
22560 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22561 let subsequent_lines_prefix_len =
22562 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22563 let mut wrapped_text = String::new();
22564 let mut current_line = first_line_prefix;
22565 let mut is_first_line = true;
22566
22567 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22568 let mut current_line_len = first_line_prefix_len;
22569 let mut in_whitespace = false;
22570 for token in tokenizer {
22571 let have_preceding_whitespace = in_whitespace;
22572 match token {
22573 WordBreakToken::Word {
22574 token,
22575 grapheme_len,
22576 } => {
22577 in_whitespace = false;
22578 let current_prefix_len = if is_first_line {
22579 first_line_prefix_len
22580 } else {
22581 subsequent_lines_prefix_len
22582 };
22583 if current_line_len + grapheme_len > wrap_column
22584 && current_line_len != current_prefix_len
22585 {
22586 wrapped_text.push_str(current_line.trim_end());
22587 wrapped_text.push('\n');
22588 is_first_line = false;
22589 current_line = subsequent_lines_prefix.clone();
22590 current_line_len = subsequent_lines_prefix_len;
22591 }
22592 current_line.push_str(token);
22593 current_line_len += grapheme_len;
22594 }
22595 WordBreakToken::InlineWhitespace {
22596 mut token,
22597 mut grapheme_len,
22598 } => {
22599 in_whitespace = true;
22600 if have_preceding_whitespace && !preserve_existing_whitespace {
22601 continue;
22602 }
22603 if !preserve_existing_whitespace {
22604 // Keep a single whitespace grapheme as-is
22605 if let Some(first) =
22606 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22607 {
22608 token = first;
22609 } else {
22610 token = " ";
22611 }
22612 grapheme_len = 1;
22613 }
22614 let current_prefix_len = if is_first_line {
22615 first_line_prefix_len
22616 } else {
22617 subsequent_lines_prefix_len
22618 };
22619 if current_line_len + grapheme_len > wrap_column {
22620 wrapped_text.push_str(current_line.trim_end());
22621 wrapped_text.push('\n');
22622 is_first_line = false;
22623 current_line = subsequent_lines_prefix.clone();
22624 current_line_len = subsequent_lines_prefix_len;
22625 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22626 current_line.push_str(token);
22627 current_line_len += grapheme_len;
22628 }
22629 }
22630 WordBreakToken::Newline => {
22631 in_whitespace = true;
22632 let current_prefix_len = if is_first_line {
22633 first_line_prefix_len
22634 } else {
22635 subsequent_lines_prefix_len
22636 };
22637 if preserve_existing_whitespace {
22638 wrapped_text.push_str(current_line.trim_end());
22639 wrapped_text.push('\n');
22640 is_first_line = false;
22641 current_line = subsequent_lines_prefix.clone();
22642 current_line_len = subsequent_lines_prefix_len;
22643 } else if have_preceding_whitespace {
22644 continue;
22645 } else if current_line_len + 1 > wrap_column
22646 && current_line_len != current_prefix_len
22647 {
22648 wrapped_text.push_str(current_line.trim_end());
22649 wrapped_text.push('\n');
22650 is_first_line = false;
22651 current_line = subsequent_lines_prefix.clone();
22652 current_line_len = subsequent_lines_prefix_len;
22653 } else if current_line_len != current_prefix_len {
22654 current_line.push(' ');
22655 current_line_len += 1;
22656 }
22657 }
22658 }
22659 }
22660
22661 if !current_line.is_empty() {
22662 wrapped_text.push_str(¤t_line);
22663 }
22664 wrapped_text
22665}
22666
22667#[test]
22668fn test_wrap_with_prefix() {
22669 assert_eq!(
22670 wrap_with_prefix(
22671 "# ".to_string(),
22672 "# ".to_string(),
22673 "abcdefg".to_string(),
22674 4,
22675 NonZeroU32::new(4).unwrap(),
22676 false,
22677 ),
22678 "# abcdefg"
22679 );
22680 assert_eq!(
22681 wrap_with_prefix(
22682 "".to_string(),
22683 "".to_string(),
22684 "\thello world".to_string(),
22685 8,
22686 NonZeroU32::new(4).unwrap(),
22687 false,
22688 ),
22689 "hello\nworld"
22690 );
22691 assert_eq!(
22692 wrap_with_prefix(
22693 "// ".to_string(),
22694 "// ".to_string(),
22695 "xx \nyy zz aa bb cc".to_string(),
22696 12,
22697 NonZeroU32::new(4).unwrap(),
22698 false,
22699 ),
22700 "// xx yy zz\n// aa bb cc"
22701 );
22702 assert_eq!(
22703 wrap_with_prefix(
22704 String::new(),
22705 String::new(),
22706 "这是什么 \n 钢笔".to_string(),
22707 3,
22708 NonZeroU32::new(4).unwrap(),
22709 false,
22710 ),
22711 "这是什\n么 钢\n笔"
22712 );
22713 assert_eq!(
22714 wrap_with_prefix(
22715 String::new(),
22716 String::new(),
22717 format!("foo{}bar", '\u{2009}'), // thin space
22718 80,
22719 NonZeroU32::new(4).unwrap(),
22720 false,
22721 ),
22722 format!("foo{}bar", '\u{2009}')
22723 );
22724}
22725
22726pub trait CollaborationHub {
22727 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22728 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22729 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22730}
22731
22732impl CollaborationHub for Entity<Project> {
22733 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22734 self.read(cx).collaborators()
22735 }
22736
22737 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22738 self.read(cx).user_store().read(cx).participant_indices()
22739 }
22740
22741 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22742 let this = self.read(cx);
22743 let user_ids = this.collaborators().values().map(|c| c.user_id);
22744 this.user_store().read(cx).participant_names(user_ids, cx)
22745 }
22746}
22747
22748pub trait SemanticsProvider {
22749 fn hover(
22750 &self,
22751 buffer: &Entity<Buffer>,
22752 position: text::Anchor,
22753 cx: &mut App,
22754 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22755
22756 fn inline_values(
22757 &self,
22758 buffer_handle: Entity<Buffer>,
22759 range: Range<text::Anchor>,
22760 cx: &mut App,
22761 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22762
22763 fn inlay_hints(
22764 &self,
22765 buffer_handle: Entity<Buffer>,
22766 range: Range<text::Anchor>,
22767 cx: &mut App,
22768 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22769
22770 fn resolve_inlay_hint(
22771 &self,
22772 hint: InlayHint,
22773 buffer_handle: Entity<Buffer>,
22774 server_id: LanguageServerId,
22775 cx: &mut App,
22776 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22777
22778 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22779
22780 fn document_highlights(
22781 &self,
22782 buffer: &Entity<Buffer>,
22783 position: text::Anchor,
22784 cx: &mut App,
22785 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22786
22787 fn definitions(
22788 &self,
22789 buffer: &Entity<Buffer>,
22790 position: text::Anchor,
22791 kind: GotoDefinitionKind,
22792 cx: &mut App,
22793 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22794
22795 fn range_for_rename(
22796 &self,
22797 buffer: &Entity<Buffer>,
22798 position: text::Anchor,
22799 cx: &mut App,
22800 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22801
22802 fn perform_rename(
22803 &self,
22804 buffer: &Entity<Buffer>,
22805 position: text::Anchor,
22806 new_name: String,
22807 cx: &mut App,
22808 ) -> Option<Task<Result<ProjectTransaction>>>;
22809}
22810
22811pub trait CompletionProvider {
22812 fn completions(
22813 &self,
22814 excerpt_id: ExcerptId,
22815 buffer: &Entity<Buffer>,
22816 buffer_position: text::Anchor,
22817 trigger: CompletionContext,
22818 window: &mut Window,
22819 cx: &mut Context<Editor>,
22820 ) -> Task<Result<Vec<CompletionResponse>>>;
22821
22822 fn resolve_completions(
22823 &self,
22824 _buffer: Entity<Buffer>,
22825 _completion_indices: Vec<usize>,
22826 _completions: Rc<RefCell<Box<[Completion]>>>,
22827 _cx: &mut Context<Editor>,
22828 ) -> Task<Result<bool>> {
22829 Task::ready(Ok(false))
22830 }
22831
22832 fn apply_additional_edits_for_completion(
22833 &self,
22834 _buffer: Entity<Buffer>,
22835 _completions: Rc<RefCell<Box<[Completion]>>>,
22836 _completion_index: usize,
22837 _push_to_history: bool,
22838 _cx: &mut Context<Editor>,
22839 ) -> Task<Result<Option<language::Transaction>>> {
22840 Task::ready(Ok(None))
22841 }
22842
22843 fn is_completion_trigger(
22844 &self,
22845 buffer: &Entity<Buffer>,
22846 position: language::Anchor,
22847 text: &str,
22848 trigger_in_words: bool,
22849 menu_is_open: bool,
22850 cx: &mut Context<Editor>,
22851 ) -> bool;
22852
22853 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22854
22855 fn sort_completions(&self) -> bool {
22856 true
22857 }
22858
22859 fn filter_completions(&self) -> bool {
22860 true
22861 }
22862}
22863
22864pub trait CodeActionProvider {
22865 fn id(&self) -> Arc<str>;
22866
22867 fn code_actions(
22868 &self,
22869 buffer: &Entity<Buffer>,
22870 range: Range<text::Anchor>,
22871 window: &mut Window,
22872 cx: &mut App,
22873 ) -> Task<Result<Vec<CodeAction>>>;
22874
22875 fn apply_code_action(
22876 &self,
22877 buffer_handle: Entity<Buffer>,
22878 action: CodeAction,
22879 excerpt_id: ExcerptId,
22880 push_to_history: bool,
22881 window: &mut Window,
22882 cx: &mut App,
22883 ) -> Task<Result<ProjectTransaction>>;
22884}
22885
22886impl CodeActionProvider for Entity<Project> {
22887 fn id(&self) -> Arc<str> {
22888 "project".into()
22889 }
22890
22891 fn code_actions(
22892 &self,
22893 buffer: &Entity<Buffer>,
22894 range: Range<text::Anchor>,
22895 _window: &mut Window,
22896 cx: &mut App,
22897 ) -> Task<Result<Vec<CodeAction>>> {
22898 self.update(cx, |project, cx| {
22899 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22900 let code_actions = project.code_actions(buffer, range, None, cx);
22901 cx.background_spawn(async move {
22902 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22903 Ok(code_lens_actions
22904 .context("code lens fetch")?
22905 .into_iter()
22906 .flatten()
22907 .chain(
22908 code_actions
22909 .context("code action fetch")?
22910 .into_iter()
22911 .flatten(),
22912 )
22913 .collect())
22914 })
22915 })
22916 }
22917
22918 fn apply_code_action(
22919 &self,
22920 buffer_handle: Entity<Buffer>,
22921 action: CodeAction,
22922 _excerpt_id: ExcerptId,
22923 push_to_history: bool,
22924 _window: &mut Window,
22925 cx: &mut App,
22926 ) -> Task<Result<ProjectTransaction>> {
22927 self.update(cx, |project, cx| {
22928 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22929 })
22930 }
22931}
22932
22933fn snippet_completions(
22934 project: &Project,
22935 buffer: &Entity<Buffer>,
22936 buffer_position: text::Anchor,
22937 cx: &mut App,
22938) -> Task<Result<CompletionResponse>> {
22939 let languages = buffer.read(cx).languages_at(buffer_position);
22940 let snippet_store = project.snippets().read(cx);
22941
22942 let scopes: Vec<_> = languages
22943 .iter()
22944 .filter_map(|language| {
22945 let language_name = language.lsp_id();
22946 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22947
22948 if snippets.is_empty() {
22949 None
22950 } else {
22951 Some((language.default_scope(), snippets))
22952 }
22953 })
22954 .collect();
22955
22956 if scopes.is_empty() {
22957 return Task::ready(Ok(CompletionResponse {
22958 completions: vec![],
22959 display_options: CompletionDisplayOptions::default(),
22960 is_incomplete: false,
22961 }));
22962 }
22963
22964 let snapshot = buffer.read(cx).text_snapshot();
22965 let executor = cx.background_executor().clone();
22966
22967 cx.background_spawn(async move {
22968 let mut is_incomplete = false;
22969 let mut completions: Vec<Completion> = Vec::new();
22970 for (scope, snippets) in scopes.into_iter() {
22971 let classifier =
22972 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
22973
22974 const MAX_WORD_PREFIX_LEN: usize = 128;
22975 let last_word: String = snapshot
22976 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22977 .take(MAX_WORD_PREFIX_LEN)
22978 .take_while(|c| classifier.is_word(*c))
22979 .collect::<String>()
22980 .chars()
22981 .rev()
22982 .collect();
22983
22984 if last_word.is_empty() {
22985 return Ok(CompletionResponse {
22986 completions: vec![],
22987 display_options: CompletionDisplayOptions::default(),
22988 is_incomplete: true,
22989 });
22990 }
22991
22992 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22993 let to_lsp = |point: &text::Anchor| {
22994 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22995 point_to_lsp(end)
22996 };
22997 let lsp_end = to_lsp(&buffer_position);
22998
22999 let candidates = snippets
23000 .iter()
23001 .enumerate()
23002 .flat_map(|(ix, snippet)| {
23003 snippet
23004 .prefix
23005 .iter()
23006 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
23007 })
23008 .collect::<Vec<StringMatchCandidate>>();
23009
23010 const MAX_RESULTS: usize = 100;
23011 let mut matches = fuzzy::match_strings(
23012 &candidates,
23013 &last_word,
23014 last_word.chars().any(|c| c.is_uppercase()),
23015 true,
23016 MAX_RESULTS,
23017 &Default::default(),
23018 executor.clone(),
23019 )
23020 .await;
23021
23022 if matches.len() >= MAX_RESULTS {
23023 is_incomplete = true;
23024 }
23025
23026 // Remove all candidates where the query's start does not match the start of any word in the candidate
23027 if let Some(query_start) = last_word.chars().next() {
23028 matches.retain(|string_match| {
23029 split_words(&string_match.string).any(|word| {
23030 // Check that the first codepoint of the word as lowercase matches the first
23031 // codepoint of the query as lowercase
23032 word.chars()
23033 .flat_map(|codepoint| codepoint.to_lowercase())
23034 .zip(query_start.to_lowercase())
23035 .all(|(word_cp, query_cp)| word_cp == query_cp)
23036 })
23037 });
23038 }
23039
23040 let matched_strings = matches
23041 .into_iter()
23042 .map(|m| m.string)
23043 .collect::<HashSet<_>>();
23044
23045 completions.extend(snippets.iter().filter_map(|snippet| {
23046 let matching_prefix = snippet
23047 .prefix
23048 .iter()
23049 .find(|prefix| matched_strings.contains(*prefix))?;
23050 let start = as_offset - last_word.len();
23051 let start = snapshot.anchor_before(start);
23052 let range = start..buffer_position;
23053 let lsp_start = to_lsp(&start);
23054 let lsp_range = lsp::Range {
23055 start: lsp_start,
23056 end: lsp_end,
23057 };
23058 Some(Completion {
23059 replace_range: range,
23060 new_text: snippet.body.clone(),
23061 source: CompletionSource::Lsp {
23062 insert_range: None,
23063 server_id: LanguageServerId(usize::MAX),
23064 resolved: true,
23065 lsp_completion: Box::new(lsp::CompletionItem {
23066 label: snippet.prefix.first().unwrap().clone(),
23067 kind: Some(CompletionItemKind::SNIPPET),
23068 label_details: snippet.description.as_ref().map(|description| {
23069 lsp::CompletionItemLabelDetails {
23070 detail: Some(description.clone()),
23071 description: None,
23072 }
23073 }),
23074 insert_text_format: Some(InsertTextFormat::SNIPPET),
23075 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23076 lsp::InsertReplaceEdit {
23077 new_text: snippet.body.clone(),
23078 insert: lsp_range,
23079 replace: lsp_range,
23080 },
23081 )),
23082 filter_text: Some(snippet.body.clone()),
23083 sort_text: Some(char::MAX.to_string()),
23084 ..lsp::CompletionItem::default()
23085 }),
23086 lsp_defaults: None,
23087 },
23088 label: CodeLabel::plain(matching_prefix.clone(), None),
23089 icon_path: None,
23090 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23091 single_line: snippet.name.clone().into(),
23092 plain_text: snippet
23093 .description
23094 .clone()
23095 .map(|description| description.into()),
23096 }),
23097 insert_text_mode: None,
23098 confirm: None,
23099 })
23100 }))
23101 }
23102
23103 Ok(CompletionResponse {
23104 completions,
23105 display_options: CompletionDisplayOptions::default(),
23106 is_incomplete,
23107 })
23108 })
23109}
23110
23111impl CompletionProvider for Entity<Project> {
23112 fn completions(
23113 &self,
23114 _excerpt_id: ExcerptId,
23115 buffer: &Entity<Buffer>,
23116 buffer_position: text::Anchor,
23117 options: CompletionContext,
23118 _window: &mut Window,
23119 cx: &mut Context<Editor>,
23120 ) -> Task<Result<Vec<CompletionResponse>>> {
23121 self.update(cx, |project, cx| {
23122 let snippets = snippet_completions(project, buffer, buffer_position, cx);
23123 let project_completions = project.completions(buffer, buffer_position, options, cx);
23124 cx.background_spawn(async move {
23125 let mut responses = project_completions.await?;
23126 let snippets = snippets.await?;
23127 if !snippets.completions.is_empty() {
23128 responses.push(snippets);
23129 }
23130 Ok(responses)
23131 })
23132 })
23133 }
23134
23135 fn resolve_completions(
23136 &self,
23137 buffer: Entity<Buffer>,
23138 completion_indices: Vec<usize>,
23139 completions: Rc<RefCell<Box<[Completion]>>>,
23140 cx: &mut Context<Editor>,
23141 ) -> Task<Result<bool>> {
23142 self.update(cx, |project, cx| {
23143 project.lsp_store().update(cx, |lsp_store, cx| {
23144 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23145 })
23146 })
23147 }
23148
23149 fn apply_additional_edits_for_completion(
23150 &self,
23151 buffer: Entity<Buffer>,
23152 completions: Rc<RefCell<Box<[Completion]>>>,
23153 completion_index: usize,
23154 push_to_history: bool,
23155 cx: &mut Context<Editor>,
23156 ) -> Task<Result<Option<language::Transaction>>> {
23157 self.update(cx, |project, cx| {
23158 project.lsp_store().update(cx, |lsp_store, cx| {
23159 lsp_store.apply_additional_edits_for_completion(
23160 buffer,
23161 completions,
23162 completion_index,
23163 push_to_history,
23164 cx,
23165 )
23166 })
23167 })
23168 }
23169
23170 fn is_completion_trigger(
23171 &self,
23172 buffer: &Entity<Buffer>,
23173 position: language::Anchor,
23174 text: &str,
23175 trigger_in_words: bool,
23176 menu_is_open: bool,
23177 cx: &mut Context<Editor>,
23178 ) -> bool {
23179 let mut chars = text.chars();
23180 let char = if let Some(char) = chars.next() {
23181 char
23182 } else {
23183 return false;
23184 };
23185 if chars.next().is_some() {
23186 return false;
23187 }
23188
23189 let buffer = buffer.read(cx);
23190 let snapshot = buffer.snapshot();
23191 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23192 return false;
23193 }
23194 let classifier = snapshot
23195 .char_classifier_at(position)
23196 .scope_context(Some(CharScopeContext::Completion));
23197 if trigger_in_words && classifier.is_word(char) {
23198 return true;
23199 }
23200
23201 buffer.completion_triggers().contains(text)
23202 }
23203}
23204
23205impl SemanticsProvider for Entity<Project> {
23206 fn hover(
23207 &self,
23208 buffer: &Entity<Buffer>,
23209 position: text::Anchor,
23210 cx: &mut App,
23211 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23212 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23213 }
23214
23215 fn document_highlights(
23216 &self,
23217 buffer: &Entity<Buffer>,
23218 position: text::Anchor,
23219 cx: &mut App,
23220 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23221 Some(self.update(cx, |project, cx| {
23222 project.document_highlights(buffer, position, cx)
23223 }))
23224 }
23225
23226 fn definitions(
23227 &self,
23228 buffer: &Entity<Buffer>,
23229 position: text::Anchor,
23230 kind: GotoDefinitionKind,
23231 cx: &mut App,
23232 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23233 Some(self.update(cx, |project, cx| match kind {
23234 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23235 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23236 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23237 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23238 }))
23239 }
23240
23241 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23242 self.update(cx, |project, cx| {
23243 if project
23244 .active_debug_session(cx)
23245 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23246 {
23247 return true;
23248 }
23249
23250 buffer.update(cx, |buffer, cx| {
23251 project.any_language_server_supports_inlay_hints(buffer, cx)
23252 })
23253 })
23254 }
23255
23256 fn inline_values(
23257 &self,
23258 buffer_handle: Entity<Buffer>,
23259 range: Range<text::Anchor>,
23260 cx: &mut App,
23261 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23262 self.update(cx, |project, cx| {
23263 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23264
23265 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23266 })
23267 }
23268
23269 fn inlay_hints(
23270 &self,
23271 buffer_handle: Entity<Buffer>,
23272 range: Range<text::Anchor>,
23273 cx: &mut App,
23274 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23275 Some(self.update(cx, |project, cx| {
23276 project.inlay_hints(buffer_handle, range, cx)
23277 }))
23278 }
23279
23280 fn resolve_inlay_hint(
23281 &self,
23282 hint: InlayHint,
23283 buffer_handle: Entity<Buffer>,
23284 server_id: LanguageServerId,
23285 cx: &mut App,
23286 ) -> Option<Task<anyhow::Result<InlayHint>>> {
23287 Some(self.update(cx, |project, cx| {
23288 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
23289 }))
23290 }
23291
23292 fn range_for_rename(
23293 &self,
23294 buffer: &Entity<Buffer>,
23295 position: text::Anchor,
23296 cx: &mut App,
23297 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23298 Some(self.update(cx, |project, cx| {
23299 let buffer = buffer.clone();
23300 let task = project.prepare_rename(buffer.clone(), position, cx);
23301 cx.spawn(async move |_, cx| {
23302 Ok(match task.await? {
23303 PrepareRenameResponse::Success(range) => Some(range),
23304 PrepareRenameResponse::InvalidPosition => None,
23305 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23306 // Fallback on using TreeSitter info to determine identifier range
23307 buffer.read_with(cx, |buffer, _| {
23308 let snapshot = buffer.snapshot();
23309 let (range, kind) = snapshot.surrounding_word(position, None);
23310 if kind != Some(CharKind::Word) {
23311 return None;
23312 }
23313 Some(
23314 snapshot.anchor_before(range.start)
23315 ..snapshot.anchor_after(range.end),
23316 )
23317 })?
23318 }
23319 })
23320 })
23321 }))
23322 }
23323
23324 fn perform_rename(
23325 &self,
23326 buffer: &Entity<Buffer>,
23327 position: text::Anchor,
23328 new_name: String,
23329 cx: &mut App,
23330 ) -> Option<Task<Result<ProjectTransaction>>> {
23331 Some(self.update(cx, |project, cx| {
23332 project.perform_rename(buffer.clone(), position, new_name, cx)
23333 }))
23334 }
23335}
23336
23337fn inlay_hint_settings(
23338 location: Anchor,
23339 snapshot: &MultiBufferSnapshot,
23340 cx: &mut Context<Editor>,
23341) -> InlayHintSettings {
23342 let file = snapshot.file_at(location);
23343 let language = snapshot.language_at(location).map(|l| l.name());
23344 language_settings(language, file, cx).inlay_hints
23345}
23346
23347fn consume_contiguous_rows(
23348 contiguous_row_selections: &mut Vec<Selection<Point>>,
23349 selection: &Selection<Point>,
23350 display_map: &DisplaySnapshot,
23351 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23352) -> (MultiBufferRow, MultiBufferRow) {
23353 contiguous_row_selections.push(selection.clone());
23354 let start_row = starting_row(selection, display_map);
23355 let mut end_row = ending_row(selection, display_map);
23356
23357 while let Some(next_selection) = selections.peek() {
23358 if next_selection.start.row <= end_row.0 {
23359 end_row = ending_row(next_selection, display_map);
23360 contiguous_row_selections.push(selections.next().unwrap().clone());
23361 } else {
23362 break;
23363 }
23364 }
23365 (start_row, end_row)
23366}
23367
23368fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23369 if selection.start.column > 0 {
23370 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23371 } else {
23372 MultiBufferRow(selection.start.row)
23373 }
23374}
23375
23376fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23377 if next_selection.end.column > 0 || next_selection.is_empty() {
23378 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23379 } else {
23380 MultiBufferRow(next_selection.end.row)
23381 }
23382}
23383
23384impl EditorSnapshot {
23385 pub fn remote_selections_in_range<'a>(
23386 &'a self,
23387 range: &'a Range<Anchor>,
23388 collaboration_hub: &dyn CollaborationHub,
23389 cx: &'a App,
23390 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23391 let participant_names = collaboration_hub.user_names(cx);
23392 let participant_indices = collaboration_hub.user_participant_indices(cx);
23393 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23394 let collaborators_by_replica_id = collaborators_by_peer_id
23395 .values()
23396 .map(|collaborator| (collaborator.replica_id, collaborator))
23397 .collect::<HashMap<_, _>>();
23398 self.buffer_snapshot()
23399 .selections_in_range(range, false)
23400 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23401 if replica_id == AGENT_REPLICA_ID {
23402 Some(RemoteSelection {
23403 replica_id,
23404 selection,
23405 cursor_shape,
23406 line_mode,
23407 collaborator_id: CollaboratorId::Agent,
23408 user_name: Some("Agent".into()),
23409 color: cx.theme().players().agent(),
23410 })
23411 } else {
23412 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23413 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23414 let user_name = participant_names.get(&collaborator.user_id).cloned();
23415 Some(RemoteSelection {
23416 replica_id,
23417 selection,
23418 cursor_shape,
23419 line_mode,
23420 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23421 user_name,
23422 color: if let Some(index) = participant_index {
23423 cx.theme().players().color_for_participant(index.0)
23424 } else {
23425 cx.theme().players().absent()
23426 },
23427 })
23428 }
23429 })
23430 }
23431
23432 pub fn hunks_for_ranges(
23433 &self,
23434 ranges: impl IntoIterator<Item = Range<Point>>,
23435 ) -> Vec<MultiBufferDiffHunk> {
23436 let mut hunks = Vec::new();
23437 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23438 HashMap::default();
23439 for query_range in ranges {
23440 let query_rows =
23441 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23442 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23443 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23444 ) {
23445 // Include deleted hunks that are adjacent to the query range, because
23446 // otherwise they would be missed.
23447 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23448 if hunk.status().is_deleted() {
23449 intersects_range |= hunk.row_range.start == query_rows.end;
23450 intersects_range |= hunk.row_range.end == query_rows.start;
23451 }
23452 if intersects_range {
23453 if !processed_buffer_rows
23454 .entry(hunk.buffer_id)
23455 .or_default()
23456 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23457 {
23458 continue;
23459 }
23460 hunks.push(hunk);
23461 }
23462 }
23463 }
23464
23465 hunks
23466 }
23467
23468 fn display_diff_hunks_for_rows<'a>(
23469 &'a self,
23470 display_rows: Range<DisplayRow>,
23471 folded_buffers: &'a HashSet<BufferId>,
23472 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23473 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23474 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23475
23476 self.buffer_snapshot()
23477 .diff_hunks_in_range(buffer_start..buffer_end)
23478 .filter_map(|hunk| {
23479 if folded_buffers.contains(&hunk.buffer_id) {
23480 return None;
23481 }
23482
23483 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23484 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23485
23486 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23487 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23488
23489 let display_hunk = if hunk_display_start.column() != 0 {
23490 DisplayDiffHunk::Folded {
23491 display_row: hunk_display_start.row(),
23492 }
23493 } else {
23494 let mut end_row = hunk_display_end.row();
23495 if hunk_display_end.column() > 0 {
23496 end_row.0 += 1;
23497 }
23498 let is_created_file = hunk.is_created_file();
23499 DisplayDiffHunk::Unfolded {
23500 status: hunk.status(),
23501 diff_base_byte_range: hunk.diff_base_byte_range,
23502 display_row_range: hunk_display_start.row()..end_row,
23503 multi_buffer_range: Anchor::range_in_buffer(
23504 hunk.excerpt_id,
23505 hunk.buffer_id,
23506 hunk.buffer_range,
23507 ),
23508 is_created_file,
23509 }
23510 };
23511
23512 Some(display_hunk)
23513 })
23514 }
23515
23516 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23517 self.display_snapshot
23518 .buffer_snapshot()
23519 .language_at(position)
23520 }
23521
23522 pub fn is_focused(&self) -> bool {
23523 self.is_focused
23524 }
23525
23526 pub fn placeholder_text(&self) -> Option<String> {
23527 self.placeholder_display_snapshot
23528 .as_ref()
23529 .map(|display_map| display_map.text())
23530 }
23531
23532 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23533 self.scroll_anchor.scroll_position(&self.display_snapshot)
23534 }
23535
23536 fn gutter_dimensions(
23537 &self,
23538 font_id: FontId,
23539 font_size: Pixels,
23540 max_line_number_width: Pixels,
23541 cx: &App,
23542 ) -> Option<GutterDimensions> {
23543 if !self.show_gutter {
23544 return None;
23545 }
23546
23547 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23548 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23549
23550 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23551 matches!(
23552 ProjectSettings::get_global(cx).git.git_gutter,
23553 GitGutterSetting::TrackedFiles
23554 )
23555 });
23556 let gutter_settings = EditorSettings::get_global(cx).gutter;
23557 let show_line_numbers = self
23558 .show_line_numbers
23559 .unwrap_or(gutter_settings.line_numbers);
23560 let line_gutter_width = if show_line_numbers {
23561 // Avoid flicker-like gutter resizes when the line number gains another digit by
23562 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23563 let min_width_for_number_on_gutter =
23564 ch_advance * gutter_settings.min_line_number_digits as f32;
23565 max_line_number_width.max(min_width_for_number_on_gutter)
23566 } else {
23567 0.0.into()
23568 };
23569
23570 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23571 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23572
23573 let git_blame_entries_width =
23574 self.git_blame_gutter_max_author_length
23575 .map(|max_author_length| {
23576 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23577 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23578
23579 /// The number of characters to dedicate to gaps and margins.
23580 const SPACING_WIDTH: usize = 4;
23581
23582 let max_char_count = max_author_length.min(renderer.max_author_length())
23583 + ::git::SHORT_SHA_LENGTH
23584 + MAX_RELATIVE_TIMESTAMP.len()
23585 + SPACING_WIDTH;
23586
23587 ch_advance * max_char_count
23588 });
23589
23590 let is_singleton = self.buffer_snapshot().is_singleton();
23591
23592 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23593 left_padding += if !is_singleton {
23594 ch_width * 4.0
23595 } else if show_runnables || show_breakpoints {
23596 ch_width * 3.0
23597 } else if show_git_gutter && show_line_numbers {
23598 ch_width * 2.0
23599 } else if show_git_gutter || show_line_numbers {
23600 ch_width
23601 } else {
23602 px(0.)
23603 };
23604
23605 let shows_folds = is_singleton && gutter_settings.folds;
23606
23607 let right_padding = if shows_folds && show_line_numbers {
23608 ch_width * 4.0
23609 } else if shows_folds || (!is_singleton && show_line_numbers) {
23610 ch_width * 3.0
23611 } else if show_line_numbers {
23612 ch_width
23613 } else {
23614 px(0.)
23615 };
23616
23617 Some(GutterDimensions {
23618 left_padding,
23619 right_padding,
23620 width: line_gutter_width + left_padding + right_padding,
23621 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23622 git_blame_entries_width,
23623 })
23624 }
23625
23626 pub fn render_crease_toggle(
23627 &self,
23628 buffer_row: MultiBufferRow,
23629 row_contains_cursor: bool,
23630 editor: Entity<Editor>,
23631 window: &mut Window,
23632 cx: &mut App,
23633 ) -> Option<AnyElement> {
23634 let folded = self.is_line_folded(buffer_row);
23635 let mut is_foldable = false;
23636
23637 if let Some(crease) = self
23638 .crease_snapshot
23639 .query_row(buffer_row, self.buffer_snapshot())
23640 {
23641 is_foldable = true;
23642 match crease {
23643 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23644 if let Some(render_toggle) = render_toggle {
23645 let toggle_callback =
23646 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23647 if folded {
23648 editor.update(cx, |editor, cx| {
23649 editor.fold_at(buffer_row, window, cx)
23650 });
23651 } else {
23652 editor.update(cx, |editor, cx| {
23653 editor.unfold_at(buffer_row, window, cx)
23654 });
23655 }
23656 });
23657 return Some((render_toggle)(
23658 buffer_row,
23659 folded,
23660 toggle_callback,
23661 window,
23662 cx,
23663 ));
23664 }
23665 }
23666 }
23667 }
23668
23669 is_foldable |= self.starts_indent(buffer_row);
23670
23671 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23672 Some(
23673 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23674 .toggle_state(folded)
23675 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23676 if folded {
23677 this.unfold_at(buffer_row, window, cx);
23678 } else {
23679 this.fold_at(buffer_row, window, cx);
23680 }
23681 }))
23682 .into_any_element(),
23683 )
23684 } else {
23685 None
23686 }
23687 }
23688
23689 pub fn render_crease_trailer(
23690 &self,
23691 buffer_row: MultiBufferRow,
23692 window: &mut Window,
23693 cx: &mut App,
23694 ) -> Option<AnyElement> {
23695 let folded = self.is_line_folded(buffer_row);
23696 if let Crease::Inline { render_trailer, .. } = self
23697 .crease_snapshot
23698 .query_row(buffer_row, self.buffer_snapshot())?
23699 {
23700 let render_trailer = render_trailer.as_ref()?;
23701 Some(render_trailer(buffer_row, folded, window, cx))
23702 } else {
23703 None
23704 }
23705 }
23706}
23707
23708impl Deref for EditorSnapshot {
23709 type Target = DisplaySnapshot;
23710
23711 fn deref(&self) -> &Self::Target {
23712 &self.display_snapshot
23713 }
23714}
23715
23716#[derive(Clone, Debug, PartialEq, Eq)]
23717pub enum EditorEvent {
23718 InputIgnored {
23719 text: Arc<str>,
23720 },
23721 InputHandled {
23722 utf16_range_to_replace: Option<Range<isize>>,
23723 text: Arc<str>,
23724 },
23725 ExcerptsAdded {
23726 buffer: Entity<Buffer>,
23727 predecessor: ExcerptId,
23728 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23729 },
23730 ExcerptsRemoved {
23731 ids: Vec<ExcerptId>,
23732 removed_buffer_ids: Vec<BufferId>,
23733 },
23734 BufferFoldToggled {
23735 ids: Vec<ExcerptId>,
23736 folded: bool,
23737 },
23738 ExcerptsEdited {
23739 ids: Vec<ExcerptId>,
23740 },
23741 ExcerptsExpanded {
23742 ids: Vec<ExcerptId>,
23743 },
23744 BufferEdited,
23745 Edited {
23746 transaction_id: clock::Lamport,
23747 },
23748 Reparsed(BufferId),
23749 Focused,
23750 FocusedIn,
23751 Blurred,
23752 DirtyChanged,
23753 Saved,
23754 TitleChanged,
23755 SelectionsChanged {
23756 local: bool,
23757 },
23758 ScrollPositionChanged {
23759 local: bool,
23760 autoscroll: bool,
23761 },
23762 TransactionUndone {
23763 transaction_id: clock::Lamport,
23764 },
23765 TransactionBegun {
23766 transaction_id: clock::Lamport,
23767 },
23768 CursorShapeChanged,
23769 BreadcrumbsChanged,
23770 PushedToNavHistory {
23771 anchor: Anchor,
23772 is_deactivate: bool,
23773 },
23774}
23775
23776impl EventEmitter<EditorEvent> for Editor {}
23777
23778impl Focusable for Editor {
23779 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23780 self.focus_handle.clone()
23781 }
23782}
23783
23784impl Render for Editor {
23785 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23786 let settings = ThemeSettings::get_global(cx);
23787
23788 let mut text_style = match self.mode {
23789 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23790 color: cx.theme().colors().editor_foreground,
23791 font_family: settings.ui_font.family.clone(),
23792 font_features: settings.ui_font.features.clone(),
23793 font_fallbacks: settings.ui_font.fallbacks.clone(),
23794 font_size: rems(0.875).into(),
23795 font_weight: settings.ui_font.weight,
23796 line_height: relative(settings.buffer_line_height.value()),
23797 ..Default::default()
23798 },
23799 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23800 color: cx.theme().colors().editor_foreground,
23801 font_family: settings.buffer_font.family.clone(),
23802 font_features: settings.buffer_font.features.clone(),
23803 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23804 font_size: settings.buffer_font_size(cx).into(),
23805 font_weight: settings.buffer_font.weight,
23806 line_height: relative(settings.buffer_line_height.value()),
23807 ..Default::default()
23808 },
23809 };
23810 if let Some(text_style_refinement) = &self.text_style_refinement {
23811 text_style.refine(text_style_refinement)
23812 }
23813
23814 let background = match self.mode {
23815 EditorMode::SingleLine => cx.theme().system().transparent,
23816 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23817 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23818 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23819 };
23820
23821 EditorElement::new(
23822 &cx.entity(),
23823 EditorStyle {
23824 background,
23825 border: cx.theme().colors().border,
23826 local_player: cx.theme().players().local(),
23827 text: text_style,
23828 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23829 syntax: cx.theme().syntax().clone(),
23830 status: cx.theme().status().clone(),
23831 inlay_hints_style: make_inlay_hints_style(cx),
23832 edit_prediction_styles: make_suggestion_styles(cx),
23833 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23834 show_underlines: self.diagnostics_enabled(),
23835 },
23836 )
23837 }
23838}
23839
23840impl EntityInputHandler for Editor {
23841 fn text_for_range(
23842 &mut self,
23843 range_utf16: Range<usize>,
23844 adjusted_range: &mut Option<Range<usize>>,
23845 _: &mut Window,
23846 cx: &mut Context<Self>,
23847 ) -> Option<String> {
23848 let snapshot = self.buffer.read(cx).read(cx);
23849 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23850 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23851 if (start.0..end.0) != range_utf16 {
23852 adjusted_range.replace(start.0..end.0);
23853 }
23854 Some(snapshot.text_for_range(start..end).collect())
23855 }
23856
23857 fn selected_text_range(
23858 &mut self,
23859 ignore_disabled_input: bool,
23860 _: &mut Window,
23861 cx: &mut Context<Self>,
23862 ) -> Option<UTF16Selection> {
23863 // Prevent the IME menu from appearing when holding down an alphabetic key
23864 // while input is disabled.
23865 if !ignore_disabled_input && !self.input_enabled {
23866 return None;
23867 }
23868
23869 let selection = self.selections.newest::<OffsetUtf16>(cx);
23870 let range = selection.range();
23871
23872 Some(UTF16Selection {
23873 range: range.start.0..range.end.0,
23874 reversed: selection.reversed,
23875 })
23876 }
23877
23878 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23879 let snapshot = self.buffer.read(cx).read(cx);
23880 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23881 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23882 }
23883
23884 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23885 self.clear_highlights::<InputComposition>(cx);
23886 self.ime_transaction.take();
23887 }
23888
23889 fn replace_text_in_range(
23890 &mut self,
23891 range_utf16: Option<Range<usize>>,
23892 text: &str,
23893 window: &mut Window,
23894 cx: &mut Context<Self>,
23895 ) {
23896 if !self.input_enabled {
23897 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23898 return;
23899 }
23900
23901 self.transact(window, cx, |this, window, cx| {
23902 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23903 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23904 Some(this.selection_replacement_ranges(range_utf16, cx))
23905 } else {
23906 this.marked_text_ranges(cx)
23907 };
23908
23909 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23910 let newest_selection_id = this.selections.newest_anchor().id;
23911 this.selections
23912 .all::<OffsetUtf16>(cx)
23913 .iter()
23914 .zip(ranges_to_replace.iter())
23915 .find_map(|(selection, range)| {
23916 if selection.id == newest_selection_id {
23917 Some(
23918 (range.start.0 as isize - selection.head().0 as isize)
23919 ..(range.end.0 as isize - selection.head().0 as isize),
23920 )
23921 } else {
23922 None
23923 }
23924 })
23925 });
23926
23927 cx.emit(EditorEvent::InputHandled {
23928 utf16_range_to_replace: range_to_replace,
23929 text: text.into(),
23930 });
23931
23932 if let Some(new_selected_ranges) = new_selected_ranges {
23933 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23934 selections.select_ranges(new_selected_ranges)
23935 });
23936 this.backspace(&Default::default(), window, cx);
23937 }
23938
23939 this.handle_input(text, window, cx);
23940 });
23941
23942 if let Some(transaction) = self.ime_transaction {
23943 self.buffer.update(cx, |buffer, cx| {
23944 buffer.group_until_transaction(transaction, cx);
23945 });
23946 }
23947
23948 self.unmark_text(window, cx);
23949 }
23950
23951 fn replace_and_mark_text_in_range(
23952 &mut self,
23953 range_utf16: Option<Range<usize>>,
23954 text: &str,
23955 new_selected_range_utf16: Option<Range<usize>>,
23956 window: &mut Window,
23957 cx: &mut Context<Self>,
23958 ) {
23959 if !self.input_enabled {
23960 return;
23961 }
23962
23963 let transaction = self.transact(window, cx, |this, window, cx| {
23964 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23965 let snapshot = this.buffer.read(cx).read(cx);
23966 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23967 for marked_range in &mut marked_ranges {
23968 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23969 marked_range.start.0 += relative_range_utf16.start;
23970 marked_range.start =
23971 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23972 marked_range.end =
23973 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23974 }
23975 }
23976 Some(marked_ranges)
23977 } else if let Some(range_utf16) = range_utf16 {
23978 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23979 Some(this.selection_replacement_ranges(range_utf16, cx))
23980 } else {
23981 None
23982 };
23983
23984 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23985 let newest_selection_id = this.selections.newest_anchor().id;
23986 this.selections
23987 .all::<OffsetUtf16>(cx)
23988 .iter()
23989 .zip(ranges_to_replace.iter())
23990 .find_map(|(selection, range)| {
23991 if selection.id == newest_selection_id {
23992 Some(
23993 (range.start.0 as isize - selection.head().0 as isize)
23994 ..(range.end.0 as isize - selection.head().0 as isize),
23995 )
23996 } else {
23997 None
23998 }
23999 })
24000 });
24001
24002 cx.emit(EditorEvent::InputHandled {
24003 utf16_range_to_replace: range_to_replace,
24004 text: text.into(),
24005 });
24006
24007 if let Some(ranges) = ranges_to_replace {
24008 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24009 s.select_ranges(ranges)
24010 });
24011 }
24012
24013 let marked_ranges = {
24014 let snapshot = this.buffer.read(cx).read(cx);
24015 this.selections
24016 .disjoint_anchors_arc()
24017 .iter()
24018 .map(|selection| {
24019 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24020 })
24021 .collect::<Vec<_>>()
24022 };
24023
24024 if text.is_empty() {
24025 this.unmark_text(window, cx);
24026 } else {
24027 this.highlight_text::<InputComposition>(
24028 marked_ranges.clone(),
24029 HighlightStyle {
24030 underline: Some(UnderlineStyle {
24031 thickness: px(1.),
24032 color: None,
24033 wavy: false,
24034 }),
24035 ..Default::default()
24036 },
24037 cx,
24038 );
24039 }
24040
24041 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24042 let use_autoclose = this.use_autoclose;
24043 let use_auto_surround = this.use_auto_surround;
24044 this.set_use_autoclose(false);
24045 this.set_use_auto_surround(false);
24046 this.handle_input(text, window, cx);
24047 this.set_use_autoclose(use_autoclose);
24048 this.set_use_auto_surround(use_auto_surround);
24049
24050 if let Some(new_selected_range) = new_selected_range_utf16 {
24051 let snapshot = this.buffer.read(cx).read(cx);
24052 let new_selected_ranges = marked_ranges
24053 .into_iter()
24054 .map(|marked_range| {
24055 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24056 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
24057 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
24058 snapshot.clip_offset_utf16(new_start, Bias::Left)
24059 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24060 })
24061 .collect::<Vec<_>>();
24062
24063 drop(snapshot);
24064 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24065 selections.select_ranges(new_selected_ranges)
24066 });
24067 }
24068 });
24069
24070 self.ime_transaction = self.ime_transaction.or(transaction);
24071 if let Some(transaction) = self.ime_transaction {
24072 self.buffer.update(cx, |buffer, cx| {
24073 buffer.group_until_transaction(transaction, cx);
24074 });
24075 }
24076
24077 if self.text_highlights::<InputComposition>(cx).is_none() {
24078 self.ime_transaction.take();
24079 }
24080 }
24081
24082 fn bounds_for_range(
24083 &mut self,
24084 range_utf16: Range<usize>,
24085 element_bounds: gpui::Bounds<Pixels>,
24086 window: &mut Window,
24087 cx: &mut Context<Self>,
24088 ) -> Option<gpui::Bounds<Pixels>> {
24089 let text_layout_details = self.text_layout_details(window);
24090 let CharacterDimensions {
24091 em_width,
24092 em_advance,
24093 line_height,
24094 } = self.character_dimensions(window);
24095
24096 let snapshot = self.snapshot(window, cx);
24097 let scroll_position = snapshot.scroll_position();
24098 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24099
24100 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
24101 let x = Pixels::from(
24102 ScrollOffset::from(
24103 snapshot.x_for_display_point(start, &text_layout_details)
24104 + self.gutter_dimensions.full_width(),
24105 ) - scroll_left,
24106 );
24107 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24108
24109 Some(Bounds {
24110 origin: element_bounds.origin + point(x, y),
24111 size: size(em_width, line_height),
24112 })
24113 }
24114
24115 fn character_index_for_point(
24116 &mut self,
24117 point: gpui::Point<Pixels>,
24118 _window: &mut Window,
24119 _cx: &mut Context<Self>,
24120 ) -> Option<usize> {
24121 let position_map = self.last_position_map.as_ref()?;
24122 if !position_map.text_hitbox.contains(&point) {
24123 return None;
24124 }
24125 let display_point = position_map.point_for_position(point).previous_valid;
24126 let anchor = position_map
24127 .snapshot
24128 .display_point_to_anchor(display_point, Bias::Left);
24129 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24130 Some(utf16_offset.0)
24131 }
24132}
24133
24134trait SelectionExt {
24135 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24136 fn spanned_rows(
24137 &self,
24138 include_end_if_at_line_start: bool,
24139 map: &DisplaySnapshot,
24140 ) -> Range<MultiBufferRow>;
24141}
24142
24143impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24144 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24145 let start = self
24146 .start
24147 .to_point(map.buffer_snapshot())
24148 .to_display_point(map);
24149 let end = self
24150 .end
24151 .to_point(map.buffer_snapshot())
24152 .to_display_point(map);
24153 if self.reversed {
24154 end..start
24155 } else {
24156 start..end
24157 }
24158 }
24159
24160 fn spanned_rows(
24161 &self,
24162 include_end_if_at_line_start: bool,
24163 map: &DisplaySnapshot,
24164 ) -> Range<MultiBufferRow> {
24165 let start = self.start.to_point(map.buffer_snapshot());
24166 let mut end = self.end.to_point(map.buffer_snapshot());
24167 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24168 end.row -= 1;
24169 }
24170
24171 let buffer_start = map.prev_line_boundary(start).0;
24172 let buffer_end = map.next_line_boundary(end).0;
24173 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24174 }
24175}
24176
24177impl<T: InvalidationRegion> InvalidationStack<T> {
24178 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24179 where
24180 S: Clone + ToOffset,
24181 {
24182 while let Some(region) = self.last() {
24183 let all_selections_inside_invalidation_ranges =
24184 if selections.len() == region.ranges().len() {
24185 selections
24186 .iter()
24187 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24188 .all(|(selection, invalidation_range)| {
24189 let head = selection.head().to_offset(buffer);
24190 invalidation_range.start <= head && invalidation_range.end >= head
24191 })
24192 } else {
24193 false
24194 };
24195
24196 if all_selections_inside_invalidation_ranges {
24197 break;
24198 } else {
24199 self.pop();
24200 }
24201 }
24202 }
24203}
24204
24205impl<T> Default for InvalidationStack<T> {
24206 fn default() -> Self {
24207 Self(Default::default())
24208 }
24209}
24210
24211impl<T> Deref for InvalidationStack<T> {
24212 type Target = Vec<T>;
24213
24214 fn deref(&self) -> &Self::Target {
24215 &self.0
24216 }
24217}
24218
24219impl<T> DerefMut for InvalidationStack<T> {
24220 fn deref_mut(&mut self) -> &mut Self::Target {
24221 &mut self.0
24222 }
24223}
24224
24225impl InvalidationRegion for SnippetState {
24226 fn ranges(&self) -> &[Range<Anchor>] {
24227 &self.ranges[self.active_index]
24228 }
24229}
24230
24231fn edit_prediction_edit_text(
24232 current_snapshot: &BufferSnapshot,
24233 edits: &[(Range<Anchor>, String)],
24234 edit_preview: &EditPreview,
24235 include_deletions: bool,
24236 cx: &App,
24237) -> HighlightedText {
24238 let edits = edits
24239 .iter()
24240 .map(|(anchor, text)| {
24241 (
24242 anchor.start.text_anchor..anchor.end.text_anchor,
24243 text.clone(),
24244 )
24245 })
24246 .collect::<Vec<_>>();
24247
24248 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24249}
24250
24251fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
24252 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24253 // Just show the raw edit text with basic styling
24254 let mut text = String::new();
24255 let mut highlights = Vec::new();
24256
24257 let insertion_highlight_style = HighlightStyle {
24258 color: Some(cx.theme().colors().text),
24259 ..Default::default()
24260 };
24261
24262 for (_, edit_text) in edits {
24263 let start_offset = text.len();
24264 text.push_str(edit_text);
24265 let end_offset = text.len();
24266
24267 if start_offset < end_offset {
24268 highlights.push((start_offset..end_offset, insertion_highlight_style));
24269 }
24270 }
24271
24272 HighlightedText {
24273 text: text.into(),
24274 highlights,
24275 }
24276}
24277
24278pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24279 match severity {
24280 lsp::DiagnosticSeverity::ERROR => colors.error,
24281 lsp::DiagnosticSeverity::WARNING => colors.warning,
24282 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24283 lsp::DiagnosticSeverity::HINT => colors.info,
24284 _ => colors.ignored,
24285 }
24286}
24287
24288pub fn styled_runs_for_code_label<'a>(
24289 label: &'a CodeLabel,
24290 syntax_theme: &'a theme::SyntaxTheme,
24291) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24292 let fade_out = HighlightStyle {
24293 fade_out: Some(0.35),
24294 ..Default::default()
24295 };
24296
24297 let mut prev_end = label.filter_range.end;
24298 label
24299 .runs
24300 .iter()
24301 .enumerate()
24302 .flat_map(move |(ix, (range, highlight_id))| {
24303 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24304 style
24305 } else {
24306 return Default::default();
24307 };
24308 let muted_style = style.highlight(fade_out);
24309
24310 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24311 if range.start >= label.filter_range.end {
24312 if range.start > prev_end {
24313 runs.push((prev_end..range.start, fade_out));
24314 }
24315 runs.push((range.clone(), muted_style));
24316 } else if range.end <= label.filter_range.end {
24317 runs.push((range.clone(), style));
24318 } else {
24319 runs.push((range.start..label.filter_range.end, style));
24320 runs.push((label.filter_range.end..range.end, muted_style));
24321 }
24322 prev_end = cmp::max(prev_end, range.end);
24323
24324 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24325 runs.push((prev_end..label.text.len(), fade_out));
24326 }
24327
24328 runs
24329 })
24330}
24331
24332pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24333 let mut prev_index = 0;
24334 let mut prev_codepoint: Option<char> = None;
24335 text.char_indices()
24336 .chain([(text.len(), '\0')])
24337 .filter_map(move |(index, codepoint)| {
24338 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24339 let is_boundary = index == text.len()
24340 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24341 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24342 if is_boundary {
24343 let chunk = &text[prev_index..index];
24344 prev_index = index;
24345 Some(chunk)
24346 } else {
24347 None
24348 }
24349 })
24350}
24351
24352pub trait RangeToAnchorExt: Sized {
24353 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24354
24355 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24356 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24357 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24358 }
24359}
24360
24361impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24362 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24363 let start_offset = self.start.to_offset(snapshot);
24364 let end_offset = self.end.to_offset(snapshot);
24365 if start_offset == end_offset {
24366 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24367 } else {
24368 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24369 }
24370 }
24371}
24372
24373pub trait RowExt {
24374 fn as_f64(&self) -> f64;
24375
24376 fn next_row(&self) -> Self;
24377
24378 fn previous_row(&self) -> Self;
24379
24380 fn minus(&self, other: Self) -> u32;
24381}
24382
24383impl RowExt for DisplayRow {
24384 fn as_f64(&self) -> f64 {
24385 self.0 as _
24386 }
24387
24388 fn next_row(&self) -> Self {
24389 Self(self.0 + 1)
24390 }
24391
24392 fn previous_row(&self) -> Self {
24393 Self(self.0.saturating_sub(1))
24394 }
24395
24396 fn minus(&self, other: Self) -> u32 {
24397 self.0 - other.0
24398 }
24399}
24400
24401impl RowExt for MultiBufferRow {
24402 fn as_f64(&self) -> f64 {
24403 self.0 as _
24404 }
24405
24406 fn next_row(&self) -> Self {
24407 Self(self.0 + 1)
24408 }
24409
24410 fn previous_row(&self) -> Self {
24411 Self(self.0.saturating_sub(1))
24412 }
24413
24414 fn minus(&self, other: Self) -> u32 {
24415 self.0 - other.0
24416 }
24417}
24418
24419trait RowRangeExt {
24420 type Row;
24421
24422 fn len(&self) -> usize;
24423
24424 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24425}
24426
24427impl RowRangeExt for Range<MultiBufferRow> {
24428 type Row = MultiBufferRow;
24429
24430 fn len(&self) -> usize {
24431 (self.end.0 - self.start.0) as usize
24432 }
24433
24434 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24435 (self.start.0..self.end.0).map(MultiBufferRow)
24436 }
24437}
24438
24439impl RowRangeExt for Range<DisplayRow> {
24440 type Row = DisplayRow;
24441
24442 fn len(&self) -> usize {
24443 (self.end.0 - self.start.0) as usize
24444 }
24445
24446 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24447 (self.start.0..self.end.0).map(DisplayRow)
24448 }
24449}
24450
24451/// If select range has more than one line, we
24452/// just point the cursor to range.start.
24453fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24454 if range.start.row == range.end.row {
24455 range
24456 } else {
24457 range.start..range.start
24458 }
24459}
24460pub struct KillRing(ClipboardItem);
24461impl Global for KillRing {}
24462
24463const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24464
24465enum BreakpointPromptEditAction {
24466 Log,
24467 Condition,
24468 HitCondition,
24469}
24470
24471struct BreakpointPromptEditor {
24472 pub(crate) prompt: Entity<Editor>,
24473 editor: WeakEntity<Editor>,
24474 breakpoint_anchor: Anchor,
24475 breakpoint: Breakpoint,
24476 edit_action: BreakpointPromptEditAction,
24477 block_ids: HashSet<CustomBlockId>,
24478 editor_margins: Arc<Mutex<EditorMargins>>,
24479 _subscriptions: Vec<Subscription>,
24480}
24481
24482impl BreakpointPromptEditor {
24483 const MAX_LINES: u8 = 4;
24484
24485 fn new(
24486 editor: WeakEntity<Editor>,
24487 breakpoint_anchor: Anchor,
24488 breakpoint: Breakpoint,
24489 edit_action: BreakpointPromptEditAction,
24490 window: &mut Window,
24491 cx: &mut Context<Self>,
24492 ) -> Self {
24493 let base_text = match edit_action {
24494 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24495 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24496 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24497 }
24498 .map(|msg| msg.to_string())
24499 .unwrap_or_default();
24500
24501 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24502 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24503
24504 let prompt = cx.new(|cx| {
24505 let mut prompt = Editor::new(
24506 EditorMode::AutoHeight {
24507 min_lines: 1,
24508 max_lines: Some(Self::MAX_LINES as usize),
24509 },
24510 buffer,
24511 None,
24512 window,
24513 cx,
24514 );
24515 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24516 prompt.set_show_cursor_when_unfocused(false, cx);
24517 prompt.set_placeholder_text(
24518 match edit_action {
24519 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24520 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24521 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24522 },
24523 window,
24524 cx,
24525 );
24526
24527 prompt
24528 });
24529
24530 Self {
24531 prompt,
24532 editor,
24533 breakpoint_anchor,
24534 breakpoint,
24535 edit_action,
24536 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24537 block_ids: Default::default(),
24538 _subscriptions: vec![],
24539 }
24540 }
24541
24542 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24543 self.block_ids.extend(block_ids)
24544 }
24545
24546 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24547 if let Some(editor) = self.editor.upgrade() {
24548 let message = self
24549 .prompt
24550 .read(cx)
24551 .buffer
24552 .read(cx)
24553 .as_singleton()
24554 .expect("A multi buffer in breakpoint prompt isn't possible")
24555 .read(cx)
24556 .as_rope()
24557 .to_string();
24558
24559 editor.update(cx, |editor, cx| {
24560 editor.edit_breakpoint_at_anchor(
24561 self.breakpoint_anchor,
24562 self.breakpoint.clone(),
24563 match self.edit_action {
24564 BreakpointPromptEditAction::Log => {
24565 BreakpointEditAction::EditLogMessage(message.into())
24566 }
24567 BreakpointPromptEditAction::Condition => {
24568 BreakpointEditAction::EditCondition(message.into())
24569 }
24570 BreakpointPromptEditAction::HitCondition => {
24571 BreakpointEditAction::EditHitCondition(message.into())
24572 }
24573 },
24574 cx,
24575 );
24576
24577 editor.remove_blocks(self.block_ids.clone(), None, cx);
24578 cx.focus_self(window);
24579 });
24580 }
24581 }
24582
24583 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24584 self.editor
24585 .update(cx, |editor, cx| {
24586 editor.remove_blocks(self.block_ids.clone(), None, cx);
24587 window.focus(&editor.focus_handle);
24588 })
24589 .log_err();
24590 }
24591
24592 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24593 let settings = ThemeSettings::get_global(cx);
24594 let text_style = TextStyle {
24595 color: if self.prompt.read(cx).read_only(cx) {
24596 cx.theme().colors().text_disabled
24597 } else {
24598 cx.theme().colors().text
24599 },
24600 font_family: settings.buffer_font.family.clone(),
24601 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24602 font_size: settings.buffer_font_size(cx).into(),
24603 font_weight: settings.buffer_font.weight,
24604 line_height: relative(settings.buffer_line_height.value()),
24605 ..Default::default()
24606 };
24607 EditorElement::new(
24608 &self.prompt,
24609 EditorStyle {
24610 background: cx.theme().colors().editor_background,
24611 local_player: cx.theme().players().local(),
24612 text: text_style,
24613 ..Default::default()
24614 },
24615 )
24616 }
24617}
24618
24619impl Render for BreakpointPromptEditor {
24620 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24621 let editor_margins = *self.editor_margins.lock();
24622 let gutter_dimensions = editor_margins.gutter;
24623 h_flex()
24624 .key_context("Editor")
24625 .bg(cx.theme().colors().editor_background)
24626 .border_y_1()
24627 .border_color(cx.theme().status().info_border)
24628 .size_full()
24629 .py(window.line_height() / 2.5)
24630 .on_action(cx.listener(Self::confirm))
24631 .on_action(cx.listener(Self::cancel))
24632 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24633 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24634 }
24635}
24636
24637impl Focusable for BreakpointPromptEditor {
24638 fn focus_handle(&self, cx: &App) -> FocusHandle {
24639 self.prompt.focus_handle(cx)
24640 }
24641}
24642
24643fn all_edits_insertions_or_deletions(
24644 edits: &Vec<(Range<Anchor>, String)>,
24645 snapshot: &MultiBufferSnapshot,
24646) -> bool {
24647 let mut all_insertions = true;
24648 let mut all_deletions = true;
24649
24650 for (range, new_text) in edits.iter() {
24651 let range_is_empty = range.to_offset(snapshot).is_empty();
24652 let text_is_empty = new_text.is_empty();
24653
24654 if range_is_empty != text_is_empty {
24655 if range_is_empty {
24656 all_deletions = false;
24657 } else {
24658 all_insertions = false;
24659 }
24660 } else {
24661 return false;
24662 }
24663
24664 if !all_insertions && !all_deletions {
24665 return false;
24666 }
24667 }
24668 all_insertions || all_deletions
24669}
24670
24671struct MissingEditPredictionKeybindingTooltip;
24672
24673impl Render for MissingEditPredictionKeybindingTooltip {
24674 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24675 ui::tooltip_container(cx, |container, cx| {
24676 container
24677 .flex_shrink_0()
24678 .max_w_80()
24679 .min_h(rems_from_px(124.))
24680 .justify_between()
24681 .child(
24682 v_flex()
24683 .flex_1()
24684 .text_ui_sm(cx)
24685 .child(Label::new("Conflict with Accept Keybinding"))
24686 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24687 )
24688 .child(
24689 h_flex()
24690 .pb_1()
24691 .gap_1()
24692 .items_end()
24693 .w_full()
24694 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24695 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
24696 }))
24697 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24698 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24699 })),
24700 )
24701 })
24702 }
24703}
24704
24705#[derive(Debug, Clone, Copy, PartialEq)]
24706pub struct LineHighlight {
24707 pub background: Background,
24708 pub border: Option<gpui::Hsla>,
24709 pub include_gutter: bool,
24710 pub type_id: Option<TypeId>,
24711}
24712
24713struct LineManipulationResult {
24714 pub new_text: String,
24715 pub line_count_before: usize,
24716 pub line_count_after: usize,
24717}
24718
24719fn render_diff_hunk_controls(
24720 row: u32,
24721 status: &DiffHunkStatus,
24722 hunk_range: Range<Anchor>,
24723 is_created_file: bool,
24724 line_height: Pixels,
24725 editor: &Entity<Editor>,
24726 _window: &mut Window,
24727 cx: &mut App,
24728) -> AnyElement {
24729 h_flex()
24730 .h(line_height)
24731 .mr_1()
24732 .gap_1()
24733 .px_0p5()
24734 .pb_1()
24735 .border_x_1()
24736 .border_b_1()
24737 .border_color(cx.theme().colors().border_variant)
24738 .rounded_b_lg()
24739 .bg(cx.theme().colors().editor_background)
24740 .gap_1()
24741 .block_mouse_except_scroll()
24742 .shadow_md()
24743 .child(if status.has_secondary_hunk() {
24744 Button::new(("stage", row as u64), "Stage")
24745 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24746 .tooltip({
24747 let focus_handle = editor.focus_handle(cx);
24748 move |window, cx| {
24749 Tooltip::for_action_in(
24750 "Stage Hunk",
24751 &::git::ToggleStaged,
24752 &focus_handle,
24753 window,
24754 cx,
24755 )
24756 }
24757 })
24758 .on_click({
24759 let editor = editor.clone();
24760 move |_event, _window, cx| {
24761 editor.update(cx, |editor, cx| {
24762 editor.stage_or_unstage_diff_hunks(
24763 true,
24764 vec![hunk_range.start..hunk_range.start],
24765 cx,
24766 );
24767 });
24768 }
24769 })
24770 } else {
24771 Button::new(("unstage", row as u64), "Unstage")
24772 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24773 .tooltip({
24774 let focus_handle = editor.focus_handle(cx);
24775 move |window, cx| {
24776 Tooltip::for_action_in(
24777 "Unstage Hunk",
24778 &::git::ToggleStaged,
24779 &focus_handle,
24780 window,
24781 cx,
24782 )
24783 }
24784 })
24785 .on_click({
24786 let editor = editor.clone();
24787 move |_event, _window, cx| {
24788 editor.update(cx, |editor, cx| {
24789 editor.stage_or_unstage_diff_hunks(
24790 false,
24791 vec![hunk_range.start..hunk_range.start],
24792 cx,
24793 );
24794 });
24795 }
24796 })
24797 })
24798 .child(
24799 Button::new(("restore", row as u64), "Restore")
24800 .tooltip({
24801 let focus_handle = editor.focus_handle(cx);
24802 move |window, cx| {
24803 Tooltip::for_action_in(
24804 "Restore Hunk",
24805 &::git::Restore,
24806 &focus_handle,
24807 window,
24808 cx,
24809 )
24810 }
24811 })
24812 .on_click({
24813 let editor = editor.clone();
24814 move |_event, window, cx| {
24815 editor.update(cx, |editor, cx| {
24816 let snapshot = editor.snapshot(window, cx);
24817 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
24818 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24819 });
24820 }
24821 })
24822 .disabled(is_created_file),
24823 )
24824 .when(
24825 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24826 |el| {
24827 el.child(
24828 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24829 .shape(IconButtonShape::Square)
24830 .icon_size(IconSize::Small)
24831 // .disabled(!has_multiple_hunks)
24832 .tooltip({
24833 let focus_handle = editor.focus_handle(cx);
24834 move |window, cx| {
24835 Tooltip::for_action_in(
24836 "Next Hunk",
24837 &GoToHunk,
24838 &focus_handle,
24839 window,
24840 cx,
24841 )
24842 }
24843 })
24844 .on_click({
24845 let editor = editor.clone();
24846 move |_event, window, cx| {
24847 editor.update(cx, |editor, cx| {
24848 let snapshot = editor.snapshot(window, cx);
24849 let position =
24850 hunk_range.end.to_point(&snapshot.buffer_snapshot());
24851 editor.go_to_hunk_before_or_after_position(
24852 &snapshot,
24853 position,
24854 Direction::Next,
24855 window,
24856 cx,
24857 );
24858 editor.expand_selected_diff_hunks(cx);
24859 });
24860 }
24861 }),
24862 )
24863 .child(
24864 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24865 .shape(IconButtonShape::Square)
24866 .icon_size(IconSize::Small)
24867 // .disabled(!has_multiple_hunks)
24868 .tooltip({
24869 let focus_handle = editor.focus_handle(cx);
24870 move |window, cx| {
24871 Tooltip::for_action_in(
24872 "Previous Hunk",
24873 &GoToPreviousHunk,
24874 &focus_handle,
24875 window,
24876 cx,
24877 )
24878 }
24879 })
24880 .on_click({
24881 let editor = editor.clone();
24882 move |_event, window, cx| {
24883 editor.update(cx, |editor, cx| {
24884 let snapshot = editor.snapshot(window, cx);
24885 let point =
24886 hunk_range.start.to_point(&snapshot.buffer_snapshot());
24887 editor.go_to_hunk_before_or_after_position(
24888 &snapshot,
24889 point,
24890 Direction::Prev,
24891 window,
24892 cx,
24893 );
24894 editor.expand_selected_diff_hunks(cx);
24895 });
24896 }
24897 }),
24898 )
24899 },
24900 )
24901 .into_any_element()
24902}
24903
24904pub fn multibuffer_context_lines(cx: &App) -> u32 {
24905 EditorSettings::try_get(cx)
24906 .map(|settings| settings.excerpt_context_lines)
24907 .unwrap_or(2)
24908 .min(32)
24909}