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 excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5993 let multibuffer_anchor = snapshot
5994 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5995 .unwrap()
5996 ..snapshot
5997 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5998 .unwrap();
5999 multibuffer_anchor.start.to_offset(&snapshot)
6000 ..multibuffer_anchor.end.to_offset(&snapshot)
6001 };
6002 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6003 return None;
6004 }
6005
6006 let old_text = buffer
6007 .text_for_range(replace_range.clone())
6008 .collect::<String>();
6009 let lookbehind = newest_anchor
6010 .start
6011 .text_anchor
6012 .to_offset(buffer)
6013 .saturating_sub(replace_range.start);
6014 let lookahead = replace_range
6015 .end
6016 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6017 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6018 let suffix = &old_text[lookbehind.min(old_text.len())..];
6019
6020 let selections = self.selections.all::<usize>(cx);
6021 let mut ranges = Vec::new();
6022 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6023
6024 for selection in &selections {
6025 let range = if selection.id == newest_anchor.id {
6026 replace_range_multibuffer.clone()
6027 } else {
6028 let mut range = selection.range();
6029
6030 // if prefix is present, don't duplicate it
6031 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
6032 range.start = range.start.saturating_sub(lookbehind);
6033
6034 // if suffix is also present, mimic the newest cursor and replace it
6035 if selection.id != newest_anchor.id
6036 && snapshot.contains_str_at(range.end, suffix)
6037 {
6038 range.end += lookahead;
6039 }
6040 }
6041 range
6042 };
6043
6044 ranges.push(range.clone());
6045
6046 if !self.linked_edit_ranges.is_empty() {
6047 let start_anchor = snapshot.anchor_before(range.start);
6048 let end_anchor = snapshot.anchor_after(range.end);
6049 if let Some(ranges) = self
6050 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6051 {
6052 for (buffer, edits) in ranges {
6053 linked_edits
6054 .entry(buffer.clone())
6055 .or_default()
6056 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6057 }
6058 }
6059 }
6060 }
6061
6062 let common_prefix_len = old_text
6063 .chars()
6064 .zip(new_text.chars())
6065 .take_while(|(a, b)| a == b)
6066 .map(|(a, _)| a.len_utf8())
6067 .sum::<usize>();
6068
6069 cx.emit(EditorEvent::InputHandled {
6070 utf16_range_to_replace: None,
6071 text: new_text[common_prefix_len..].into(),
6072 });
6073
6074 self.transact(window, cx, |editor, window, cx| {
6075 if let Some(mut snippet) = snippet {
6076 snippet.text = new_text.to_string();
6077 editor
6078 .insert_snippet(&ranges, snippet, window, cx)
6079 .log_err();
6080 } else {
6081 editor.buffer.update(cx, |multi_buffer, cx| {
6082 let auto_indent = match completion.insert_text_mode {
6083 Some(InsertTextMode::AS_IS) => None,
6084 _ => editor.autoindent_mode.clone(),
6085 };
6086 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6087 multi_buffer.edit(edits, auto_indent, cx);
6088 });
6089 }
6090 for (buffer, edits) in linked_edits {
6091 buffer.update(cx, |buffer, cx| {
6092 let snapshot = buffer.snapshot();
6093 let edits = edits
6094 .into_iter()
6095 .map(|(range, text)| {
6096 use text::ToPoint as TP;
6097 let end_point = TP::to_point(&range.end, &snapshot);
6098 let start_point = TP::to_point(&range.start, &snapshot);
6099 (start_point..end_point, text)
6100 })
6101 .sorted_by_key(|(range, _)| range.start);
6102 buffer.edit(edits, None, cx);
6103 })
6104 }
6105
6106 editor.refresh_edit_prediction(true, false, window, cx);
6107 });
6108 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6109
6110 let show_new_completions_on_confirm = completion
6111 .confirm
6112 .as_ref()
6113 .is_some_and(|confirm| confirm(intent, window, cx));
6114 if show_new_completions_on_confirm {
6115 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6116 }
6117
6118 let provider = self.completion_provider.as_ref()?;
6119 drop(completion);
6120 let apply_edits = provider.apply_additional_edits_for_completion(
6121 buffer_handle,
6122 completions_menu.completions.clone(),
6123 candidate_id,
6124 true,
6125 cx,
6126 );
6127
6128 let editor_settings = EditorSettings::get_global(cx);
6129 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6130 // After the code completion is finished, users often want to know what signatures are needed.
6131 // so we should automatically call signature_help
6132 self.show_signature_help(&ShowSignatureHelp, window, cx);
6133 }
6134
6135 Some(cx.foreground_executor().spawn(async move {
6136 apply_edits.await?;
6137 Ok(())
6138 }))
6139 }
6140
6141 pub fn toggle_code_actions(
6142 &mut self,
6143 action: &ToggleCodeActions,
6144 window: &mut Window,
6145 cx: &mut Context<Self>,
6146 ) {
6147 let quick_launch = action.quick_launch;
6148 let mut context_menu = self.context_menu.borrow_mut();
6149 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6150 if code_actions.deployed_from == action.deployed_from {
6151 // Toggle if we're selecting the same one
6152 *context_menu = None;
6153 cx.notify();
6154 return;
6155 } else {
6156 // Otherwise, clear it and start a new one
6157 *context_menu = None;
6158 cx.notify();
6159 }
6160 }
6161 drop(context_menu);
6162 let snapshot = self.snapshot(window, cx);
6163 let deployed_from = action.deployed_from.clone();
6164 let action = action.clone();
6165 self.completion_tasks.clear();
6166 self.discard_edit_prediction(false, cx);
6167
6168 let multibuffer_point = match &action.deployed_from {
6169 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6170 DisplayPoint::new(*row, 0).to_point(&snapshot)
6171 }
6172 _ => self.selections.newest::<Point>(cx).head(),
6173 };
6174 let Some((buffer, buffer_row)) = snapshot
6175 .buffer_snapshot()
6176 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6177 .and_then(|(buffer_snapshot, range)| {
6178 self.buffer()
6179 .read(cx)
6180 .buffer(buffer_snapshot.remote_id())
6181 .map(|buffer| (buffer, range.start.row))
6182 })
6183 else {
6184 return;
6185 };
6186 let buffer_id = buffer.read(cx).remote_id();
6187 let tasks = self
6188 .tasks
6189 .get(&(buffer_id, buffer_row))
6190 .map(|t| Arc::new(t.to_owned()));
6191
6192 if !self.focus_handle.is_focused(window) {
6193 return;
6194 }
6195 let project = self.project.clone();
6196
6197 let code_actions_task = match deployed_from {
6198 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6199 _ => self.code_actions(buffer_row, window, cx),
6200 };
6201
6202 let runnable_task = match deployed_from {
6203 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6204 _ => {
6205 let mut task_context_task = Task::ready(None);
6206 if let Some(tasks) = &tasks
6207 && let Some(project) = project
6208 {
6209 task_context_task =
6210 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6211 }
6212
6213 cx.spawn_in(window, {
6214 let buffer = buffer.clone();
6215 async move |editor, cx| {
6216 let task_context = task_context_task.await;
6217
6218 let resolved_tasks =
6219 tasks
6220 .zip(task_context.clone())
6221 .map(|(tasks, task_context)| ResolvedTasks {
6222 templates: tasks.resolve(&task_context).collect(),
6223 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6224 multibuffer_point.row,
6225 tasks.column,
6226 )),
6227 });
6228 let debug_scenarios = editor
6229 .update(cx, |editor, cx| {
6230 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6231 })?
6232 .await;
6233 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6234 }
6235 })
6236 }
6237 };
6238
6239 cx.spawn_in(window, async move |editor, cx| {
6240 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6241 let code_actions = code_actions_task.await;
6242 let spawn_straight_away = quick_launch
6243 && resolved_tasks
6244 .as_ref()
6245 .is_some_and(|tasks| tasks.templates.len() == 1)
6246 && code_actions
6247 .as_ref()
6248 .is_none_or(|actions| actions.is_empty())
6249 && debug_scenarios.is_empty();
6250
6251 editor.update_in(cx, |editor, window, cx| {
6252 crate::hover_popover::hide_hover(editor, cx);
6253 let actions = CodeActionContents::new(
6254 resolved_tasks,
6255 code_actions,
6256 debug_scenarios,
6257 task_context.unwrap_or_default(),
6258 );
6259
6260 // Don't show the menu if there are no actions available
6261 if actions.is_empty() {
6262 cx.notify();
6263 return Task::ready(Ok(()));
6264 }
6265
6266 *editor.context_menu.borrow_mut() =
6267 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6268 buffer,
6269 actions,
6270 selected_item: Default::default(),
6271 scroll_handle: UniformListScrollHandle::default(),
6272 deployed_from,
6273 }));
6274 cx.notify();
6275 if spawn_straight_away
6276 && let Some(task) = editor.confirm_code_action(
6277 &ConfirmCodeAction { item_ix: Some(0) },
6278 window,
6279 cx,
6280 )
6281 {
6282 return task;
6283 }
6284
6285 Task::ready(Ok(()))
6286 })
6287 })
6288 .detach_and_log_err(cx);
6289 }
6290
6291 fn debug_scenarios(
6292 &mut self,
6293 resolved_tasks: &Option<ResolvedTasks>,
6294 buffer: &Entity<Buffer>,
6295 cx: &mut App,
6296 ) -> Task<Vec<task::DebugScenario>> {
6297 maybe!({
6298 let project = self.project()?;
6299 let dap_store = project.read(cx).dap_store();
6300 let mut scenarios = vec![];
6301 let resolved_tasks = resolved_tasks.as_ref()?;
6302 let buffer = buffer.read(cx);
6303 let language = buffer.language()?;
6304 let file = buffer.file();
6305 let debug_adapter = language_settings(language.name().into(), file, cx)
6306 .debuggers
6307 .first()
6308 .map(SharedString::from)
6309 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6310
6311 dap_store.update(cx, |dap_store, cx| {
6312 for (_, task) in &resolved_tasks.templates {
6313 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6314 task.original_task().clone(),
6315 debug_adapter.clone().into(),
6316 task.display_label().to_owned().into(),
6317 cx,
6318 );
6319 scenarios.push(maybe_scenario);
6320 }
6321 });
6322 Some(cx.background_spawn(async move {
6323 futures::future::join_all(scenarios)
6324 .await
6325 .into_iter()
6326 .flatten()
6327 .collect::<Vec<_>>()
6328 }))
6329 })
6330 .unwrap_or_else(|| Task::ready(vec![]))
6331 }
6332
6333 fn code_actions(
6334 &mut self,
6335 buffer_row: u32,
6336 window: &mut Window,
6337 cx: &mut Context<Self>,
6338 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6339 let mut task = self.code_actions_task.take();
6340 cx.spawn_in(window, async move |editor, cx| {
6341 while let Some(prev_task) = task {
6342 prev_task.await.log_err();
6343 task = editor
6344 .update(cx, |this, _| this.code_actions_task.take())
6345 .ok()?;
6346 }
6347
6348 editor
6349 .update(cx, |editor, cx| {
6350 editor
6351 .available_code_actions
6352 .clone()
6353 .and_then(|(location, code_actions)| {
6354 let snapshot = location.buffer.read(cx).snapshot();
6355 let point_range = location.range.to_point(&snapshot);
6356 let point_range = point_range.start.row..=point_range.end.row;
6357 if point_range.contains(&buffer_row) {
6358 Some(code_actions)
6359 } else {
6360 None
6361 }
6362 })
6363 })
6364 .ok()
6365 .flatten()
6366 })
6367 }
6368
6369 pub fn confirm_code_action(
6370 &mut self,
6371 action: &ConfirmCodeAction,
6372 window: &mut Window,
6373 cx: &mut Context<Self>,
6374 ) -> Option<Task<Result<()>>> {
6375 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6376
6377 let actions_menu =
6378 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6379 menu
6380 } else {
6381 return None;
6382 };
6383
6384 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6385 let action = actions_menu.actions.get(action_ix)?;
6386 let title = action.label();
6387 let buffer = actions_menu.buffer;
6388 let workspace = self.workspace()?;
6389
6390 match action {
6391 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6392 workspace.update(cx, |workspace, cx| {
6393 workspace.schedule_resolved_task(
6394 task_source_kind,
6395 resolved_task,
6396 false,
6397 window,
6398 cx,
6399 );
6400
6401 Some(Task::ready(Ok(())))
6402 })
6403 }
6404 CodeActionsItem::CodeAction {
6405 excerpt_id,
6406 action,
6407 provider,
6408 } => {
6409 let apply_code_action =
6410 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6411 let workspace = workspace.downgrade();
6412 Some(cx.spawn_in(window, async move |editor, cx| {
6413 let project_transaction = apply_code_action.await?;
6414 Self::open_project_transaction(
6415 &editor,
6416 workspace,
6417 project_transaction,
6418 title,
6419 cx,
6420 )
6421 .await
6422 }))
6423 }
6424 CodeActionsItem::DebugScenario(scenario) => {
6425 let context = actions_menu.actions.context;
6426
6427 workspace.update(cx, |workspace, cx| {
6428 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6429 workspace.start_debug_session(
6430 scenario,
6431 context,
6432 Some(buffer),
6433 None,
6434 window,
6435 cx,
6436 );
6437 });
6438 Some(Task::ready(Ok(())))
6439 }
6440 }
6441 }
6442
6443 pub async fn open_project_transaction(
6444 editor: &WeakEntity<Editor>,
6445 workspace: WeakEntity<Workspace>,
6446 transaction: ProjectTransaction,
6447 title: String,
6448 cx: &mut AsyncWindowContext,
6449 ) -> Result<()> {
6450 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6451 cx.update(|_, cx| {
6452 entries.sort_unstable_by_key(|(buffer, _)| {
6453 buffer.read(cx).file().map(|f| f.path().clone())
6454 });
6455 })?;
6456 if entries.is_empty() {
6457 return Ok(());
6458 }
6459
6460 // If the project transaction's edits are all contained within this editor, then
6461 // avoid opening a new editor to display them.
6462
6463 if let [(buffer, transaction)] = &*entries {
6464 let excerpt = editor.update(cx, |editor, cx| {
6465 editor
6466 .buffer()
6467 .read(cx)
6468 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6469 })?;
6470 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6471 && excerpted_buffer == *buffer
6472 {
6473 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6474 let excerpt_range = excerpt_range.to_offset(buffer);
6475 buffer
6476 .edited_ranges_for_transaction::<usize>(transaction)
6477 .all(|range| {
6478 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6479 })
6480 })?;
6481
6482 if all_edits_within_excerpt {
6483 return Ok(());
6484 }
6485 }
6486 }
6487
6488 let mut ranges_to_highlight = Vec::new();
6489 let excerpt_buffer = cx.new(|cx| {
6490 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6491 for (buffer_handle, transaction) in &entries {
6492 let edited_ranges = buffer_handle
6493 .read(cx)
6494 .edited_ranges_for_transaction::<Point>(transaction)
6495 .collect::<Vec<_>>();
6496 let (ranges, _) = multibuffer.set_excerpts_for_path(
6497 PathKey::for_buffer(buffer_handle, cx),
6498 buffer_handle.clone(),
6499 edited_ranges,
6500 multibuffer_context_lines(cx),
6501 cx,
6502 );
6503
6504 ranges_to_highlight.extend(ranges);
6505 }
6506 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6507 multibuffer
6508 })?;
6509
6510 workspace.update_in(cx, |workspace, window, cx| {
6511 let project = workspace.project().clone();
6512 let editor =
6513 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6514 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6515 editor.update(cx, |editor, cx| {
6516 editor.highlight_background::<Self>(
6517 &ranges_to_highlight,
6518 |theme| theme.colors().editor_highlighted_line_background,
6519 cx,
6520 );
6521 });
6522 })?;
6523
6524 Ok(())
6525 }
6526
6527 pub fn clear_code_action_providers(&mut self) {
6528 self.code_action_providers.clear();
6529 self.available_code_actions.take();
6530 }
6531
6532 pub fn add_code_action_provider(
6533 &mut self,
6534 provider: Rc<dyn CodeActionProvider>,
6535 window: &mut Window,
6536 cx: &mut Context<Self>,
6537 ) {
6538 if self
6539 .code_action_providers
6540 .iter()
6541 .any(|existing_provider| existing_provider.id() == provider.id())
6542 {
6543 return;
6544 }
6545
6546 self.code_action_providers.push(provider);
6547 self.refresh_code_actions(window, cx);
6548 }
6549
6550 pub fn remove_code_action_provider(
6551 &mut self,
6552 id: Arc<str>,
6553 window: &mut Window,
6554 cx: &mut Context<Self>,
6555 ) {
6556 self.code_action_providers
6557 .retain(|provider| provider.id() != id);
6558 self.refresh_code_actions(window, cx);
6559 }
6560
6561 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6562 !self.code_action_providers.is_empty()
6563 && EditorSettings::get_global(cx).toolbar.code_actions
6564 }
6565
6566 pub fn has_available_code_actions(&self) -> bool {
6567 self.available_code_actions
6568 .as_ref()
6569 .is_some_and(|(_, actions)| !actions.is_empty())
6570 }
6571
6572 fn render_inline_code_actions(
6573 &self,
6574 icon_size: ui::IconSize,
6575 display_row: DisplayRow,
6576 is_active: bool,
6577 cx: &mut Context<Self>,
6578 ) -> AnyElement {
6579 let show_tooltip = !self.context_menu_visible();
6580 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6581 .icon_size(icon_size)
6582 .shape(ui::IconButtonShape::Square)
6583 .icon_color(ui::Color::Hidden)
6584 .toggle_state(is_active)
6585 .when(show_tooltip, |this| {
6586 this.tooltip({
6587 let focus_handle = self.focus_handle.clone();
6588 move |window, cx| {
6589 Tooltip::for_action_in(
6590 "Toggle Code Actions",
6591 &ToggleCodeActions {
6592 deployed_from: None,
6593 quick_launch: false,
6594 },
6595 &focus_handle,
6596 window,
6597 cx,
6598 )
6599 }
6600 })
6601 })
6602 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6603 window.focus(&editor.focus_handle(cx));
6604 editor.toggle_code_actions(
6605 &crate::actions::ToggleCodeActions {
6606 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6607 display_row,
6608 )),
6609 quick_launch: false,
6610 },
6611 window,
6612 cx,
6613 );
6614 }))
6615 .into_any_element()
6616 }
6617
6618 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6619 &self.context_menu
6620 }
6621
6622 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6623 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6624 cx.background_executor()
6625 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6626 .await;
6627
6628 let (start_buffer, start, _, end, newest_selection) = this
6629 .update(cx, |this, cx| {
6630 let newest_selection = this.selections.newest_anchor().clone();
6631 if newest_selection.head().diff_base_anchor.is_some() {
6632 return None;
6633 }
6634 let newest_selection_adjusted = this.selections.newest_adjusted(cx);
6635 let buffer = this.buffer.read(cx);
6636
6637 let (start_buffer, start) =
6638 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6639 let (end_buffer, end) =
6640 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6641
6642 Some((start_buffer, start, end_buffer, end, newest_selection))
6643 })?
6644 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6645 .context(
6646 "Expected selection to lie in a single buffer when refreshing code actions",
6647 )?;
6648 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6649 let providers = this.code_action_providers.clone();
6650 let tasks = this
6651 .code_action_providers
6652 .iter()
6653 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6654 .collect::<Vec<_>>();
6655 (providers, tasks)
6656 })?;
6657
6658 let mut actions = Vec::new();
6659 for (provider, provider_actions) in
6660 providers.into_iter().zip(future::join_all(tasks).await)
6661 {
6662 if let Some(provider_actions) = provider_actions.log_err() {
6663 actions.extend(provider_actions.into_iter().map(|action| {
6664 AvailableCodeAction {
6665 excerpt_id: newest_selection.start.excerpt_id,
6666 action,
6667 provider: provider.clone(),
6668 }
6669 }));
6670 }
6671 }
6672
6673 this.update(cx, |this, cx| {
6674 this.available_code_actions = if actions.is_empty() {
6675 None
6676 } else {
6677 Some((
6678 Location {
6679 buffer: start_buffer,
6680 range: start..end,
6681 },
6682 actions.into(),
6683 ))
6684 };
6685 cx.notify();
6686 })
6687 }));
6688 }
6689
6690 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6691 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6692 self.show_git_blame_inline = false;
6693
6694 self.show_git_blame_inline_delay_task =
6695 Some(cx.spawn_in(window, async move |this, cx| {
6696 cx.background_executor().timer(delay).await;
6697
6698 this.update(cx, |this, cx| {
6699 this.show_git_blame_inline = true;
6700 cx.notify();
6701 })
6702 .log_err();
6703 }));
6704 }
6705 }
6706
6707 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6708 let snapshot = self.snapshot(window, cx);
6709 let cursor = self.selections.newest::<Point>(cx).head();
6710 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6711 else {
6712 return;
6713 };
6714
6715 let Some(blame) = self.blame.as_ref() else {
6716 return;
6717 };
6718
6719 let row_info = RowInfo {
6720 buffer_id: Some(buffer.remote_id()),
6721 buffer_row: Some(point.row),
6722 ..Default::default()
6723 };
6724 let Some((buffer, blame_entry)) = blame
6725 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6726 .flatten()
6727 else {
6728 return;
6729 };
6730
6731 let anchor = self.selections.newest_anchor().head();
6732 let position = self.to_pixel_point(anchor, &snapshot, window);
6733 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6734 self.show_blame_popover(
6735 buffer,
6736 &blame_entry,
6737 position + last_bounds.origin,
6738 true,
6739 cx,
6740 );
6741 };
6742 }
6743
6744 fn show_blame_popover(
6745 &mut self,
6746 buffer: BufferId,
6747 blame_entry: &BlameEntry,
6748 position: gpui::Point<Pixels>,
6749 ignore_timeout: bool,
6750 cx: &mut Context<Self>,
6751 ) {
6752 if let Some(state) = &mut self.inline_blame_popover {
6753 state.hide_task.take();
6754 } else {
6755 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6756 let blame_entry = blame_entry.clone();
6757 let show_task = cx.spawn(async move |editor, cx| {
6758 if !ignore_timeout {
6759 cx.background_executor()
6760 .timer(std::time::Duration::from_millis(blame_popover_delay))
6761 .await;
6762 }
6763 editor
6764 .update(cx, |editor, cx| {
6765 editor.inline_blame_popover_show_task.take();
6766 let Some(blame) = editor.blame.as_ref() else {
6767 return;
6768 };
6769 let blame = blame.read(cx);
6770 let details = blame.details_for_entry(buffer, &blame_entry);
6771 let markdown = cx.new(|cx| {
6772 Markdown::new(
6773 details
6774 .as_ref()
6775 .map(|message| message.message.clone())
6776 .unwrap_or_default(),
6777 None,
6778 None,
6779 cx,
6780 )
6781 });
6782 editor.inline_blame_popover = Some(InlineBlamePopover {
6783 position,
6784 hide_task: None,
6785 popover_bounds: None,
6786 popover_state: InlineBlamePopoverState {
6787 scroll_handle: ScrollHandle::new(),
6788 commit_message: details,
6789 markdown,
6790 },
6791 keyboard_grace: ignore_timeout,
6792 });
6793 cx.notify();
6794 })
6795 .ok();
6796 });
6797 self.inline_blame_popover_show_task = Some(show_task);
6798 }
6799 }
6800
6801 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6802 self.inline_blame_popover_show_task.take();
6803 if let Some(state) = &mut self.inline_blame_popover {
6804 let hide_task = cx.spawn(async move |editor, cx| {
6805 cx.background_executor()
6806 .timer(std::time::Duration::from_millis(100))
6807 .await;
6808 editor
6809 .update(cx, |editor, cx| {
6810 editor.inline_blame_popover.take();
6811 cx.notify();
6812 })
6813 .ok();
6814 });
6815 state.hide_task = Some(hide_task);
6816 }
6817 }
6818
6819 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6820 if self.pending_rename.is_some() {
6821 return None;
6822 }
6823
6824 let provider = self.semantics_provider.clone()?;
6825 let buffer = self.buffer.read(cx);
6826 let newest_selection = self.selections.newest_anchor().clone();
6827 let cursor_position = newest_selection.head();
6828 let (cursor_buffer, cursor_buffer_position) =
6829 buffer.text_anchor_for_position(cursor_position, cx)?;
6830 let (tail_buffer, tail_buffer_position) =
6831 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6832 if cursor_buffer != tail_buffer {
6833 return None;
6834 }
6835
6836 let snapshot = cursor_buffer.read(cx).snapshot();
6837 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6838 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6839 if start_word_range != end_word_range {
6840 self.document_highlights_task.take();
6841 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6842 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6843 return None;
6844 }
6845
6846 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6847 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6848 cx.background_executor()
6849 .timer(Duration::from_millis(debounce))
6850 .await;
6851
6852 let highlights = if let Some(highlights) = cx
6853 .update(|cx| {
6854 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6855 })
6856 .ok()
6857 .flatten()
6858 {
6859 highlights.await.log_err()
6860 } else {
6861 None
6862 };
6863
6864 if let Some(highlights) = highlights {
6865 this.update(cx, |this, cx| {
6866 if this.pending_rename.is_some() {
6867 return;
6868 }
6869
6870 let buffer = this.buffer.read(cx);
6871 if buffer
6872 .text_anchor_for_position(cursor_position, cx)
6873 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6874 {
6875 return;
6876 }
6877
6878 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6879 let mut write_ranges = Vec::new();
6880 let mut read_ranges = Vec::new();
6881 for highlight in highlights {
6882 let buffer_id = cursor_buffer.read(cx).remote_id();
6883 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6884 {
6885 let start = highlight
6886 .range
6887 .start
6888 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6889 let end = highlight
6890 .range
6891 .end
6892 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6893 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6894 continue;
6895 }
6896
6897 let range = Anchor::range_in_buffer(excerpt_id, buffer_id, start..end);
6898 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6899 write_ranges.push(range);
6900 } else {
6901 read_ranges.push(range);
6902 }
6903 }
6904 }
6905
6906 this.highlight_background::<DocumentHighlightRead>(
6907 &read_ranges,
6908 |theme| theme.colors().editor_document_highlight_read_background,
6909 cx,
6910 );
6911 this.highlight_background::<DocumentHighlightWrite>(
6912 &write_ranges,
6913 |theme| theme.colors().editor_document_highlight_write_background,
6914 cx,
6915 );
6916 cx.notify();
6917 })
6918 .log_err();
6919 }
6920 }));
6921 None
6922 }
6923
6924 fn prepare_highlight_query_from_selection(
6925 &mut self,
6926 cx: &mut Context<Editor>,
6927 ) -> Option<(String, Range<Anchor>)> {
6928 if matches!(self.mode, EditorMode::SingleLine) {
6929 return None;
6930 }
6931 if !EditorSettings::get_global(cx).selection_highlight {
6932 return None;
6933 }
6934 if self.selections.count() != 1 || self.selections.line_mode() {
6935 return None;
6936 }
6937 let selection = self.selections.newest_anchor();
6938 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6939 let selection_point_range = selection.start.to_point(&multi_buffer_snapshot)
6940 ..selection.end.to_point(&multi_buffer_snapshot);
6941 // If the selection spans multiple rows OR it is empty
6942 if selection_point_range.start.row != selection_point_range.end.row
6943 || selection_point_range.start.column == selection_point_range.end.column
6944 {
6945 return None;
6946 }
6947
6948 let query = multi_buffer_snapshot
6949 .text_for_range(selection.range())
6950 .collect::<String>();
6951 if query.trim().is_empty() {
6952 return None;
6953 }
6954 Some((query, selection.range()))
6955 }
6956
6957 fn update_selection_occurrence_highlights(
6958 &mut self,
6959 query_text: String,
6960 query_range: Range<Anchor>,
6961 multi_buffer_range_to_query: Range<Point>,
6962 use_debounce: bool,
6963 window: &mut Window,
6964 cx: &mut Context<Editor>,
6965 ) -> Task<()> {
6966 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6967 cx.spawn_in(window, async move |editor, cx| {
6968 if use_debounce {
6969 cx.background_executor()
6970 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6971 .await;
6972 }
6973 let match_task = cx.background_spawn(async move {
6974 let buffer_ranges = multi_buffer_snapshot
6975 .range_to_buffer_ranges(multi_buffer_range_to_query)
6976 .into_iter()
6977 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6978 let mut match_ranges = Vec::new();
6979 let Ok(regex) = project::search::SearchQuery::text(
6980 query_text.clone(),
6981 false,
6982 false,
6983 false,
6984 Default::default(),
6985 Default::default(),
6986 false,
6987 None,
6988 ) else {
6989 return Vec::default();
6990 };
6991 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
6992 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6993 match_ranges.extend(
6994 regex
6995 .search(buffer_snapshot, Some(search_range.clone()))
6996 .await
6997 .into_iter()
6998 .filter_map(|match_range| {
6999 let match_start = buffer_snapshot
7000 .anchor_after(search_range.start + match_range.start);
7001 let match_end = buffer_snapshot
7002 .anchor_before(search_range.start + match_range.end);
7003 let match_anchor_range = Anchor::range_in_buffer(
7004 excerpt_id,
7005 buffer_snapshot.remote_id(),
7006 match_start..match_end,
7007 );
7008 (match_anchor_range != query_range).then_some(match_anchor_range)
7009 }),
7010 );
7011 }
7012 match_ranges
7013 });
7014 let match_ranges = match_task.await;
7015 editor
7016 .update_in(cx, |editor, _, cx| {
7017 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7018 if !match_ranges.is_empty() {
7019 editor.highlight_background::<SelectedTextHighlight>(
7020 &match_ranges,
7021 |theme| theme.colors().editor_document_highlight_bracket_background,
7022 cx,
7023 )
7024 }
7025 })
7026 .log_err();
7027 })
7028 }
7029
7030 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7031 struct NewlineFold;
7032 let type_id = std::any::TypeId::of::<NewlineFold>();
7033 if !self.mode.is_single_line() {
7034 return;
7035 }
7036 let snapshot = self.snapshot(window, cx);
7037 if snapshot.buffer_snapshot().max_point().row == 0 {
7038 return;
7039 }
7040 let task = cx.background_spawn(async move {
7041 let new_newlines = snapshot
7042 .buffer_chars_at(0)
7043 .filter_map(|(c, i)| {
7044 if c == '\n' {
7045 Some(
7046 snapshot.buffer_snapshot().anchor_after(i)
7047 ..snapshot.buffer_snapshot().anchor_before(i + 1),
7048 )
7049 } else {
7050 None
7051 }
7052 })
7053 .collect::<Vec<_>>();
7054 let existing_newlines = snapshot
7055 .folds_in_range(0..snapshot.buffer_snapshot().len())
7056 .filter_map(|fold| {
7057 if fold.placeholder.type_tag == Some(type_id) {
7058 Some(fold.range.start..fold.range.end)
7059 } else {
7060 None
7061 }
7062 })
7063 .collect::<Vec<_>>();
7064
7065 (new_newlines, existing_newlines)
7066 });
7067 self.folding_newlines = cx.spawn(async move |this, cx| {
7068 let (new_newlines, existing_newlines) = task.await;
7069 if new_newlines == existing_newlines {
7070 return;
7071 }
7072 let placeholder = FoldPlaceholder {
7073 render: Arc::new(move |_, _, cx| {
7074 div()
7075 .bg(cx.theme().status().hint_background)
7076 .border_b_1()
7077 .size_full()
7078 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7079 .border_color(cx.theme().status().hint)
7080 .child("\\n")
7081 .into_any()
7082 }),
7083 constrain_width: false,
7084 merge_adjacent: false,
7085 type_tag: Some(type_id),
7086 };
7087 let creases = new_newlines
7088 .into_iter()
7089 .map(|range| Crease::simple(range, placeholder.clone()))
7090 .collect();
7091 this.update(cx, |this, cx| {
7092 this.display_map.update(cx, |display_map, cx| {
7093 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7094 display_map.fold(creases, cx);
7095 });
7096 })
7097 .ok();
7098 });
7099 }
7100
7101 fn refresh_selected_text_highlights(
7102 &mut self,
7103 on_buffer_edit: bool,
7104 window: &mut Window,
7105 cx: &mut Context<Editor>,
7106 ) {
7107 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
7108 else {
7109 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7110 self.quick_selection_highlight_task.take();
7111 self.debounced_selection_highlight_task.take();
7112 return;
7113 };
7114 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7115 if on_buffer_edit
7116 || self
7117 .quick_selection_highlight_task
7118 .as_ref()
7119 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7120 {
7121 let multi_buffer_visible_start = self
7122 .scroll_manager
7123 .anchor()
7124 .anchor
7125 .to_point(&multi_buffer_snapshot);
7126 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7127 multi_buffer_visible_start
7128 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7129 Bias::Left,
7130 );
7131 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7132 self.quick_selection_highlight_task = Some((
7133 query_range.clone(),
7134 self.update_selection_occurrence_highlights(
7135 query_text.clone(),
7136 query_range.clone(),
7137 multi_buffer_visible_range,
7138 false,
7139 window,
7140 cx,
7141 ),
7142 ));
7143 }
7144 if on_buffer_edit
7145 || self
7146 .debounced_selection_highlight_task
7147 .as_ref()
7148 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7149 {
7150 let multi_buffer_start = multi_buffer_snapshot
7151 .anchor_before(0)
7152 .to_point(&multi_buffer_snapshot);
7153 let multi_buffer_end = multi_buffer_snapshot
7154 .anchor_after(multi_buffer_snapshot.len())
7155 .to_point(&multi_buffer_snapshot);
7156 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7157 self.debounced_selection_highlight_task = Some((
7158 query_range.clone(),
7159 self.update_selection_occurrence_highlights(
7160 query_text,
7161 query_range,
7162 multi_buffer_full_range,
7163 true,
7164 window,
7165 cx,
7166 ),
7167 ));
7168 }
7169 }
7170
7171 pub fn refresh_edit_prediction(
7172 &mut self,
7173 debounce: bool,
7174 user_requested: bool,
7175 window: &mut Window,
7176 cx: &mut Context<Self>,
7177 ) -> Option<()> {
7178 if DisableAiSettings::get_global(cx).disable_ai {
7179 return None;
7180 }
7181
7182 let provider = self.edit_prediction_provider()?;
7183 let cursor = self.selections.newest_anchor().head();
7184 let (buffer, cursor_buffer_position) =
7185 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7186
7187 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7188 self.discard_edit_prediction(false, cx);
7189 return None;
7190 }
7191
7192 self.update_visible_edit_prediction(window, cx);
7193
7194 if !user_requested
7195 && (!self.should_show_edit_predictions()
7196 || !self.is_focused(window)
7197 || buffer.read(cx).is_empty())
7198 {
7199 self.discard_edit_prediction(false, cx);
7200 return None;
7201 }
7202
7203 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7204 Some(())
7205 }
7206
7207 fn show_edit_predictions_in_menu(&self) -> bool {
7208 match self.edit_prediction_settings {
7209 EditPredictionSettings::Disabled => false,
7210 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7211 }
7212 }
7213
7214 pub fn edit_predictions_enabled(&self) -> bool {
7215 match self.edit_prediction_settings {
7216 EditPredictionSettings::Disabled => false,
7217 EditPredictionSettings::Enabled { .. } => true,
7218 }
7219 }
7220
7221 fn edit_prediction_requires_modifier(&self) -> bool {
7222 match self.edit_prediction_settings {
7223 EditPredictionSettings::Disabled => false,
7224 EditPredictionSettings::Enabled {
7225 preview_requires_modifier,
7226 ..
7227 } => preview_requires_modifier,
7228 }
7229 }
7230
7231 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7232 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7233 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7234 self.discard_edit_prediction(false, cx);
7235 } else {
7236 let selection = self.selections.newest_anchor();
7237 let cursor = selection.head();
7238
7239 if let Some((buffer, cursor_buffer_position)) =
7240 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7241 {
7242 self.edit_prediction_settings =
7243 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7244 }
7245 }
7246 }
7247
7248 fn edit_prediction_settings_at_position(
7249 &self,
7250 buffer: &Entity<Buffer>,
7251 buffer_position: language::Anchor,
7252 cx: &App,
7253 ) -> EditPredictionSettings {
7254 if !self.mode.is_full()
7255 || !self.show_edit_predictions_override.unwrap_or(true)
7256 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7257 {
7258 return EditPredictionSettings::Disabled;
7259 }
7260
7261 let buffer = buffer.read(cx);
7262
7263 let file = buffer.file();
7264
7265 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7266 return EditPredictionSettings::Disabled;
7267 };
7268
7269 let by_provider = matches!(
7270 self.menu_edit_predictions_policy,
7271 MenuEditPredictionsPolicy::ByProvider
7272 );
7273
7274 let show_in_menu = by_provider
7275 && self
7276 .edit_prediction_provider
7277 .as_ref()
7278 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7279
7280 let preview_requires_modifier =
7281 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7282
7283 EditPredictionSettings::Enabled {
7284 show_in_menu,
7285 preview_requires_modifier,
7286 }
7287 }
7288
7289 fn should_show_edit_predictions(&self) -> bool {
7290 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7291 }
7292
7293 pub fn edit_prediction_preview_is_active(&self) -> bool {
7294 matches!(
7295 self.edit_prediction_preview,
7296 EditPredictionPreview::Active { .. }
7297 )
7298 }
7299
7300 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7301 let cursor = self.selections.newest_anchor().head();
7302 if let Some((buffer, cursor_position)) =
7303 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7304 {
7305 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7306 } else {
7307 false
7308 }
7309 }
7310
7311 pub fn supports_minimap(&self, cx: &App) -> bool {
7312 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7313 }
7314
7315 fn edit_predictions_enabled_in_buffer(
7316 &self,
7317 buffer: &Entity<Buffer>,
7318 buffer_position: language::Anchor,
7319 cx: &App,
7320 ) -> bool {
7321 maybe!({
7322 if self.read_only(cx) {
7323 return Some(false);
7324 }
7325 let provider = self.edit_prediction_provider()?;
7326 if !provider.is_enabled(buffer, buffer_position, cx) {
7327 return Some(false);
7328 }
7329 let buffer = buffer.read(cx);
7330 let Some(file) = buffer.file() else {
7331 return Some(true);
7332 };
7333 let settings = all_language_settings(Some(file), cx);
7334 Some(settings.edit_predictions_enabled_for_file(file, cx))
7335 })
7336 .unwrap_or(false)
7337 }
7338
7339 fn cycle_edit_prediction(
7340 &mut self,
7341 direction: Direction,
7342 window: &mut Window,
7343 cx: &mut Context<Self>,
7344 ) -> Option<()> {
7345 let provider = self.edit_prediction_provider()?;
7346 let cursor = self.selections.newest_anchor().head();
7347 let (buffer, cursor_buffer_position) =
7348 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7349 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7350 return None;
7351 }
7352
7353 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7354 self.update_visible_edit_prediction(window, cx);
7355
7356 Some(())
7357 }
7358
7359 pub fn show_edit_prediction(
7360 &mut self,
7361 _: &ShowEditPrediction,
7362 window: &mut Window,
7363 cx: &mut Context<Self>,
7364 ) {
7365 if !self.has_active_edit_prediction() {
7366 self.refresh_edit_prediction(false, true, window, cx);
7367 return;
7368 }
7369
7370 self.update_visible_edit_prediction(window, cx);
7371 }
7372
7373 pub fn display_cursor_names(
7374 &mut self,
7375 _: &DisplayCursorNames,
7376 window: &mut Window,
7377 cx: &mut Context<Self>,
7378 ) {
7379 self.show_cursor_names(window, cx);
7380 }
7381
7382 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7383 self.show_cursor_names = true;
7384 cx.notify();
7385 cx.spawn_in(window, async move |this, cx| {
7386 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7387 this.update(cx, |this, cx| {
7388 this.show_cursor_names = false;
7389 cx.notify()
7390 })
7391 .ok()
7392 })
7393 .detach();
7394 }
7395
7396 pub fn next_edit_prediction(
7397 &mut self,
7398 _: &NextEditPrediction,
7399 window: &mut Window,
7400 cx: &mut Context<Self>,
7401 ) {
7402 if self.has_active_edit_prediction() {
7403 self.cycle_edit_prediction(Direction::Next, window, cx);
7404 } else {
7405 let is_copilot_disabled = self
7406 .refresh_edit_prediction(false, true, window, cx)
7407 .is_none();
7408 if is_copilot_disabled {
7409 cx.propagate();
7410 }
7411 }
7412 }
7413
7414 pub fn previous_edit_prediction(
7415 &mut self,
7416 _: &PreviousEditPrediction,
7417 window: &mut Window,
7418 cx: &mut Context<Self>,
7419 ) {
7420 if self.has_active_edit_prediction() {
7421 self.cycle_edit_prediction(Direction::Prev, window, cx);
7422 } else {
7423 let is_copilot_disabled = self
7424 .refresh_edit_prediction(false, true, window, cx)
7425 .is_none();
7426 if is_copilot_disabled {
7427 cx.propagate();
7428 }
7429 }
7430 }
7431
7432 pub fn accept_edit_prediction(
7433 &mut self,
7434 _: &AcceptEditPrediction,
7435 window: &mut Window,
7436 cx: &mut Context<Self>,
7437 ) {
7438 if self.show_edit_predictions_in_menu() {
7439 self.hide_context_menu(window, cx);
7440 }
7441
7442 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7443 return;
7444 };
7445
7446 match &active_edit_prediction.completion {
7447 EditPrediction::MoveWithin { target, .. } => {
7448 let target = *target;
7449
7450 if let Some(position_map) = &self.last_position_map {
7451 if position_map
7452 .visible_row_range
7453 .contains(&target.to_display_point(&position_map.snapshot).row())
7454 || !self.edit_prediction_requires_modifier()
7455 {
7456 self.unfold_ranges(&[target..target], true, false, cx);
7457 // Note that this is also done in vim's handler of the Tab action.
7458 self.change_selections(
7459 SelectionEffects::scroll(Autoscroll::newest()),
7460 window,
7461 cx,
7462 |selections| {
7463 selections.select_anchor_ranges([target..target]);
7464 },
7465 );
7466 self.clear_row_highlights::<EditPredictionPreview>();
7467
7468 self.edit_prediction_preview
7469 .set_previous_scroll_position(None);
7470 } else {
7471 self.edit_prediction_preview
7472 .set_previous_scroll_position(Some(
7473 position_map.snapshot.scroll_anchor,
7474 ));
7475
7476 self.highlight_rows::<EditPredictionPreview>(
7477 target..target,
7478 cx.theme().colors().editor_highlighted_line_background,
7479 RowHighlightOptions {
7480 autoscroll: true,
7481 ..Default::default()
7482 },
7483 cx,
7484 );
7485 self.request_autoscroll(Autoscroll::fit(), cx);
7486 }
7487 }
7488 }
7489 EditPrediction::MoveOutside { snapshot, target } => {
7490 if let Some(workspace) = self.workspace() {
7491 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7492 .detach_and_log_err(cx);
7493 }
7494 }
7495 EditPrediction::Edit { edits, .. } => {
7496 self.report_edit_prediction_event(
7497 active_edit_prediction.completion_id.clone(),
7498 true,
7499 cx,
7500 );
7501
7502 if let Some(provider) = self.edit_prediction_provider() {
7503 provider.accept(cx);
7504 }
7505
7506 // Store the transaction ID and selections before applying the edit
7507 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7508
7509 let snapshot = self.buffer.read(cx).snapshot(cx);
7510 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7511
7512 self.buffer.update(cx, |buffer, cx| {
7513 buffer.edit(edits.iter().cloned(), None, cx)
7514 });
7515
7516 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7517 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7518 });
7519
7520 let selections = self.selections.disjoint_anchors_arc();
7521 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7522 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7523 if has_new_transaction {
7524 self.selection_history
7525 .insert_transaction(transaction_id_now, selections);
7526 }
7527 }
7528
7529 self.update_visible_edit_prediction(window, cx);
7530 if self.active_edit_prediction.is_none() {
7531 self.refresh_edit_prediction(true, true, window, cx);
7532 }
7533
7534 cx.notify();
7535 }
7536 }
7537
7538 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7539 }
7540
7541 pub fn accept_partial_edit_prediction(
7542 &mut self,
7543 _: &AcceptPartialEditPrediction,
7544 window: &mut Window,
7545 cx: &mut Context<Self>,
7546 ) {
7547 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7548 return;
7549 };
7550 if self.selections.count() != 1 {
7551 return;
7552 }
7553
7554 match &active_edit_prediction.completion {
7555 EditPrediction::MoveWithin { target, .. } => {
7556 let target = *target;
7557 self.change_selections(
7558 SelectionEffects::scroll(Autoscroll::newest()),
7559 window,
7560 cx,
7561 |selections| {
7562 selections.select_anchor_ranges([target..target]);
7563 },
7564 );
7565 }
7566 EditPrediction::MoveOutside { snapshot, target } => {
7567 if let Some(workspace) = self.workspace() {
7568 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7569 .detach_and_log_err(cx);
7570 }
7571 }
7572 EditPrediction::Edit { edits, .. } => {
7573 self.report_edit_prediction_event(
7574 active_edit_prediction.completion_id.clone(),
7575 true,
7576 cx,
7577 );
7578
7579 // Find an insertion that starts at the cursor position.
7580 let snapshot = self.buffer.read(cx).snapshot(cx);
7581 let cursor_offset = self.selections.newest::<usize>(cx).head();
7582 let insertion = edits.iter().find_map(|(range, text)| {
7583 let range = range.to_offset(&snapshot);
7584 if range.is_empty() && range.start == cursor_offset {
7585 Some(text)
7586 } else {
7587 None
7588 }
7589 });
7590
7591 if let Some(text) = insertion {
7592 let mut partial_completion = text
7593 .chars()
7594 .by_ref()
7595 .take_while(|c| c.is_alphabetic())
7596 .collect::<String>();
7597 if partial_completion.is_empty() {
7598 partial_completion = text
7599 .chars()
7600 .by_ref()
7601 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7602 .collect::<String>();
7603 }
7604
7605 cx.emit(EditorEvent::InputHandled {
7606 utf16_range_to_replace: None,
7607 text: partial_completion.clone().into(),
7608 });
7609
7610 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7611
7612 self.refresh_edit_prediction(true, true, window, cx);
7613 cx.notify();
7614 } else {
7615 self.accept_edit_prediction(&Default::default(), window, cx);
7616 }
7617 }
7618 }
7619 }
7620
7621 fn discard_edit_prediction(
7622 &mut self,
7623 should_report_edit_prediction_event: bool,
7624 cx: &mut Context<Self>,
7625 ) -> bool {
7626 if should_report_edit_prediction_event {
7627 let completion_id = self
7628 .active_edit_prediction
7629 .as_ref()
7630 .and_then(|active_completion| active_completion.completion_id.clone());
7631
7632 self.report_edit_prediction_event(completion_id, false, cx);
7633 }
7634
7635 if let Some(provider) = self.edit_prediction_provider() {
7636 provider.discard(cx);
7637 }
7638
7639 self.take_active_edit_prediction(cx)
7640 }
7641
7642 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7643 let Some(provider) = self.edit_prediction_provider() else {
7644 return;
7645 };
7646
7647 let Some((_, buffer, _)) = self
7648 .buffer
7649 .read(cx)
7650 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7651 else {
7652 return;
7653 };
7654
7655 let extension = buffer
7656 .read(cx)
7657 .file()
7658 .and_then(|file| Some(file.path().extension()?.to_string()));
7659
7660 let event_type = match accepted {
7661 true => "Edit Prediction Accepted",
7662 false => "Edit Prediction Discarded",
7663 };
7664 telemetry::event!(
7665 event_type,
7666 provider = provider.name(),
7667 prediction_id = id,
7668 suggestion_accepted = accepted,
7669 file_extension = extension,
7670 );
7671 }
7672
7673 fn open_editor_at_anchor(
7674 snapshot: &language::BufferSnapshot,
7675 target: language::Anchor,
7676 workspace: &Entity<Workspace>,
7677 window: &mut Window,
7678 cx: &mut App,
7679 ) -> Task<Result<()>> {
7680 workspace.update(cx, |workspace, cx| {
7681 let path = snapshot.file().map(|file| file.full_path(cx));
7682 let Some(path) =
7683 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7684 else {
7685 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7686 };
7687 let target = text::ToPoint::to_point(&target, snapshot);
7688 let item = workspace.open_path(path, None, true, window, cx);
7689 window.spawn(cx, async move |cx| {
7690 let Some(editor) = item.await?.downcast::<Editor>() else {
7691 return Ok(());
7692 };
7693 editor
7694 .update_in(cx, |editor, window, cx| {
7695 editor.go_to_singleton_buffer_point(target, window, cx);
7696 })
7697 .ok();
7698 anyhow::Ok(())
7699 })
7700 })
7701 }
7702
7703 pub fn has_active_edit_prediction(&self) -> bool {
7704 self.active_edit_prediction.is_some()
7705 }
7706
7707 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7708 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7709 return false;
7710 };
7711
7712 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7713 self.clear_highlights::<EditPredictionHighlight>(cx);
7714 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7715 true
7716 }
7717
7718 /// Returns true when we're displaying the edit prediction popover below the cursor
7719 /// like we are not previewing and the LSP autocomplete menu is visible
7720 /// or we are in `when_holding_modifier` mode.
7721 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7722 if self.edit_prediction_preview_is_active()
7723 || !self.show_edit_predictions_in_menu()
7724 || !self.edit_predictions_enabled()
7725 {
7726 return false;
7727 }
7728
7729 if self.has_visible_completions_menu() {
7730 return true;
7731 }
7732
7733 has_completion && self.edit_prediction_requires_modifier()
7734 }
7735
7736 fn handle_modifiers_changed(
7737 &mut self,
7738 modifiers: Modifiers,
7739 position_map: &PositionMap,
7740 window: &mut Window,
7741 cx: &mut Context<Self>,
7742 ) {
7743 if self.show_edit_predictions_in_menu() {
7744 self.update_edit_prediction_preview(&modifiers, window, cx);
7745 }
7746
7747 self.update_selection_mode(&modifiers, position_map, window, cx);
7748
7749 let mouse_position = window.mouse_position();
7750 if !position_map.text_hitbox.is_hovered(window) {
7751 return;
7752 }
7753
7754 self.update_hovered_link(
7755 position_map.point_for_position(mouse_position),
7756 &position_map.snapshot,
7757 modifiers,
7758 window,
7759 cx,
7760 )
7761 }
7762
7763 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7764 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7765 if invert {
7766 match multi_cursor_setting {
7767 MultiCursorModifier::Alt => modifiers.alt,
7768 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7769 }
7770 } else {
7771 match multi_cursor_setting {
7772 MultiCursorModifier::Alt => modifiers.secondary(),
7773 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7774 }
7775 }
7776 }
7777
7778 fn columnar_selection_mode(
7779 modifiers: &Modifiers,
7780 cx: &mut Context<Self>,
7781 ) -> Option<ColumnarMode> {
7782 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7783 if Self::multi_cursor_modifier(false, modifiers, cx) {
7784 Some(ColumnarMode::FromMouse)
7785 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7786 Some(ColumnarMode::FromSelection)
7787 } else {
7788 None
7789 }
7790 } else {
7791 None
7792 }
7793 }
7794
7795 fn update_selection_mode(
7796 &mut self,
7797 modifiers: &Modifiers,
7798 position_map: &PositionMap,
7799 window: &mut Window,
7800 cx: &mut Context<Self>,
7801 ) {
7802 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7803 return;
7804 };
7805 if self.selections.pending_anchor().is_none() {
7806 return;
7807 }
7808
7809 let mouse_position = window.mouse_position();
7810 let point_for_position = position_map.point_for_position(mouse_position);
7811 let position = point_for_position.previous_valid;
7812
7813 self.select(
7814 SelectPhase::BeginColumnar {
7815 position,
7816 reset: false,
7817 mode,
7818 goal_column: point_for_position.exact_unclipped.column(),
7819 },
7820 window,
7821 cx,
7822 );
7823 }
7824
7825 fn update_edit_prediction_preview(
7826 &mut self,
7827 modifiers: &Modifiers,
7828 window: &mut Window,
7829 cx: &mut Context<Self>,
7830 ) {
7831 let mut modifiers_held = false;
7832 if let Some(accept_keystroke) = self
7833 .accept_edit_prediction_keybind(false, window, cx)
7834 .keystroke()
7835 {
7836 modifiers_held = modifiers_held
7837 || (accept_keystroke.modifiers() == modifiers
7838 && accept_keystroke.modifiers().modified());
7839 };
7840 if let Some(accept_partial_keystroke) = self
7841 .accept_edit_prediction_keybind(true, window, cx)
7842 .keystroke()
7843 {
7844 modifiers_held = modifiers_held
7845 || (accept_partial_keystroke.modifiers() == modifiers
7846 && accept_partial_keystroke.modifiers().modified());
7847 }
7848
7849 if modifiers_held {
7850 if matches!(
7851 self.edit_prediction_preview,
7852 EditPredictionPreview::Inactive { .. }
7853 ) {
7854 self.edit_prediction_preview = EditPredictionPreview::Active {
7855 previous_scroll_position: None,
7856 since: Instant::now(),
7857 };
7858
7859 self.update_visible_edit_prediction(window, cx);
7860 cx.notify();
7861 }
7862 } else if let EditPredictionPreview::Active {
7863 previous_scroll_position,
7864 since,
7865 } = self.edit_prediction_preview
7866 {
7867 if let (Some(previous_scroll_position), Some(position_map)) =
7868 (previous_scroll_position, self.last_position_map.as_ref())
7869 {
7870 self.set_scroll_position(
7871 previous_scroll_position
7872 .scroll_position(&position_map.snapshot.display_snapshot),
7873 window,
7874 cx,
7875 );
7876 }
7877
7878 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7879 released_too_fast: since.elapsed() < Duration::from_millis(200),
7880 };
7881 self.clear_row_highlights::<EditPredictionPreview>();
7882 self.update_visible_edit_prediction(window, cx);
7883 cx.notify();
7884 }
7885 }
7886
7887 fn update_visible_edit_prediction(
7888 &mut self,
7889 _window: &mut Window,
7890 cx: &mut Context<Self>,
7891 ) -> Option<()> {
7892 if DisableAiSettings::get_global(cx).disable_ai {
7893 return None;
7894 }
7895
7896 if self.ime_transaction.is_some() {
7897 self.discard_edit_prediction(false, cx);
7898 return None;
7899 }
7900
7901 let selection = self.selections.newest_anchor();
7902 let cursor = selection.head();
7903 let multibuffer = self.buffer.read(cx).snapshot(cx);
7904 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7905 let excerpt_id = cursor.excerpt_id;
7906
7907 let show_in_menu = self.show_edit_predictions_in_menu();
7908 let completions_menu_has_precedence = !show_in_menu
7909 && (self.context_menu.borrow().is_some()
7910 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7911
7912 if completions_menu_has_precedence
7913 || !offset_selection.is_empty()
7914 || self
7915 .active_edit_prediction
7916 .as_ref()
7917 .is_some_and(|completion| {
7918 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7919 return false;
7920 };
7921 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7922 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7923 !invalidation_range.contains(&offset_selection.head())
7924 })
7925 {
7926 self.discard_edit_prediction(false, cx);
7927 return None;
7928 }
7929
7930 self.take_active_edit_prediction(cx);
7931 let Some(provider) = self.edit_prediction_provider() else {
7932 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7933 return None;
7934 };
7935
7936 let (buffer, cursor_buffer_position) =
7937 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7938
7939 self.edit_prediction_settings =
7940 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7941
7942 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7943
7944 if self.edit_prediction_indent_conflict {
7945 let cursor_point = cursor.to_point(&multibuffer);
7946
7947 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7948
7949 if let Some((_, indent)) = indents.iter().next()
7950 && indent.len == cursor_point.column
7951 {
7952 self.edit_prediction_indent_conflict = false;
7953 }
7954 }
7955
7956 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7957
7958 let (completion_id, edits, edit_preview) = match edit_prediction {
7959 edit_prediction::EditPrediction::Local {
7960 id,
7961 edits,
7962 edit_preview,
7963 } => (id, edits, edit_preview),
7964 edit_prediction::EditPrediction::Jump {
7965 id,
7966 snapshot,
7967 target,
7968 } => {
7969 self.stale_edit_prediction_in_menu = None;
7970 self.active_edit_prediction = Some(EditPredictionState {
7971 inlay_ids: vec![],
7972 completion: EditPrediction::MoveOutside { snapshot, target },
7973 completion_id: id,
7974 invalidation_range: None,
7975 });
7976 cx.notify();
7977 return Some(());
7978 }
7979 };
7980
7981 let edits = edits
7982 .into_iter()
7983 .flat_map(|(range, new_text)| {
7984 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7985 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7986 Some((start..end, new_text))
7987 })
7988 .collect::<Vec<_>>();
7989 if edits.is_empty() {
7990 return None;
7991 }
7992
7993 let first_edit_start = edits.first().unwrap().0.start;
7994 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7995 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7996
7997 let last_edit_end = edits.last().unwrap().0.end;
7998 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7999 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8000
8001 let cursor_row = cursor.to_point(&multibuffer).row;
8002
8003 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8004
8005 let mut inlay_ids = Vec::new();
8006 let invalidation_row_range;
8007 let move_invalidation_row_range = if cursor_row < edit_start_row {
8008 Some(cursor_row..edit_end_row)
8009 } else if cursor_row > edit_end_row {
8010 Some(edit_start_row..cursor_row)
8011 } else {
8012 None
8013 };
8014 let supports_jump = self
8015 .edit_prediction_provider
8016 .as_ref()
8017 .map(|provider| provider.provider.supports_jump_to_edit())
8018 .unwrap_or(true);
8019
8020 let is_move = supports_jump
8021 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8022 let completion = if is_move {
8023 invalidation_row_range =
8024 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8025 let target = first_edit_start;
8026 EditPrediction::MoveWithin { target, snapshot }
8027 } else {
8028 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8029 && !self.edit_predictions_hidden_for_vim_mode;
8030
8031 if show_completions_in_buffer {
8032 if edits
8033 .iter()
8034 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8035 {
8036 let mut inlays = Vec::new();
8037 for (range, new_text) in &edits {
8038 let inlay = Inlay::edit_prediction(
8039 post_inc(&mut self.next_inlay_id),
8040 range.start,
8041 new_text.as_str(),
8042 );
8043 inlay_ids.push(inlay.id);
8044 inlays.push(inlay);
8045 }
8046
8047 self.splice_inlays(&[], inlays, cx);
8048 } else {
8049 let background_color = cx.theme().status().deleted_background;
8050 self.highlight_text::<EditPredictionHighlight>(
8051 edits.iter().map(|(range, _)| range.clone()).collect(),
8052 HighlightStyle {
8053 background_color: Some(background_color),
8054 ..Default::default()
8055 },
8056 cx,
8057 );
8058 }
8059 }
8060
8061 invalidation_row_range = edit_start_row..edit_end_row;
8062
8063 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8064 if provider.show_tab_accept_marker() {
8065 EditDisplayMode::TabAccept
8066 } else {
8067 EditDisplayMode::Inline
8068 }
8069 } else {
8070 EditDisplayMode::DiffPopover
8071 };
8072
8073 EditPrediction::Edit {
8074 edits,
8075 edit_preview,
8076 display_mode,
8077 snapshot,
8078 }
8079 };
8080
8081 let invalidation_range = multibuffer
8082 .anchor_before(Point::new(invalidation_row_range.start, 0))
8083 ..multibuffer.anchor_after(Point::new(
8084 invalidation_row_range.end,
8085 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8086 ));
8087
8088 self.stale_edit_prediction_in_menu = None;
8089 self.active_edit_prediction = Some(EditPredictionState {
8090 inlay_ids,
8091 completion,
8092 completion_id,
8093 invalidation_range: Some(invalidation_range),
8094 });
8095
8096 cx.notify();
8097
8098 Some(())
8099 }
8100
8101 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8102 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8103 }
8104
8105 fn clear_tasks(&mut self) {
8106 self.tasks.clear()
8107 }
8108
8109 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8110 if self.tasks.insert(key, value).is_some() {
8111 // This case should hopefully be rare, but just in case...
8112 log::error!(
8113 "multiple different run targets found on a single line, only the last target will be rendered"
8114 )
8115 }
8116 }
8117
8118 /// Get all display points of breakpoints that will be rendered within editor
8119 ///
8120 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8121 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8122 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8123 fn active_breakpoints(
8124 &self,
8125 range: Range<DisplayRow>,
8126 window: &mut Window,
8127 cx: &mut Context<Self>,
8128 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8129 let mut breakpoint_display_points = HashMap::default();
8130
8131 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8132 return breakpoint_display_points;
8133 };
8134
8135 let snapshot = self.snapshot(window, cx);
8136
8137 let multi_buffer_snapshot = snapshot.display_snapshot.buffer_snapshot();
8138 let Some(project) = self.project() else {
8139 return breakpoint_display_points;
8140 };
8141
8142 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8143 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8144
8145 for (buffer_snapshot, range, excerpt_id) in
8146 multi_buffer_snapshot.range_to_buffer_ranges(range)
8147 {
8148 let Some(buffer) = project
8149 .read(cx)
8150 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8151 else {
8152 continue;
8153 };
8154 let breakpoints = breakpoint_store.read(cx).breakpoints(
8155 &buffer,
8156 Some(
8157 buffer_snapshot.anchor_before(range.start)
8158 ..buffer_snapshot.anchor_after(range.end),
8159 ),
8160 buffer_snapshot,
8161 cx,
8162 );
8163 for (breakpoint, state) in breakpoints {
8164 let multi_buffer_anchor =
8165 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8166 let position = multi_buffer_anchor
8167 .to_point(multi_buffer_snapshot)
8168 .to_display_point(&snapshot);
8169
8170 breakpoint_display_points.insert(
8171 position.row(),
8172 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8173 );
8174 }
8175 }
8176
8177 breakpoint_display_points
8178 }
8179
8180 fn breakpoint_context_menu(
8181 &self,
8182 anchor: Anchor,
8183 window: &mut Window,
8184 cx: &mut Context<Self>,
8185 ) -> Entity<ui::ContextMenu> {
8186 let weak_editor = cx.weak_entity();
8187 let focus_handle = self.focus_handle(cx);
8188
8189 let row = self
8190 .buffer
8191 .read(cx)
8192 .snapshot(cx)
8193 .summary_for_anchor::<Point>(&anchor)
8194 .row;
8195
8196 let breakpoint = self
8197 .breakpoint_at_row(row, window, cx)
8198 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8199
8200 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8201 "Edit Log Breakpoint"
8202 } else {
8203 "Set Log Breakpoint"
8204 };
8205
8206 let condition_breakpoint_msg = if breakpoint
8207 .as_ref()
8208 .is_some_and(|bp| bp.1.condition.is_some())
8209 {
8210 "Edit Condition Breakpoint"
8211 } else {
8212 "Set Condition Breakpoint"
8213 };
8214
8215 let hit_condition_breakpoint_msg = if breakpoint
8216 .as_ref()
8217 .is_some_and(|bp| bp.1.hit_condition.is_some())
8218 {
8219 "Edit Hit Condition Breakpoint"
8220 } else {
8221 "Set Hit Condition Breakpoint"
8222 };
8223
8224 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8225 "Unset Breakpoint"
8226 } else {
8227 "Set Breakpoint"
8228 };
8229
8230 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8231
8232 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8233 BreakpointState::Enabled => Some("Disable"),
8234 BreakpointState::Disabled => Some("Enable"),
8235 });
8236
8237 let (anchor, breakpoint) =
8238 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8239
8240 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8241 menu.on_blur_subscription(Subscription::new(|| {}))
8242 .context(focus_handle)
8243 .when(run_to_cursor, |this| {
8244 let weak_editor = weak_editor.clone();
8245 this.entry("Run to cursor", None, move |window, cx| {
8246 weak_editor
8247 .update(cx, |editor, cx| {
8248 editor.change_selections(
8249 SelectionEffects::no_scroll(),
8250 window,
8251 cx,
8252 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8253 );
8254 })
8255 .ok();
8256
8257 window.dispatch_action(Box::new(RunToCursor), cx);
8258 })
8259 .separator()
8260 })
8261 .when_some(toggle_state_msg, |this, msg| {
8262 this.entry(msg, None, {
8263 let weak_editor = weak_editor.clone();
8264 let breakpoint = breakpoint.clone();
8265 move |_window, cx| {
8266 weak_editor
8267 .update(cx, |this, cx| {
8268 this.edit_breakpoint_at_anchor(
8269 anchor,
8270 breakpoint.as_ref().clone(),
8271 BreakpointEditAction::InvertState,
8272 cx,
8273 );
8274 })
8275 .log_err();
8276 }
8277 })
8278 })
8279 .entry(set_breakpoint_msg, None, {
8280 let weak_editor = weak_editor.clone();
8281 let breakpoint = breakpoint.clone();
8282 move |_window, cx| {
8283 weak_editor
8284 .update(cx, |this, cx| {
8285 this.edit_breakpoint_at_anchor(
8286 anchor,
8287 breakpoint.as_ref().clone(),
8288 BreakpointEditAction::Toggle,
8289 cx,
8290 );
8291 })
8292 .log_err();
8293 }
8294 })
8295 .entry(log_breakpoint_msg, None, {
8296 let breakpoint = breakpoint.clone();
8297 let weak_editor = weak_editor.clone();
8298 move |window, cx| {
8299 weak_editor
8300 .update(cx, |this, cx| {
8301 this.add_edit_breakpoint_block(
8302 anchor,
8303 breakpoint.as_ref(),
8304 BreakpointPromptEditAction::Log,
8305 window,
8306 cx,
8307 );
8308 })
8309 .log_err();
8310 }
8311 })
8312 .entry(condition_breakpoint_msg, None, {
8313 let breakpoint = breakpoint.clone();
8314 let weak_editor = weak_editor.clone();
8315 move |window, cx| {
8316 weak_editor
8317 .update(cx, |this, cx| {
8318 this.add_edit_breakpoint_block(
8319 anchor,
8320 breakpoint.as_ref(),
8321 BreakpointPromptEditAction::Condition,
8322 window,
8323 cx,
8324 );
8325 })
8326 .log_err();
8327 }
8328 })
8329 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8330 weak_editor
8331 .update(cx, |this, cx| {
8332 this.add_edit_breakpoint_block(
8333 anchor,
8334 breakpoint.as_ref(),
8335 BreakpointPromptEditAction::HitCondition,
8336 window,
8337 cx,
8338 );
8339 })
8340 .log_err();
8341 })
8342 })
8343 }
8344
8345 fn render_breakpoint(
8346 &self,
8347 position: Anchor,
8348 row: DisplayRow,
8349 breakpoint: &Breakpoint,
8350 state: Option<BreakpointSessionState>,
8351 cx: &mut Context<Self>,
8352 ) -> IconButton {
8353 let is_rejected = state.is_some_and(|s| !s.verified);
8354 // Is it a breakpoint that shows up when hovering over gutter?
8355 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8356 (false, false),
8357 |PhantomBreakpointIndicator {
8358 is_active,
8359 display_row,
8360 collides_with_existing_breakpoint,
8361 }| {
8362 (
8363 is_active && display_row == row,
8364 collides_with_existing_breakpoint,
8365 )
8366 },
8367 );
8368
8369 let (color, icon) = {
8370 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8371 (false, false) => ui::IconName::DebugBreakpoint,
8372 (true, false) => ui::IconName::DebugLogBreakpoint,
8373 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8374 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8375 };
8376
8377 let color = if is_phantom {
8378 Color::Hint
8379 } else if is_rejected {
8380 Color::Disabled
8381 } else {
8382 Color::Debugger
8383 };
8384
8385 (color, icon)
8386 };
8387
8388 let breakpoint = Arc::from(breakpoint.clone());
8389
8390 let alt_as_text = gpui::Keystroke {
8391 modifiers: Modifiers::secondary_key(),
8392 ..Default::default()
8393 };
8394 let primary_action_text = if breakpoint.is_disabled() {
8395 "Enable breakpoint"
8396 } else if is_phantom && !collides_with_existing {
8397 "Set breakpoint"
8398 } else {
8399 "Unset breakpoint"
8400 };
8401 let focus_handle = self.focus_handle.clone();
8402
8403 let meta = if is_rejected {
8404 SharedString::from("No executable code is associated with this line.")
8405 } else if collides_with_existing && !breakpoint.is_disabled() {
8406 SharedString::from(format!(
8407 "{alt_as_text}-click to disable,\nright-click for more options."
8408 ))
8409 } else {
8410 SharedString::from("Right-click for more options.")
8411 };
8412 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8413 .icon_size(IconSize::XSmall)
8414 .size(ui::ButtonSize::None)
8415 .when(is_rejected, |this| {
8416 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8417 })
8418 .icon_color(color)
8419 .style(ButtonStyle::Transparent)
8420 .on_click(cx.listener({
8421 move |editor, event: &ClickEvent, window, cx| {
8422 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8423 BreakpointEditAction::InvertState
8424 } else {
8425 BreakpointEditAction::Toggle
8426 };
8427
8428 window.focus(&editor.focus_handle(cx));
8429 editor.edit_breakpoint_at_anchor(
8430 position,
8431 breakpoint.as_ref().clone(),
8432 edit_action,
8433 cx,
8434 );
8435 }
8436 }))
8437 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8438 editor.set_breakpoint_context_menu(
8439 row,
8440 Some(position),
8441 event.position(),
8442 window,
8443 cx,
8444 );
8445 }))
8446 .tooltip(move |window, cx| {
8447 Tooltip::with_meta_in(
8448 primary_action_text,
8449 Some(&ToggleBreakpoint),
8450 meta.clone(),
8451 &focus_handle,
8452 window,
8453 cx,
8454 )
8455 })
8456 }
8457
8458 fn build_tasks_context(
8459 project: &Entity<Project>,
8460 buffer: &Entity<Buffer>,
8461 buffer_row: u32,
8462 tasks: &Arc<RunnableTasks>,
8463 cx: &mut Context<Self>,
8464 ) -> Task<Option<task::TaskContext>> {
8465 let position = Point::new(buffer_row, tasks.column);
8466 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8467 let location = Location {
8468 buffer: buffer.clone(),
8469 range: range_start..range_start,
8470 };
8471 // Fill in the environmental variables from the tree-sitter captures
8472 let mut captured_task_variables = TaskVariables::default();
8473 for (capture_name, value) in tasks.extra_variables.clone() {
8474 captured_task_variables.insert(
8475 task::VariableName::Custom(capture_name.into()),
8476 value.clone(),
8477 );
8478 }
8479 project.update(cx, |project, cx| {
8480 project.task_store().update(cx, |task_store, cx| {
8481 task_store.task_context_for_location(captured_task_variables, location, cx)
8482 })
8483 })
8484 }
8485
8486 pub fn spawn_nearest_task(
8487 &mut self,
8488 action: &SpawnNearestTask,
8489 window: &mut Window,
8490 cx: &mut Context<Self>,
8491 ) {
8492 let Some((workspace, _)) = self.workspace.clone() else {
8493 return;
8494 };
8495 let Some(project) = self.project.clone() else {
8496 return;
8497 };
8498
8499 // Try to find a closest, enclosing node using tree-sitter that has a task
8500 let Some((buffer, buffer_row, tasks)) = self
8501 .find_enclosing_node_task(cx)
8502 // Or find the task that's closest in row-distance.
8503 .or_else(|| self.find_closest_task(cx))
8504 else {
8505 return;
8506 };
8507
8508 let reveal_strategy = action.reveal;
8509 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8510 cx.spawn_in(window, async move |_, cx| {
8511 let context = task_context.await?;
8512 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8513
8514 let resolved = &mut resolved_task.resolved;
8515 resolved.reveal = reveal_strategy;
8516
8517 workspace
8518 .update_in(cx, |workspace, window, cx| {
8519 workspace.schedule_resolved_task(
8520 task_source_kind,
8521 resolved_task,
8522 false,
8523 window,
8524 cx,
8525 );
8526 })
8527 .ok()
8528 })
8529 .detach();
8530 }
8531
8532 fn find_closest_task(
8533 &mut self,
8534 cx: &mut Context<Self>,
8535 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8536 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8537
8538 let ((buffer_id, row), tasks) = self
8539 .tasks
8540 .iter()
8541 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8542
8543 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8544 let tasks = Arc::new(tasks.to_owned());
8545 Some((buffer, *row, tasks))
8546 }
8547
8548 fn find_enclosing_node_task(
8549 &mut self,
8550 cx: &mut Context<Self>,
8551 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8552 let snapshot = self.buffer.read(cx).snapshot(cx);
8553 let offset = self.selections.newest::<usize>(cx).head();
8554 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8555 let buffer_id = excerpt.buffer().remote_id();
8556
8557 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8558 let mut cursor = layer.node().walk();
8559
8560 while cursor.goto_first_child_for_byte(offset).is_some() {
8561 if cursor.node().end_byte() == offset {
8562 cursor.goto_next_sibling();
8563 }
8564 }
8565
8566 // Ascend to the smallest ancestor that contains the range and has a task.
8567 loop {
8568 let node = cursor.node();
8569 let node_range = node.byte_range();
8570 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8571
8572 // Check if this node contains our offset
8573 if node_range.start <= offset && node_range.end >= offset {
8574 // If it contains offset, check for task
8575 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8576 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8577 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8578 }
8579 }
8580
8581 if !cursor.goto_parent() {
8582 break;
8583 }
8584 }
8585 None
8586 }
8587
8588 fn render_run_indicator(
8589 &self,
8590 _style: &EditorStyle,
8591 is_active: bool,
8592 row: DisplayRow,
8593 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8594 cx: &mut Context<Self>,
8595 ) -> IconButton {
8596 let color = Color::Muted;
8597 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8598
8599 IconButton::new(
8600 ("run_indicator", row.0 as usize),
8601 ui::IconName::PlayOutlined,
8602 )
8603 .shape(ui::IconButtonShape::Square)
8604 .icon_size(IconSize::XSmall)
8605 .icon_color(color)
8606 .toggle_state(is_active)
8607 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8608 let quick_launch = match e {
8609 ClickEvent::Keyboard(_) => true,
8610 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8611 };
8612
8613 window.focus(&editor.focus_handle(cx));
8614 editor.toggle_code_actions(
8615 &ToggleCodeActions {
8616 deployed_from: Some(CodeActionSource::RunMenu(row)),
8617 quick_launch,
8618 },
8619 window,
8620 cx,
8621 );
8622 }))
8623 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8624 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8625 }))
8626 }
8627
8628 pub fn context_menu_visible(&self) -> bool {
8629 !self.edit_prediction_preview_is_active()
8630 && self
8631 .context_menu
8632 .borrow()
8633 .as_ref()
8634 .is_some_and(|menu| menu.visible())
8635 }
8636
8637 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8638 self.context_menu
8639 .borrow()
8640 .as_ref()
8641 .map(|menu| menu.origin())
8642 }
8643
8644 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8645 self.context_menu_options = Some(options);
8646 }
8647
8648 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8649 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8650
8651 fn render_edit_prediction_popover(
8652 &mut self,
8653 text_bounds: &Bounds<Pixels>,
8654 content_origin: gpui::Point<Pixels>,
8655 right_margin: Pixels,
8656 editor_snapshot: &EditorSnapshot,
8657 visible_row_range: Range<DisplayRow>,
8658 scroll_top: ScrollOffset,
8659 scroll_bottom: ScrollOffset,
8660 line_layouts: &[LineWithInvisibles],
8661 line_height: Pixels,
8662 scroll_position: gpui::Point<ScrollOffset>,
8663 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8664 newest_selection_head: Option<DisplayPoint>,
8665 editor_width: Pixels,
8666 style: &EditorStyle,
8667 window: &mut Window,
8668 cx: &mut App,
8669 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8670 if self.mode().is_minimap() {
8671 return None;
8672 }
8673 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8674
8675 if self.edit_prediction_visible_in_cursor_popover(true) {
8676 return None;
8677 }
8678
8679 match &active_edit_prediction.completion {
8680 EditPrediction::MoveWithin { target, .. } => {
8681 let target_display_point = target.to_display_point(editor_snapshot);
8682
8683 if self.edit_prediction_requires_modifier() {
8684 if !self.edit_prediction_preview_is_active() {
8685 return None;
8686 }
8687
8688 self.render_edit_prediction_modifier_jump_popover(
8689 text_bounds,
8690 content_origin,
8691 visible_row_range,
8692 line_layouts,
8693 line_height,
8694 scroll_pixel_position,
8695 newest_selection_head,
8696 target_display_point,
8697 window,
8698 cx,
8699 )
8700 } else {
8701 self.render_edit_prediction_eager_jump_popover(
8702 text_bounds,
8703 content_origin,
8704 editor_snapshot,
8705 visible_row_range,
8706 scroll_top,
8707 scroll_bottom,
8708 line_height,
8709 scroll_pixel_position,
8710 target_display_point,
8711 editor_width,
8712 window,
8713 cx,
8714 )
8715 }
8716 }
8717 EditPrediction::Edit {
8718 display_mode: EditDisplayMode::Inline,
8719 ..
8720 } => None,
8721 EditPrediction::Edit {
8722 display_mode: EditDisplayMode::TabAccept,
8723 edits,
8724 ..
8725 } => {
8726 let range = &edits.first()?.0;
8727 let target_display_point = range.end.to_display_point(editor_snapshot);
8728
8729 self.render_edit_prediction_end_of_line_popover(
8730 "Accept",
8731 editor_snapshot,
8732 visible_row_range,
8733 target_display_point,
8734 line_height,
8735 scroll_pixel_position,
8736 content_origin,
8737 editor_width,
8738 window,
8739 cx,
8740 )
8741 }
8742 EditPrediction::Edit {
8743 edits,
8744 edit_preview,
8745 display_mode: EditDisplayMode::DiffPopover,
8746 snapshot,
8747 } => self.render_edit_prediction_diff_popover(
8748 text_bounds,
8749 content_origin,
8750 right_margin,
8751 editor_snapshot,
8752 visible_row_range,
8753 line_layouts,
8754 line_height,
8755 scroll_position,
8756 scroll_pixel_position,
8757 newest_selection_head,
8758 editor_width,
8759 style,
8760 edits,
8761 edit_preview,
8762 snapshot,
8763 window,
8764 cx,
8765 ),
8766 EditPrediction::MoveOutside { snapshot, .. } => {
8767 let file_name = snapshot
8768 .file()
8769 .map(|file| file.file_name(cx))
8770 .unwrap_or("untitled");
8771 let mut element = self
8772 .render_edit_prediction_line_popover(
8773 format!("Jump to {file_name}"),
8774 Some(IconName::ZedPredict),
8775 window,
8776 cx,
8777 )
8778 .into_any();
8779
8780 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8781 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8782 let origin_y = text_bounds.size.height - size.height - px(30.);
8783 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8784 element.prepaint_at(origin, window, cx);
8785
8786 Some((element, origin))
8787 }
8788 }
8789 }
8790
8791 fn render_edit_prediction_modifier_jump_popover(
8792 &mut self,
8793 text_bounds: &Bounds<Pixels>,
8794 content_origin: gpui::Point<Pixels>,
8795 visible_row_range: Range<DisplayRow>,
8796 line_layouts: &[LineWithInvisibles],
8797 line_height: Pixels,
8798 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8799 newest_selection_head: Option<DisplayPoint>,
8800 target_display_point: DisplayPoint,
8801 window: &mut Window,
8802 cx: &mut App,
8803 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8804 let scrolled_content_origin =
8805 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8806
8807 const SCROLL_PADDING_Y: Pixels = px(12.);
8808
8809 if target_display_point.row() < visible_row_range.start {
8810 return self.render_edit_prediction_scroll_popover(
8811 |_| SCROLL_PADDING_Y,
8812 IconName::ArrowUp,
8813 visible_row_range,
8814 line_layouts,
8815 newest_selection_head,
8816 scrolled_content_origin,
8817 window,
8818 cx,
8819 );
8820 } else if target_display_point.row() >= visible_row_range.end {
8821 return self.render_edit_prediction_scroll_popover(
8822 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8823 IconName::ArrowDown,
8824 visible_row_range,
8825 line_layouts,
8826 newest_selection_head,
8827 scrolled_content_origin,
8828 window,
8829 cx,
8830 );
8831 }
8832
8833 const POLE_WIDTH: Pixels = px(2.);
8834
8835 let line_layout =
8836 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8837 let target_column = target_display_point.column() as usize;
8838
8839 let target_x = line_layout.x_for_index(target_column);
8840 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8841 - scroll_pixel_position.y;
8842
8843 let flag_on_right = target_x < text_bounds.size.width / 2.;
8844
8845 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8846 border_color.l += 0.001;
8847
8848 let mut element = v_flex()
8849 .items_end()
8850 .when(flag_on_right, |el| el.items_start())
8851 .child(if flag_on_right {
8852 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8853 .rounded_bl(px(0.))
8854 .rounded_tl(px(0.))
8855 .border_l_2()
8856 .border_color(border_color)
8857 } else {
8858 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8859 .rounded_br(px(0.))
8860 .rounded_tr(px(0.))
8861 .border_r_2()
8862 .border_color(border_color)
8863 })
8864 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8865 .into_any();
8866
8867 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8868
8869 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8870 - point(
8871 if flag_on_right {
8872 POLE_WIDTH
8873 } else {
8874 size.width - POLE_WIDTH
8875 },
8876 size.height - line_height,
8877 );
8878
8879 origin.x = origin.x.max(content_origin.x);
8880
8881 element.prepaint_at(origin, window, cx);
8882
8883 Some((element, origin))
8884 }
8885
8886 fn render_edit_prediction_scroll_popover(
8887 &mut self,
8888 to_y: impl Fn(Size<Pixels>) -> Pixels,
8889 scroll_icon: IconName,
8890 visible_row_range: Range<DisplayRow>,
8891 line_layouts: &[LineWithInvisibles],
8892 newest_selection_head: Option<DisplayPoint>,
8893 scrolled_content_origin: gpui::Point<Pixels>,
8894 window: &mut Window,
8895 cx: &mut App,
8896 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8897 let mut element = self
8898 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8899 .into_any();
8900
8901 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8902
8903 let cursor = newest_selection_head?;
8904 let cursor_row_layout =
8905 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8906 let cursor_column = cursor.column() as usize;
8907
8908 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8909
8910 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8911
8912 element.prepaint_at(origin, window, cx);
8913 Some((element, origin))
8914 }
8915
8916 fn render_edit_prediction_eager_jump_popover(
8917 &mut self,
8918 text_bounds: &Bounds<Pixels>,
8919 content_origin: gpui::Point<Pixels>,
8920 editor_snapshot: &EditorSnapshot,
8921 visible_row_range: Range<DisplayRow>,
8922 scroll_top: ScrollOffset,
8923 scroll_bottom: ScrollOffset,
8924 line_height: Pixels,
8925 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8926 target_display_point: DisplayPoint,
8927 editor_width: Pixels,
8928 window: &mut Window,
8929 cx: &mut App,
8930 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8931 if target_display_point.row().as_f64() < scroll_top {
8932 let mut element = self
8933 .render_edit_prediction_line_popover(
8934 "Jump to Edit",
8935 Some(IconName::ArrowUp),
8936 window,
8937 cx,
8938 )
8939 .into_any();
8940
8941 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8942 let offset = point(
8943 (text_bounds.size.width - size.width) / 2.,
8944 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8945 );
8946
8947 let origin = text_bounds.origin + offset;
8948 element.prepaint_at(origin, window, cx);
8949 Some((element, origin))
8950 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8951 let mut element = self
8952 .render_edit_prediction_line_popover(
8953 "Jump to Edit",
8954 Some(IconName::ArrowDown),
8955 window,
8956 cx,
8957 )
8958 .into_any();
8959
8960 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8961 let offset = point(
8962 (text_bounds.size.width - size.width) / 2.,
8963 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8964 );
8965
8966 let origin = text_bounds.origin + offset;
8967 element.prepaint_at(origin, window, cx);
8968 Some((element, origin))
8969 } else {
8970 self.render_edit_prediction_end_of_line_popover(
8971 "Jump to Edit",
8972 editor_snapshot,
8973 visible_row_range,
8974 target_display_point,
8975 line_height,
8976 scroll_pixel_position,
8977 content_origin,
8978 editor_width,
8979 window,
8980 cx,
8981 )
8982 }
8983 }
8984
8985 fn render_edit_prediction_end_of_line_popover(
8986 self: &mut Editor,
8987 label: &'static str,
8988 editor_snapshot: &EditorSnapshot,
8989 visible_row_range: Range<DisplayRow>,
8990 target_display_point: DisplayPoint,
8991 line_height: Pixels,
8992 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8993 content_origin: gpui::Point<Pixels>,
8994 editor_width: Pixels,
8995 window: &mut Window,
8996 cx: &mut App,
8997 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8998 let target_line_end = DisplayPoint::new(
8999 target_display_point.row(),
9000 editor_snapshot.line_len(target_display_point.row()),
9001 );
9002
9003 let mut element = self
9004 .render_edit_prediction_line_popover(label, None, window, cx)
9005 .into_any();
9006
9007 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9008
9009 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
9010
9011 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9012 let mut origin = start_point
9013 + line_origin
9014 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9015 origin.x = origin.x.max(content_origin.x);
9016
9017 let max_x = content_origin.x + editor_width - size.width;
9018
9019 if origin.x > max_x {
9020 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9021
9022 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9023 origin.y += offset;
9024 IconName::ArrowUp
9025 } else {
9026 origin.y -= offset;
9027 IconName::ArrowDown
9028 };
9029
9030 element = self
9031 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9032 .into_any();
9033
9034 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9035
9036 origin.x = content_origin.x + editor_width - size.width - px(2.);
9037 }
9038
9039 element.prepaint_at(origin, window, cx);
9040 Some((element, origin))
9041 }
9042
9043 fn render_edit_prediction_diff_popover(
9044 self: &Editor,
9045 text_bounds: &Bounds<Pixels>,
9046 content_origin: gpui::Point<Pixels>,
9047 right_margin: Pixels,
9048 editor_snapshot: &EditorSnapshot,
9049 visible_row_range: Range<DisplayRow>,
9050 line_layouts: &[LineWithInvisibles],
9051 line_height: Pixels,
9052 scroll_position: gpui::Point<ScrollOffset>,
9053 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9054 newest_selection_head: Option<DisplayPoint>,
9055 editor_width: Pixels,
9056 style: &EditorStyle,
9057 edits: &Vec<(Range<Anchor>, String)>,
9058 edit_preview: &Option<language::EditPreview>,
9059 snapshot: &language::BufferSnapshot,
9060 window: &mut Window,
9061 cx: &mut App,
9062 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9063 let edit_start = edits
9064 .first()
9065 .unwrap()
9066 .0
9067 .start
9068 .to_display_point(editor_snapshot);
9069 let edit_end = edits
9070 .last()
9071 .unwrap()
9072 .0
9073 .end
9074 .to_display_point(editor_snapshot);
9075
9076 let is_visible = visible_row_range.contains(&edit_start.row())
9077 || visible_row_range.contains(&edit_end.row());
9078 if !is_visible {
9079 return None;
9080 }
9081
9082 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9083 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9084 } else {
9085 // Fallback for providers without edit_preview
9086 crate::edit_prediction_fallback_text(edits, cx)
9087 };
9088
9089 let styled_text = highlighted_edits.to_styled_text(&style.text);
9090 let line_count = highlighted_edits.text.lines().count();
9091
9092 const BORDER_WIDTH: Pixels = px(1.);
9093
9094 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9095 let has_keybind = keybind.is_some();
9096
9097 let mut element = h_flex()
9098 .items_start()
9099 .child(
9100 h_flex()
9101 .bg(cx.theme().colors().editor_background)
9102 .border(BORDER_WIDTH)
9103 .shadow_xs()
9104 .border_color(cx.theme().colors().border)
9105 .rounded_l_lg()
9106 .when(line_count > 1, |el| el.rounded_br_lg())
9107 .pr_1()
9108 .child(styled_text),
9109 )
9110 .child(
9111 h_flex()
9112 .h(line_height + BORDER_WIDTH * 2.)
9113 .px_1p5()
9114 .gap_1()
9115 // Workaround: For some reason, there's a gap if we don't do this
9116 .ml(-BORDER_WIDTH)
9117 .shadow(vec![gpui::BoxShadow {
9118 color: gpui::black().opacity(0.05),
9119 offset: point(px(1.), px(1.)),
9120 blur_radius: px(2.),
9121 spread_radius: px(0.),
9122 }])
9123 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9124 .border(BORDER_WIDTH)
9125 .border_color(cx.theme().colors().border)
9126 .rounded_r_lg()
9127 .id("edit_prediction_diff_popover_keybind")
9128 .when(!has_keybind, |el| {
9129 let status_colors = cx.theme().status();
9130
9131 el.bg(status_colors.error_background)
9132 .border_color(status_colors.error.opacity(0.6))
9133 .child(Icon::new(IconName::Info).color(Color::Error))
9134 .cursor_default()
9135 .hoverable_tooltip(move |_window, cx| {
9136 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9137 })
9138 })
9139 .children(keybind),
9140 )
9141 .into_any();
9142
9143 let longest_row =
9144 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9145 let longest_line_width = if visible_row_range.contains(&longest_row) {
9146 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9147 } else {
9148 layout_line(
9149 longest_row,
9150 editor_snapshot,
9151 style,
9152 editor_width,
9153 |_| false,
9154 window,
9155 cx,
9156 )
9157 .width
9158 };
9159
9160 let viewport_bounds =
9161 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9162 right: -right_margin,
9163 ..Default::default()
9164 });
9165
9166 let x_after_longest = Pixels::from(
9167 ScrollPixelOffset::from(
9168 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9169 ) - scroll_pixel_position.x,
9170 );
9171
9172 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9173
9174 // Fully visible if it can be displayed within the window (allow overlapping other
9175 // panes). However, this is only allowed if the popover starts within text_bounds.
9176 let can_position_to_the_right = x_after_longest < text_bounds.right()
9177 && x_after_longest + element_bounds.width < viewport_bounds.right();
9178
9179 let mut origin = if can_position_to_the_right {
9180 point(
9181 x_after_longest,
9182 text_bounds.origin.y
9183 + Pixels::from(
9184 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9185 - scroll_pixel_position.y,
9186 ),
9187 )
9188 } else {
9189 let cursor_row = newest_selection_head.map(|head| head.row());
9190 let above_edit = edit_start
9191 .row()
9192 .0
9193 .checked_sub(line_count as u32)
9194 .map(DisplayRow);
9195 let below_edit = Some(edit_end.row() + 1);
9196 let above_cursor =
9197 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9198 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9199
9200 // Place the edit popover adjacent to the edit if there is a location
9201 // available that is onscreen and does not obscure the cursor. Otherwise,
9202 // place it adjacent to the cursor.
9203 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9204 .into_iter()
9205 .flatten()
9206 .find(|&start_row| {
9207 let end_row = start_row + line_count as u32;
9208 visible_row_range.contains(&start_row)
9209 && visible_row_range.contains(&end_row)
9210 && cursor_row
9211 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9212 })?;
9213
9214 content_origin
9215 + point(
9216 Pixels::from(-scroll_pixel_position.x),
9217 Pixels::from(
9218 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9219 ),
9220 )
9221 };
9222
9223 origin.x -= BORDER_WIDTH;
9224
9225 window.defer_draw(element, origin, 1);
9226
9227 // Do not return an element, since it will already be drawn due to defer_draw.
9228 None
9229 }
9230
9231 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9232 px(30.)
9233 }
9234
9235 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9236 if self.read_only(cx) {
9237 cx.theme().players().read_only()
9238 } else {
9239 self.style.as_ref().unwrap().local_player
9240 }
9241 }
9242
9243 fn render_edit_prediction_accept_keybind(
9244 &self,
9245 window: &mut Window,
9246 cx: &App,
9247 ) -> Option<AnyElement> {
9248 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9249 let accept_keystroke = accept_binding.keystroke()?;
9250
9251 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9252
9253 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9254 Color::Accent
9255 } else {
9256 Color::Muted
9257 };
9258
9259 h_flex()
9260 .px_0p5()
9261 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9262 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9263 .text_size(TextSize::XSmall.rems(cx))
9264 .child(h_flex().children(ui::render_modifiers(
9265 accept_keystroke.modifiers(),
9266 PlatformStyle::platform(),
9267 Some(modifiers_color),
9268 Some(IconSize::XSmall.rems().into()),
9269 true,
9270 )))
9271 .when(is_platform_style_mac, |parent| {
9272 parent.child(accept_keystroke.key().to_string())
9273 })
9274 .when(!is_platform_style_mac, |parent| {
9275 parent.child(
9276 Key::new(
9277 util::capitalize(accept_keystroke.key()),
9278 Some(Color::Default),
9279 )
9280 .size(Some(IconSize::XSmall.rems().into())),
9281 )
9282 })
9283 .into_any()
9284 .into()
9285 }
9286
9287 fn render_edit_prediction_line_popover(
9288 &self,
9289 label: impl Into<SharedString>,
9290 icon: Option<IconName>,
9291 window: &mut Window,
9292 cx: &App,
9293 ) -> Stateful<Div> {
9294 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9295
9296 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9297 let has_keybind = keybind.is_some();
9298
9299 h_flex()
9300 .id("ep-line-popover")
9301 .py_0p5()
9302 .pl_1()
9303 .pr(padding_right)
9304 .gap_1()
9305 .rounded_md()
9306 .border_1()
9307 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9308 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9309 .shadow_xs()
9310 .when(!has_keybind, |el| {
9311 let status_colors = cx.theme().status();
9312
9313 el.bg(status_colors.error_background)
9314 .border_color(status_colors.error.opacity(0.6))
9315 .pl_2()
9316 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9317 .cursor_default()
9318 .hoverable_tooltip(move |_window, cx| {
9319 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9320 })
9321 })
9322 .children(keybind)
9323 .child(
9324 Label::new(label)
9325 .size(LabelSize::Small)
9326 .when(!has_keybind, |el| {
9327 el.color(cx.theme().status().error.into()).strikethrough()
9328 }),
9329 )
9330 .when(!has_keybind, |el| {
9331 el.child(
9332 h_flex().ml_1().child(
9333 Icon::new(IconName::Info)
9334 .size(IconSize::Small)
9335 .color(cx.theme().status().error.into()),
9336 ),
9337 )
9338 })
9339 .when_some(icon, |element, icon| {
9340 element.child(
9341 div()
9342 .mt(px(1.5))
9343 .child(Icon::new(icon).size(IconSize::Small)),
9344 )
9345 })
9346 }
9347
9348 fn edit_prediction_line_popover_bg_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.1))
9352 }
9353
9354 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9355 let accent_color = cx.theme().colors().text_accent;
9356 let editor_bg_color = cx.theme().colors().editor_background;
9357 editor_bg_color.blend(accent_color.opacity(0.6))
9358 }
9359 fn get_prediction_provider_icon_name(
9360 provider: &Option<RegisteredEditPredictionProvider>,
9361 ) -> IconName {
9362 match provider {
9363 Some(provider) => match provider.provider.name() {
9364 "copilot" => IconName::Copilot,
9365 "supermaven" => IconName::Supermaven,
9366 _ => IconName::ZedPredict,
9367 },
9368 None => IconName::ZedPredict,
9369 }
9370 }
9371
9372 fn render_edit_prediction_cursor_popover(
9373 &self,
9374 min_width: Pixels,
9375 max_width: Pixels,
9376 cursor_point: Point,
9377 style: &EditorStyle,
9378 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9379 _window: &Window,
9380 cx: &mut Context<Editor>,
9381 ) -> Option<AnyElement> {
9382 let provider = self.edit_prediction_provider.as_ref()?;
9383 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9384
9385 let is_refreshing = provider.provider.is_refreshing(cx);
9386
9387 fn pending_completion_container(icon: IconName) -> Div {
9388 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9389 }
9390
9391 let completion = match &self.active_edit_prediction {
9392 Some(prediction) => {
9393 if !self.has_visible_completions_menu() {
9394 const RADIUS: Pixels = px(6.);
9395 const BORDER_WIDTH: Pixels = px(1.);
9396
9397 return Some(
9398 h_flex()
9399 .elevation_2(cx)
9400 .border(BORDER_WIDTH)
9401 .border_color(cx.theme().colors().border)
9402 .when(accept_keystroke.is_none(), |el| {
9403 el.border_color(cx.theme().status().error)
9404 })
9405 .rounded(RADIUS)
9406 .rounded_tl(px(0.))
9407 .overflow_hidden()
9408 .child(div().px_1p5().child(match &prediction.completion {
9409 EditPrediction::MoveWithin { target, snapshot } => {
9410 use text::ToPoint as _;
9411 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9412 {
9413 Icon::new(IconName::ZedPredictDown)
9414 } else {
9415 Icon::new(IconName::ZedPredictUp)
9416 }
9417 }
9418 EditPrediction::MoveOutside { .. } => {
9419 // TODO [zeta2] custom icon for external jump?
9420 Icon::new(provider_icon)
9421 }
9422 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9423 }))
9424 .child(
9425 h_flex()
9426 .gap_1()
9427 .py_1()
9428 .px_2()
9429 .rounded_r(RADIUS - BORDER_WIDTH)
9430 .border_l_1()
9431 .border_color(cx.theme().colors().border)
9432 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9433 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9434 el.child(
9435 Label::new("Hold")
9436 .size(LabelSize::Small)
9437 .when(accept_keystroke.is_none(), |el| {
9438 el.strikethrough()
9439 })
9440 .line_height_style(LineHeightStyle::UiLabel),
9441 )
9442 })
9443 .id("edit_prediction_cursor_popover_keybind")
9444 .when(accept_keystroke.is_none(), |el| {
9445 let status_colors = cx.theme().status();
9446
9447 el.bg(status_colors.error_background)
9448 .border_color(status_colors.error.opacity(0.6))
9449 .child(Icon::new(IconName::Info).color(Color::Error))
9450 .cursor_default()
9451 .hoverable_tooltip(move |_window, cx| {
9452 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9453 .into()
9454 })
9455 })
9456 .when_some(
9457 accept_keystroke.as_ref(),
9458 |el, accept_keystroke| {
9459 el.child(h_flex().children(ui::render_modifiers(
9460 accept_keystroke.modifiers(),
9461 PlatformStyle::platform(),
9462 Some(Color::Default),
9463 Some(IconSize::XSmall.rems().into()),
9464 false,
9465 )))
9466 },
9467 ),
9468 )
9469 .into_any(),
9470 );
9471 }
9472
9473 self.render_edit_prediction_cursor_popover_preview(
9474 prediction,
9475 cursor_point,
9476 style,
9477 cx,
9478 )?
9479 }
9480
9481 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9482 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9483 stale_completion,
9484 cursor_point,
9485 style,
9486 cx,
9487 )?,
9488
9489 None => pending_completion_container(provider_icon)
9490 .child(Label::new("...").size(LabelSize::Small)),
9491 },
9492
9493 None => pending_completion_container(provider_icon)
9494 .child(Label::new("...").size(LabelSize::Small)),
9495 };
9496
9497 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9498 completion
9499 .with_animation(
9500 "loading-completion",
9501 Animation::new(Duration::from_secs(2))
9502 .repeat()
9503 .with_easing(pulsating_between(0.4, 0.8)),
9504 |label, delta| label.opacity(delta),
9505 )
9506 .into_any_element()
9507 } else {
9508 completion.into_any_element()
9509 };
9510
9511 let has_completion = self.active_edit_prediction.is_some();
9512
9513 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9514 Some(
9515 h_flex()
9516 .min_w(min_width)
9517 .max_w(max_width)
9518 .flex_1()
9519 .elevation_2(cx)
9520 .border_color(cx.theme().colors().border)
9521 .child(
9522 div()
9523 .flex_1()
9524 .py_1()
9525 .px_2()
9526 .overflow_hidden()
9527 .child(completion),
9528 )
9529 .when_some(accept_keystroke, |el, accept_keystroke| {
9530 if !accept_keystroke.modifiers().modified() {
9531 return el;
9532 }
9533
9534 el.child(
9535 h_flex()
9536 .h_full()
9537 .border_l_1()
9538 .rounded_r_lg()
9539 .border_color(cx.theme().colors().border)
9540 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9541 .gap_1()
9542 .py_1()
9543 .px_2()
9544 .child(
9545 h_flex()
9546 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9547 .when(is_platform_style_mac, |parent| parent.gap_1())
9548 .child(h_flex().children(ui::render_modifiers(
9549 accept_keystroke.modifiers(),
9550 PlatformStyle::platform(),
9551 Some(if !has_completion {
9552 Color::Muted
9553 } else {
9554 Color::Default
9555 }),
9556 None,
9557 false,
9558 ))),
9559 )
9560 .child(Label::new("Preview").into_any_element())
9561 .opacity(if has_completion { 1.0 } else { 0.4 }),
9562 )
9563 })
9564 .into_any(),
9565 )
9566 }
9567
9568 fn render_edit_prediction_cursor_popover_preview(
9569 &self,
9570 completion: &EditPredictionState,
9571 cursor_point: Point,
9572 style: &EditorStyle,
9573 cx: &mut Context<Editor>,
9574 ) -> Option<Div> {
9575 use text::ToPoint as _;
9576
9577 fn render_relative_row_jump(
9578 prefix: impl Into<String>,
9579 current_row: u32,
9580 target_row: u32,
9581 ) -> Div {
9582 let (row_diff, arrow) = if target_row < current_row {
9583 (current_row - target_row, IconName::ArrowUp)
9584 } else {
9585 (target_row - current_row, IconName::ArrowDown)
9586 };
9587
9588 h_flex()
9589 .child(
9590 Label::new(format!("{}{}", prefix.into(), row_diff))
9591 .color(Color::Muted)
9592 .size(LabelSize::Small),
9593 )
9594 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9595 }
9596
9597 let supports_jump = self
9598 .edit_prediction_provider
9599 .as_ref()
9600 .map(|provider| provider.provider.supports_jump_to_edit())
9601 .unwrap_or(true);
9602
9603 match &completion.completion {
9604 EditPrediction::MoveWithin {
9605 target, snapshot, ..
9606 } => {
9607 if !supports_jump {
9608 return None;
9609 }
9610
9611 Some(
9612 h_flex()
9613 .px_2()
9614 .gap_2()
9615 .flex_1()
9616 .child(
9617 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9618 Icon::new(IconName::ZedPredictDown)
9619 } else {
9620 Icon::new(IconName::ZedPredictUp)
9621 },
9622 )
9623 .child(Label::new("Jump to Edit")),
9624 )
9625 }
9626 EditPrediction::MoveOutside { snapshot, .. } => {
9627 let file_name = snapshot
9628 .file()
9629 .map(|file| file.file_name(cx))
9630 .unwrap_or("untitled");
9631 Some(
9632 h_flex()
9633 .px_2()
9634 .gap_2()
9635 .flex_1()
9636 .child(Icon::new(IconName::ZedPredict))
9637 .child(Label::new(format!("Jump to {file_name}"))),
9638 )
9639 }
9640 EditPrediction::Edit {
9641 edits,
9642 edit_preview,
9643 snapshot,
9644 display_mode: _,
9645 } => {
9646 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9647
9648 let (highlighted_edits, has_more_lines) =
9649 if let Some(edit_preview) = edit_preview.as_ref() {
9650 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9651 .first_line_preview()
9652 } else {
9653 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9654 };
9655
9656 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9657 .with_default_highlights(&style.text, highlighted_edits.highlights);
9658
9659 let preview = h_flex()
9660 .gap_1()
9661 .min_w_16()
9662 .child(styled_text)
9663 .when(has_more_lines, |parent| parent.child("…"));
9664
9665 let left = if supports_jump && first_edit_row != cursor_point.row {
9666 render_relative_row_jump("", cursor_point.row, first_edit_row)
9667 .into_any_element()
9668 } else {
9669 let icon_name =
9670 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9671 Icon::new(icon_name).into_any_element()
9672 };
9673
9674 Some(
9675 h_flex()
9676 .h_full()
9677 .flex_1()
9678 .gap_2()
9679 .pr_1()
9680 .overflow_x_hidden()
9681 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9682 .child(left)
9683 .child(preview),
9684 )
9685 }
9686 }
9687 }
9688
9689 pub fn render_context_menu(
9690 &self,
9691 style: &EditorStyle,
9692 max_height_in_lines: u32,
9693 window: &mut Window,
9694 cx: &mut Context<Editor>,
9695 ) -> Option<AnyElement> {
9696 let menu = self.context_menu.borrow();
9697 let menu = menu.as_ref()?;
9698 if !menu.visible() {
9699 return None;
9700 };
9701 Some(menu.render(style, max_height_in_lines, window, cx))
9702 }
9703
9704 fn render_context_menu_aside(
9705 &mut self,
9706 max_size: Size<Pixels>,
9707 window: &mut Window,
9708 cx: &mut Context<Editor>,
9709 ) -> Option<AnyElement> {
9710 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9711 if menu.visible() {
9712 menu.render_aside(max_size, window, cx)
9713 } else {
9714 None
9715 }
9716 })
9717 }
9718
9719 fn hide_context_menu(
9720 &mut self,
9721 window: &mut Window,
9722 cx: &mut Context<Self>,
9723 ) -> Option<CodeContextMenu> {
9724 cx.notify();
9725 self.completion_tasks.clear();
9726 let context_menu = self.context_menu.borrow_mut().take();
9727 self.stale_edit_prediction_in_menu.take();
9728 self.update_visible_edit_prediction(window, cx);
9729 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9730 && let Some(completion_provider) = &self.completion_provider
9731 {
9732 completion_provider.selection_changed(None, window, cx);
9733 }
9734 context_menu
9735 }
9736
9737 fn show_snippet_choices(
9738 &mut self,
9739 choices: &Vec<String>,
9740 selection: Range<Anchor>,
9741 cx: &mut Context<Self>,
9742 ) {
9743 let Some((_, buffer, _)) = self
9744 .buffer()
9745 .read(cx)
9746 .excerpt_containing(selection.start, cx)
9747 else {
9748 return;
9749 };
9750 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9751 else {
9752 return;
9753 };
9754 if buffer != end_buffer {
9755 log::error!("expected anchor range to have matching buffer IDs");
9756 return;
9757 }
9758
9759 let id = post_inc(&mut self.next_completion_id);
9760 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9761 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9762 CompletionsMenu::new_snippet_choices(
9763 id,
9764 true,
9765 choices,
9766 selection,
9767 buffer,
9768 snippet_sort_order,
9769 ),
9770 ));
9771 }
9772
9773 pub fn insert_snippet(
9774 &mut self,
9775 insertion_ranges: &[Range<usize>],
9776 snippet: Snippet,
9777 window: &mut Window,
9778 cx: &mut Context<Self>,
9779 ) -> Result<()> {
9780 struct Tabstop<T> {
9781 is_end_tabstop: bool,
9782 ranges: Vec<Range<T>>,
9783 choices: Option<Vec<String>>,
9784 }
9785
9786 let tabstops = self.buffer.update(cx, |buffer, cx| {
9787 let snippet_text: Arc<str> = snippet.text.clone().into();
9788 let edits = insertion_ranges
9789 .iter()
9790 .cloned()
9791 .map(|range| (range, snippet_text.clone()));
9792 let autoindent_mode = AutoindentMode::Block {
9793 original_indent_columns: Vec::new(),
9794 };
9795 buffer.edit(edits, Some(autoindent_mode), cx);
9796
9797 let snapshot = &*buffer.read(cx);
9798 let snippet = &snippet;
9799 snippet
9800 .tabstops
9801 .iter()
9802 .map(|tabstop| {
9803 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9804 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9805 });
9806 let mut tabstop_ranges = tabstop
9807 .ranges
9808 .iter()
9809 .flat_map(|tabstop_range| {
9810 let mut delta = 0_isize;
9811 insertion_ranges.iter().map(move |insertion_range| {
9812 let insertion_start = insertion_range.start as isize + delta;
9813 delta +=
9814 snippet.text.len() as isize - insertion_range.len() as isize;
9815
9816 let start = ((insertion_start + tabstop_range.start) as usize)
9817 .min(snapshot.len());
9818 let end = ((insertion_start + tabstop_range.end) as usize)
9819 .min(snapshot.len());
9820 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9821 })
9822 })
9823 .collect::<Vec<_>>();
9824 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9825
9826 Tabstop {
9827 is_end_tabstop,
9828 ranges: tabstop_ranges,
9829 choices: tabstop.choices.clone(),
9830 }
9831 })
9832 .collect::<Vec<_>>()
9833 });
9834 if let Some(tabstop) = tabstops.first() {
9835 self.change_selections(Default::default(), window, cx, |s| {
9836 // Reverse order so that the first range is the newest created selection.
9837 // Completions will use it and autoscroll will prioritize it.
9838 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9839 });
9840
9841 if let Some(choices) = &tabstop.choices
9842 && let Some(selection) = tabstop.ranges.first()
9843 {
9844 self.show_snippet_choices(choices, selection.clone(), cx)
9845 }
9846
9847 // If we're already at the last tabstop and it's at the end of the snippet,
9848 // we're done, we don't need to keep the state around.
9849 if !tabstop.is_end_tabstop {
9850 let choices = tabstops
9851 .iter()
9852 .map(|tabstop| tabstop.choices.clone())
9853 .collect();
9854
9855 let ranges = tabstops
9856 .into_iter()
9857 .map(|tabstop| tabstop.ranges)
9858 .collect::<Vec<_>>();
9859
9860 self.snippet_stack.push(SnippetState {
9861 active_index: 0,
9862 ranges,
9863 choices,
9864 });
9865 }
9866
9867 // Check whether the just-entered snippet ends with an auto-closable bracket.
9868 if self.autoclose_regions.is_empty() {
9869 let snapshot = self.buffer.read(cx).snapshot(cx);
9870 let mut all_selections = self.selections.all::<Point>(cx);
9871 for selection in &mut all_selections {
9872 let selection_head = selection.head();
9873 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9874 continue;
9875 };
9876
9877 let mut bracket_pair = None;
9878 let max_lookup_length = scope
9879 .brackets()
9880 .map(|(pair, _)| {
9881 pair.start
9882 .as_str()
9883 .chars()
9884 .count()
9885 .max(pair.end.as_str().chars().count())
9886 })
9887 .max();
9888 if let Some(max_lookup_length) = max_lookup_length {
9889 let next_text = snapshot
9890 .chars_at(selection_head)
9891 .take(max_lookup_length)
9892 .collect::<String>();
9893 let prev_text = snapshot
9894 .reversed_chars_at(selection_head)
9895 .take(max_lookup_length)
9896 .collect::<String>();
9897
9898 for (pair, enabled) in scope.brackets() {
9899 if enabled
9900 && pair.close
9901 && prev_text.starts_with(pair.start.as_str())
9902 && next_text.starts_with(pair.end.as_str())
9903 {
9904 bracket_pair = Some(pair.clone());
9905 break;
9906 }
9907 }
9908 }
9909
9910 if let Some(pair) = bracket_pair {
9911 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9912 let autoclose_enabled =
9913 self.use_autoclose && snapshot_settings.use_autoclose;
9914 if autoclose_enabled {
9915 let start = snapshot.anchor_after(selection_head);
9916 let end = snapshot.anchor_after(selection_head);
9917 self.autoclose_regions.push(AutocloseRegion {
9918 selection_id: selection.id,
9919 range: start..end,
9920 pair,
9921 });
9922 }
9923 }
9924 }
9925 }
9926 }
9927 Ok(())
9928 }
9929
9930 pub fn move_to_next_snippet_tabstop(
9931 &mut self,
9932 window: &mut Window,
9933 cx: &mut Context<Self>,
9934 ) -> bool {
9935 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9936 }
9937
9938 pub fn move_to_prev_snippet_tabstop(
9939 &mut self,
9940 window: &mut Window,
9941 cx: &mut Context<Self>,
9942 ) -> bool {
9943 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9944 }
9945
9946 pub fn move_to_snippet_tabstop(
9947 &mut self,
9948 bias: Bias,
9949 window: &mut Window,
9950 cx: &mut Context<Self>,
9951 ) -> bool {
9952 if let Some(mut snippet) = self.snippet_stack.pop() {
9953 match bias {
9954 Bias::Left => {
9955 if snippet.active_index > 0 {
9956 snippet.active_index -= 1;
9957 } else {
9958 self.snippet_stack.push(snippet);
9959 return false;
9960 }
9961 }
9962 Bias::Right => {
9963 if snippet.active_index + 1 < snippet.ranges.len() {
9964 snippet.active_index += 1;
9965 } else {
9966 self.snippet_stack.push(snippet);
9967 return false;
9968 }
9969 }
9970 }
9971 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9972 self.change_selections(Default::default(), window, cx, |s| {
9973 // Reverse order so that the first range is the newest created selection.
9974 // Completions will use it and autoscroll will prioritize it.
9975 s.select_ranges(current_ranges.iter().rev().cloned())
9976 });
9977
9978 if let Some(choices) = &snippet.choices[snippet.active_index]
9979 && let Some(selection) = current_ranges.first()
9980 {
9981 self.show_snippet_choices(choices, selection.clone(), cx);
9982 }
9983
9984 // If snippet state is not at the last tabstop, push it back on the stack
9985 if snippet.active_index + 1 < snippet.ranges.len() {
9986 self.snippet_stack.push(snippet);
9987 }
9988 return true;
9989 }
9990 }
9991
9992 false
9993 }
9994
9995 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9996 self.transact(window, cx, |this, window, cx| {
9997 this.select_all(&SelectAll, window, cx);
9998 this.insert("", window, cx);
9999 });
10000 }
10001
10002 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10003 if self.read_only(cx) {
10004 return;
10005 }
10006 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10007 self.transact(window, cx, |this, window, cx| {
10008 this.select_autoclose_pair(window, cx);
10009 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10010 if !this.linked_edit_ranges.is_empty() {
10011 let selections = this.selections.all::<MultiBufferPoint>(cx);
10012 let snapshot = this.buffer.read(cx).snapshot(cx);
10013
10014 for selection in selections.iter() {
10015 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10016 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10017 if selection_start.buffer_id != selection_end.buffer_id {
10018 continue;
10019 }
10020 if let Some(ranges) =
10021 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10022 {
10023 for (buffer, entries) in ranges {
10024 linked_ranges.entry(buffer).or_default().extend(entries);
10025 }
10026 }
10027 }
10028 }
10029
10030 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
10031 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10032 for selection in &mut selections {
10033 if selection.is_empty() {
10034 let old_head = selection.head();
10035 let mut new_head =
10036 movement::left(&display_map, old_head.to_display_point(&display_map))
10037 .to_point(&display_map);
10038 if let Some((buffer, line_buffer_range)) = display_map
10039 .buffer_snapshot()
10040 .buffer_line_for_row(MultiBufferRow(old_head.row))
10041 {
10042 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10043 let indent_len = match indent_size.kind {
10044 IndentKind::Space => {
10045 buffer.settings_at(line_buffer_range.start, cx).tab_size
10046 }
10047 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10048 };
10049 if old_head.column <= indent_size.len && old_head.column > 0 {
10050 let indent_len = indent_len.get();
10051 new_head = cmp::min(
10052 new_head,
10053 MultiBufferPoint::new(
10054 old_head.row,
10055 ((old_head.column - 1) / indent_len) * indent_len,
10056 ),
10057 );
10058 }
10059 }
10060
10061 selection.set_head(new_head, SelectionGoal::None);
10062 }
10063 }
10064
10065 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10066 this.insert("", window, cx);
10067 let empty_str: Arc<str> = Arc::from("");
10068 for (buffer, edits) in linked_ranges {
10069 let snapshot = buffer.read(cx).snapshot();
10070 use text::ToPoint as TP;
10071
10072 let edits = edits
10073 .into_iter()
10074 .map(|range| {
10075 let end_point = TP::to_point(&range.end, &snapshot);
10076 let mut start_point = TP::to_point(&range.start, &snapshot);
10077
10078 if end_point == start_point {
10079 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10080 .saturating_sub(1);
10081 start_point =
10082 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10083 };
10084
10085 (start_point..end_point, empty_str.clone())
10086 })
10087 .sorted_by_key(|(range, _)| range.start)
10088 .collect::<Vec<_>>();
10089 buffer.update(cx, |this, cx| {
10090 this.edit(edits, None, cx);
10091 })
10092 }
10093 this.refresh_edit_prediction(true, false, window, cx);
10094 refresh_linked_ranges(this, window, cx);
10095 });
10096 }
10097
10098 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10099 if self.read_only(cx) {
10100 return;
10101 }
10102 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10103 self.transact(window, cx, |this, window, cx| {
10104 this.change_selections(Default::default(), window, cx, |s| {
10105 s.move_with(|map, selection| {
10106 if selection.is_empty() {
10107 let cursor = movement::right(map, selection.head());
10108 selection.end = cursor;
10109 selection.reversed = true;
10110 selection.goal = SelectionGoal::None;
10111 }
10112 })
10113 });
10114 this.insert("", window, cx);
10115 this.refresh_edit_prediction(true, false, window, cx);
10116 });
10117 }
10118
10119 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10120 if self.mode.is_single_line() {
10121 cx.propagate();
10122 return;
10123 }
10124
10125 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10126 if self.move_to_prev_snippet_tabstop(window, cx) {
10127 return;
10128 }
10129 self.outdent(&Outdent, window, cx);
10130 }
10131
10132 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10133 if self.mode.is_single_line() {
10134 cx.propagate();
10135 return;
10136 }
10137
10138 if self.move_to_next_snippet_tabstop(window, cx) {
10139 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10140 return;
10141 }
10142 if self.read_only(cx) {
10143 return;
10144 }
10145 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10146 let mut selections = self.selections.all_adjusted(cx);
10147 let buffer = self.buffer.read(cx);
10148 let snapshot = buffer.snapshot(cx);
10149 let rows_iter = selections.iter().map(|s| s.head().row);
10150 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10151
10152 let has_some_cursor_in_whitespace = selections
10153 .iter()
10154 .filter(|selection| selection.is_empty())
10155 .any(|selection| {
10156 let cursor = selection.head();
10157 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10158 cursor.column < current_indent.len
10159 });
10160
10161 let mut edits = Vec::new();
10162 let mut prev_edited_row = 0;
10163 let mut row_delta = 0;
10164 for selection in &mut selections {
10165 if selection.start.row != prev_edited_row {
10166 row_delta = 0;
10167 }
10168 prev_edited_row = selection.end.row;
10169
10170 // If the selection is non-empty, then increase the indentation of the selected lines.
10171 if !selection.is_empty() {
10172 row_delta =
10173 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10174 continue;
10175 }
10176
10177 let cursor = selection.head();
10178 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10179 if let Some(suggested_indent) =
10180 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10181 {
10182 // Don't do anything if already at suggested indent
10183 // and there is any other cursor which is not
10184 if has_some_cursor_in_whitespace
10185 && cursor.column == current_indent.len
10186 && current_indent.len == suggested_indent.len
10187 {
10188 continue;
10189 }
10190
10191 // Adjust line and move cursor to suggested indent
10192 // if cursor is not at suggested indent
10193 if cursor.column < suggested_indent.len
10194 && cursor.column <= current_indent.len
10195 && current_indent.len <= suggested_indent.len
10196 {
10197 selection.start = Point::new(cursor.row, suggested_indent.len);
10198 selection.end = selection.start;
10199 if row_delta == 0 {
10200 edits.extend(Buffer::edit_for_indent_size_adjustment(
10201 cursor.row,
10202 current_indent,
10203 suggested_indent,
10204 ));
10205 row_delta = suggested_indent.len - current_indent.len;
10206 }
10207 continue;
10208 }
10209
10210 // If current indent is more than suggested indent
10211 // only move cursor to current indent and skip indent
10212 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10213 selection.start = Point::new(cursor.row, current_indent.len);
10214 selection.end = selection.start;
10215 continue;
10216 }
10217 }
10218
10219 // Otherwise, insert a hard or soft tab.
10220 let settings = buffer.language_settings_at(cursor, cx);
10221 let tab_size = if settings.hard_tabs {
10222 IndentSize::tab()
10223 } else {
10224 let tab_size = settings.tab_size.get();
10225 let indent_remainder = snapshot
10226 .text_for_range(Point::new(cursor.row, 0)..cursor)
10227 .flat_map(str::chars)
10228 .fold(row_delta % tab_size, |counter: u32, c| {
10229 if c == '\t' {
10230 0
10231 } else {
10232 (counter + 1) % tab_size
10233 }
10234 });
10235
10236 let chars_to_next_tab_stop = tab_size - indent_remainder;
10237 IndentSize::spaces(chars_to_next_tab_stop)
10238 };
10239 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10240 selection.end = selection.start;
10241 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10242 row_delta += tab_size.len;
10243 }
10244
10245 self.transact(window, cx, |this, window, cx| {
10246 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10247 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10248 this.refresh_edit_prediction(true, false, window, cx);
10249 });
10250 }
10251
10252 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10253 if self.read_only(cx) {
10254 return;
10255 }
10256 if self.mode.is_single_line() {
10257 cx.propagate();
10258 return;
10259 }
10260
10261 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10262 let mut selections = self.selections.all::<Point>(cx);
10263 let mut prev_edited_row = 0;
10264 let mut row_delta = 0;
10265 let mut edits = Vec::new();
10266 let buffer = self.buffer.read(cx);
10267 let snapshot = buffer.snapshot(cx);
10268 for selection in &mut selections {
10269 if selection.start.row != prev_edited_row {
10270 row_delta = 0;
10271 }
10272 prev_edited_row = selection.end.row;
10273
10274 row_delta =
10275 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10276 }
10277
10278 self.transact(window, cx, |this, window, cx| {
10279 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10280 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10281 });
10282 }
10283
10284 fn indent_selection(
10285 buffer: &MultiBuffer,
10286 snapshot: &MultiBufferSnapshot,
10287 selection: &mut Selection<Point>,
10288 edits: &mut Vec<(Range<Point>, String)>,
10289 delta_for_start_row: u32,
10290 cx: &App,
10291 ) -> u32 {
10292 let settings = buffer.language_settings_at(selection.start, cx);
10293 let tab_size = settings.tab_size.get();
10294 let indent_kind = if settings.hard_tabs {
10295 IndentKind::Tab
10296 } else {
10297 IndentKind::Space
10298 };
10299 let mut start_row = selection.start.row;
10300 let mut end_row = selection.end.row + 1;
10301
10302 // If a selection ends at the beginning of a line, don't indent
10303 // that last line.
10304 if selection.end.column == 0 && selection.end.row > selection.start.row {
10305 end_row -= 1;
10306 }
10307
10308 // Avoid re-indenting a row that has already been indented by a
10309 // previous selection, but still update this selection's column
10310 // to reflect that indentation.
10311 if delta_for_start_row > 0 {
10312 start_row += 1;
10313 selection.start.column += delta_for_start_row;
10314 if selection.end.row == selection.start.row {
10315 selection.end.column += delta_for_start_row;
10316 }
10317 }
10318
10319 let mut delta_for_end_row = 0;
10320 let has_multiple_rows = start_row + 1 != end_row;
10321 for row in start_row..end_row {
10322 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10323 let indent_delta = match (current_indent.kind, indent_kind) {
10324 (IndentKind::Space, IndentKind::Space) => {
10325 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10326 IndentSize::spaces(columns_to_next_tab_stop)
10327 }
10328 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10329 (_, IndentKind::Tab) => IndentSize::tab(),
10330 };
10331
10332 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10333 0
10334 } else {
10335 selection.start.column
10336 };
10337 let row_start = Point::new(row, start);
10338 edits.push((
10339 row_start..row_start,
10340 indent_delta.chars().collect::<String>(),
10341 ));
10342
10343 // Update this selection's endpoints to reflect the indentation.
10344 if row == selection.start.row {
10345 selection.start.column += indent_delta.len;
10346 }
10347 if row == selection.end.row {
10348 selection.end.column += indent_delta.len;
10349 delta_for_end_row = indent_delta.len;
10350 }
10351 }
10352
10353 if selection.start.row == selection.end.row {
10354 delta_for_start_row + delta_for_end_row
10355 } else {
10356 delta_for_end_row
10357 }
10358 }
10359
10360 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10361 if self.read_only(cx) {
10362 return;
10363 }
10364 if self.mode.is_single_line() {
10365 cx.propagate();
10366 return;
10367 }
10368
10369 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10370 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10371 let selections = self.selections.all::<Point>(cx);
10372 let mut deletion_ranges = Vec::new();
10373 let mut last_outdent = None;
10374 {
10375 let buffer = self.buffer.read(cx);
10376 let snapshot = buffer.snapshot(cx);
10377 for selection in &selections {
10378 let settings = buffer.language_settings_at(selection.start, cx);
10379 let tab_size = settings.tab_size.get();
10380 let mut rows = selection.spanned_rows(false, &display_map);
10381
10382 // Avoid re-outdenting a row that has already been outdented by a
10383 // previous selection.
10384 if let Some(last_row) = last_outdent
10385 && last_row == rows.start
10386 {
10387 rows.start = rows.start.next_row();
10388 }
10389 let has_multiple_rows = rows.len() > 1;
10390 for row in rows.iter_rows() {
10391 let indent_size = snapshot.indent_size_for_line(row);
10392 if indent_size.len > 0 {
10393 let deletion_len = match indent_size.kind {
10394 IndentKind::Space => {
10395 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10396 if columns_to_prev_tab_stop == 0 {
10397 tab_size
10398 } else {
10399 columns_to_prev_tab_stop
10400 }
10401 }
10402 IndentKind::Tab => 1,
10403 };
10404 let start = if has_multiple_rows
10405 || deletion_len > selection.start.column
10406 || indent_size.len < selection.start.column
10407 {
10408 0
10409 } else {
10410 selection.start.column - deletion_len
10411 };
10412 deletion_ranges.push(
10413 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10414 );
10415 last_outdent = Some(row);
10416 }
10417 }
10418 }
10419 }
10420
10421 self.transact(window, cx, |this, window, cx| {
10422 this.buffer.update(cx, |buffer, cx| {
10423 let empty_str: Arc<str> = Arc::default();
10424 buffer.edit(
10425 deletion_ranges
10426 .into_iter()
10427 .map(|range| (range, empty_str.clone())),
10428 None,
10429 cx,
10430 );
10431 });
10432 let selections = this.selections.all::<usize>(cx);
10433 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10434 });
10435 }
10436
10437 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10438 if self.read_only(cx) {
10439 return;
10440 }
10441 if self.mode.is_single_line() {
10442 cx.propagate();
10443 return;
10444 }
10445
10446 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10447 let selections = self
10448 .selections
10449 .all::<usize>(cx)
10450 .into_iter()
10451 .map(|s| s.range());
10452
10453 self.transact(window, cx, |this, window, cx| {
10454 this.buffer.update(cx, |buffer, cx| {
10455 buffer.autoindent_ranges(selections, cx);
10456 });
10457 let selections = this.selections.all::<usize>(cx);
10458 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10459 });
10460 }
10461
10462 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10463 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10464 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10465 let selections = self.selections.all::<Point>(cx);
10466
10467 let mut new_cursors = Vec::new();
10468 let mut edit_ranges = Vec::new();
10469 let mut selections = selections.iter().peekable();
10470 while let Some(selection) = selections.next() {
10471 let mut rows = selection.spanned_rows(false, &display_map);
10472
10473 // Accumulate contiguous regions of rows that we want to delete.
10474 while let Some(next_selection) = selections.peek() {
10475 let next_rows = next_selection.spanned_rows(false, &display_map);
10476 if next_rows.start <= rows.end {
10477 rows.end = next_rows.end;
10478 selections.next().unwrap();
10479 } else {
10480 break;
10481 }
10482 }
10483
10484 let buffer = display_map.buffer_snapshot();
10485 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10486 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10487 // If there's a line after the range, delete the \n from the end of the row range
10488 (
10489 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10490 rows.end,
10491 )
10492 } else {
10493 // If there isn't a line after the range, delete the \n from the line before the
10494 // start of the row range
10495 edit_start = edit_start.saturating_sub(1);
10496 (buffer.len(), rows.start.previous_row())
10497 };
10498
10499 let text_layout_details = self.text_layout_details(window);
10500 let x = display_map.x_for_display_point(
10501 selection.head().to_display_point(&display_map),
10502 &text_layout_details,
10503 );
10504 let row = Point::new(target_row.0, 0)
10505 .to_display_point(&display_map)
10506 .row();
10507 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10508
10509 new_cursors.push((
10510 selection.id,
10511 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10512 SelectionGoal::None,
10513 ));
10514 edit_ranges.push(edit_start..edit_end);
10515 }
10516
10517 self.transact(window, cx, |this, window, cx| {
10518 let buffer = this.buffer.update(cx, |buffer, cx| {
10519 let empty_str: Arc<str> = Arc::default();
10520 buffer.edit(
10521 edit_ranges
10522 .into_iter()
10523 .map(|range| (range, empty_str.clone())),
10524 None,
10525 cx,
10526 );
10527 buffer.snapshot(cx)
10528 });
10529 let new_selections = new_cursors
10530 .into_iter()
10531 .map(|(id, cursor, goal)| {
10532 let cursor = cursor.to_point(&buffer);
10533 Selection {
10534 id,
10535 start: cursor,
10536 end: cursor,
10537 reversed: false,
10538 goal,
10539 }
10540 })
10541 .collect();
10542
10543 this.change_selections(Default::default(), window, cx, |s| {
10544 s.select(new_selections);
10545 });
10546 });
10547 }
10548
10549 pub fn join_lines_impl(
10550 &mut self,
10551 insert_whitespace: bool,
10552 window: &mut Window,
10553 cx: &mut Context<Self>,
10554 ) {
10555 if self.read_only(cx) {
10556 return;
10557 }
10558 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10559 for selection in self.selections.all::<Point>(cx) {
10560 let start = MultiBufferRow(selection.start.row);
10561 // Treat single line selections as if they include the next line. Otherwise this action
10562 // would do nothing for single line selections individual cursors.
10563 let end = if selection.start.row == selection.end.row {
10564 MultiBufferRow(selection.start.row + 1)
10565 } else {
10566 MultiBufferRow(selection.end.row)
10567 };
10568
10569 if let Some(last_row_range) = row_ranges.last_mut()
10570 && start <= last_row_range.end
10571 {
10572 last_row_range.end = end;
10573 continue;
10574 }
10575 row_ranges.push(start..end);
10576 }
10577
10578 let snapshot = self.buffer.read(cx).snapshot(cx);
10579 let mut cursor_positions = Vec::new();
10580 for row_range in &row_ranges {
10581 let anchor = snapshot.anchor_before(Point::new(
10582 row_range.end.previous_row().0,
10583 snapshot.line_len(row_range.end.previous_row()),
10584 ));
10585 cursor_positions.push(anchor..anchor);
10586 }
10587
10588 self.transact(window, cx, |this, window, cx| {
10589 for row_range in row_ranges.into_iter().rev() {
10590 for row in row_range.iter_rows().rev() {
10591 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10592 let next_line_row = row.next_row();
10593 let indent = snapshot.indent_size_for_line(next_line_row);
10594 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10595
10596 let replace =
10597 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10598 " "
10599 } else {
10600 ""
10601 };
10602
10603 this.buffer.update(cx, |buffer, cx| {
10604 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10605 });
10606 }
10607 }
10608
10609 this.change_selections(Default::default(), window, cx, |s| {
10610 s.select_anchor_ranges(cursor_positions)
10611 });
10612 });
10613 }
10614
10615 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10616 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10617 self.join_lines_impl(true, window, cx);
10618 }
10619
10620 pub fn sort_lines_case_sensitive(
10621 &mut self,
10622 _: &SortLinesCaseSensitive,
10623 window: &mut Window,
10624 cx: &mut Context<Self>,
10625 ) {
10626 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10627 }
10628
10629 pub fn sort_lines_by_length(
10630 &mut self,
10631 _: &SortLinesByLength,
10632 window: &mut Window,
10633 cx: &mut Context<Self>,
10634 ) {
10635 self.manipulate_immutable_lines(window, cx, |lines| {
10636 lines.sort_by_key(|&line| line.chars().count())
10637 })
10638 }
10639
10640 pub fn sort_lines_case_insensitive(
10641 &mut self,
10642 _: &SortLinesCaseInsensitive,
10643 window: &mut Window,
10644 cx: &mut Context<Self>,
10645 ) {
10646 self.manipulate_immutable_lines(window, cx, |lines| {
10647 lines.sort_by_key(|line| line.to_lowercase())
10648 })
10649 }
10650
10651 pub fn unique_lines_case_insensitive(
10652 &mut self,
10653 _: &UniqueLinesCaseInsensitive,
10654 window: &mut Window,
10655 cx: &mut Context<Self>,
10656 ) {
10657 self.manipulate_immutable_lines(window, cx, |lines| {
10658 let mut seen = HashSet::default();
10659 lines.retain(|line| seen.insert(line.to_lowercase()));
10660 })
10661 }
10662
10663 pub fn unique_lines_case_sensitive(
10664 &mut self,
10665 _: &UniqueLinesCaseSensitive,
10666 window: &mut Window,
10667 cx: &mut Context<Self>,
10668 ) {
10669 self.manipulate_immutable_lines(window, cx, |lines| {
10670 let mut seen = HashSet::default();
10671 lines.retain(|line| seen.insert(*line));
10672 })
10673 }
10674
10675 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10676 let snapshot = self.buffer.read(cx).snapshot(cx);
10677 for selection in self.selections.disjoint_anchors_arc().iter() {
10678 if snapshot
10679 .language_at(selection.start)
10680 .and_then(|lang| lang.config().wrap_characters.as_ref())
10681 .is_some()
10682 {
10683 return true;
10684 }
10685 }
10686 false
10687 }
10688
10689 fn wrap_selections_in_tag(
10690 &mut self,
10691 _: &WrapSelectionsInTag,
10692 window: &mut Window,
10693 cx: &mut Context<Self>,
10694 ) {
10695 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10696
10697 let snapshot = self.buffer.read(cx).snapshot(cx);
10698
10699 let mut edits = Vec::new();
10700 let mut boundaries = Vec::new();
10701
10702 for selection in self.selections.all::<Point>(cx).iter() {
10703 let Some(wrap_config) = snapshot
10704 .language_at(selection.start)
10705 .and_then(|lang| lang.config().wrap_characters.clone())
10706 else {
10707 continue;
10708 };
10709
10710 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10711 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10712
10713 let start_before = snapshot.anchor_before(selection.start);
10714 let end_after = snapshot.anchor_after(selection.end);
10715
10716 edits.push((start_before..start_before, open_tag));
10717 edits.push((end_after..end_after, close_tag));
10718
10719 boundaries.push((
10720 start_before,
10721 end_after,
10722 wrap_config.start_prefix.len(),
10723 wrap_config.end_suffix.len(),
10724 ));
10725 }
10726
10727 if edits.is_empty() {
10728 return;
10729 }
10730
10731 self.transact(window, cx, |this, window, cx| {
10732 let buffer = this.buffer.update(cx, |buffer, cx| {
10733 buffer.edit(edits, None, cx);
10734 buffer.snapshot(cx)
10735 });
10736
10737 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10738 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10739 boundaries.into_iter()
10740 {
10741 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10742 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10743 new_selections.push(open_offset..open_offset);
10744 new_selections.push(close_offset..close_offset);
10745 }
10746
10747 this.change_selections(Default::default(), window, cx, |s| {
10748 s.select_ranges(new_selections);
10749 });
10750
10751 this.request_autoscroll(Autoscroll::fit(), cx);
10752 });
10753 }
10754
10755 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10756 let Some(project) = self.project.clone() else {
10757 return;
10758 };
10759 self.reload(project, window, cx)
10760 .detach_and_notify_err(window, cx);
10761 }
10762
10763 pub fn restore_file(
10764 &mut self,
10765 _: &::git::RestoreFile,
10766 window: &mut Window,
10767 cx: &mut Context<Self>,
10768 ) {
10769 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10770 let mut buffer_ids = HashSet::default();
10771 let snapshot = self.buffer().read(cx).snapshot(cx);
10772 for selection in self.selections.all::<usize>(cx) {
10773 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10774 }
10775
10776 let buffer = self.buffer().read(cx);
10777 let ranges = buffer_ids
10778 .into_iter()
10779 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10780 .collect::<Vec<_>>();
10781
10782 self.restore_hunks_in_ranges(ranges, window, cx);
10783 }
10784
10785 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10786 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10787 let selections = self
10788 .selections
10789 .all(cx)
10790 .into_iter()
10791 .map(|s| s.range())
10792 .collect();
10793 self.restore_hunks_in_ranges(selections, window, cx);
10794 }
10795
10796 pub fn restore_hunks_in_ranges(
10797 &mut self,
10798 ranges: Vec<Range<Point>>,
10799 window: &mut Window,
10800 cx: &mut Context<Editor>,
10801 ) {
10802 let mut revert_changes = HashMap::default();
10803 let chunk_by = self
10804 .snapshot(window, cx)
10805 .hunks_for_ranges(ranges)
10806 .into_iter()
10807 .chunk_by(|hunk| hunk.buffer_id);
10808 for (buffer_id, hunks) in &chunk_by {
10809 let hunks = hunks.collect::<Vec<_>>();
10810 for hunk in &hunks {
10811 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10812 }
10813 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10814 }
10815 drop(chunk_by);
10816 if !revert_changes.is_empty() {
10817 self.transact(window, cx, |editor, window, cx| {
10818 editor.restore(revert_changes, window, cx);
10819 });
10820 }
10821 }
10822
10823 pub fn open_active_item_in_terminal(
10824 &mut self,
10825 _: &OpenInTerminal,
10826 window: &mut Window,
10827 cx: &mut Context<Self>,
10828 ) {
10829 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10830 let project_path = buffer.read(cx).project_path(cx)?;
10831 let project = self.project()?.read(cx);
10832 let entry = project.entry_for_path(&project_path, cx)?;
10833 let parent = match &entry.canonical_path {
10834 Some(canonical_path) => canonical_path.to_path_buf(),
10835 None => project.absolute_path(&project_path, cx)?,
10836 }
10837 .parent()?
10838 .to_path_buf();
10839 Some(parent)
10840 }) {
10841 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10842 }
10843 }
10844
10845 fn set_breakpoint_context_menu(
10846 &mut self,
10847 display_row: DisplayRow,
10848 position: Option<Anchor>,
10849 clicked_point: gpui::Point<Pixels>,
10850 window: &mut Window,
10851 cx: &mut Context<Self>,
10852 ) {
10853 let source = self
10854 .buffer
10855 .read(cx)
10856 .snapshot(cx)
10857 .anchor_before(Point::new(display_row.0, 0u32));
10858
10859 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10860
10861 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10862 self,
10863 source,
10864 clicked_point,
10865 context_menu,
10866 window,
10867 cx,
10868 );
10869 }
10870
10871 fn add_edit_breakpoint_block(
10872 &mut self,
10873 anchor: Anchor,
10874 breakpoint: &Breakpoint,
10875 edit_action: BreakpointPromptEditAction,
10876 window: &mut Window,
10877 cx: &mut Context<Self>,
10878 ) {
10879 let weak_editor = cx.weak_entity();
10880 let bp_prompt = cx.new(|cx| {
10881 BreakpointPromptEditor::new(
10882 weak_editor,
10883 anchor,
10884 breakpoint.clone(),
10885 edit_action,
10886 window,
10887 cx,
10888 )
10889 });
10890
10891 let height = bp_prompt.update(cx, |this, cx| {
10892 this.prompt
10893 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10894 });
10895 let cloned_prompt = bp_prompt.clone();
10896 let blocks = vec![BlockProperties {
10897 style: BlockStyle::Sticky,
10898 placement: BlockPlacement::Above(anchor),
10899 height: Some(height),
10900 render: Arc::new(move |cx| {
10901 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10902 cloned_prompt.clone().into_any_element()
10903 }),
10904 priority: 0,
10905 }];
10906
10907 let focus_handle = bp_prompt.focus_handle(cx);
10908 window.focus(&focus_handle);
10909
10910 let block_ids = self.insert_blocks(blocks, None, cx);
10911 bp_prompt.update(cx, |prompt, _| {
10912 prompt.add_block_ids(block_ids);
10913 });
10914 }
10915
10916 pub(crate) fn breakpoint_at_row(
10917 &self,
10918 row: u32,
10919 window: &mut Window,
10920 cx: &mut Context<Self>,
10921 ) -> Option<(Anchor, Breakpoint)> {
10922 let snapshot = self.snapshot(window, cx);
10923 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
10924
10925 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10926 }
10927
10928 pub(crate) fn breakpoint_at_anchor(
10929 &self,
10930 breakpoint_position: Anchor,
10931 snapshot: &EditorSnapshot,
10932 cx: &mut Context<Self>,
10933 ) -> Option<(Anchor, Breakpoint)> {
10934 let buffer = self
10935 .buffer
10936 .read(cx)
10937 .buffer_for_anchor(breakpoint_position, cx)?;
10938
10939 let enclosing_excerpt = breakpoint_position.excerpt_id;
10940 let buffer_snapshot = buffer.read(cx).snapshot();
10941
10942 let row = buffer_snapshot
10943 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10944 .row;
10945
10946 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
10947 let anchor_end = snapshot
10948 .buffer_snapshot()
10949 .anchor_after(Point::new(row, line_len));
10950
10951 self.breakpoint_store
10952 .as_ref()?
10953 .read_with(cx, |breakpoint_store, cx| {
10954 breakpoint_store
10955 .breakpoints(
10956 &buffer,
10957 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10958 &buffer_snapshot,
10959 cx,
10960 )
10961 .next()
10962 .and_then(|(bp, _)| {
10963 let breakpoint_row = buffer_snapshot
10964 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10965 .row;
10966
10967 if breakpoint_row == row {
10968 snapshot
10969 .buffer_snapshot()
10970 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10971 .map(|position| (position, bp.bp.clone()))
10972 } else {
10973 None
10974 }
10975 })
10976 })
10977 }
10978
10979 pub fn edit_log_breakpoint(
10980 &mut self,
10981 _: &EditLogBreakpoint,
10982 window: &mut Window,
10983 cx: &mut Context<Self>,
10984 ) {
10985 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10986 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10987 message: None,
10988 state: BreakpointState::Enabled,
10989 condition: None,
10990 hit_condition: None,
10991 });
10992
10993 self.add_edit_breakpoint_block(
10994 anchor,
10995 &breakpoint,
10996 BreakpointPromptEditAction::Log,
10997 window,
10998 cx,
10999 );
11000 }
11001 }
11002
11003 fn breakpoints_at_cursors(
11004 &self,
11005 window: &mut Window,
11006 cx: &mut Context<Self>,
11007 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11008 let snapshot = self.snapshot(window, cx);
11009 let cursors = self
11010 .selections
11011 .disjoint_anchors_arc()
11012 .iter()
11013 .map(|selection| {
11014 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11015
11016 let breakpoint_position = self
11017 .breakpoint_at_row(cursor_position.row, window, cx)
11018 .map(|bp| bp.0)
11019 .unwrap_or_else(|| {
11020 snapshot
11021 .display_snapshot
11022 .buffer_snapshot()
11023 .anchor_after(Point::new(cursor_position.row, 0))
11024 });
11025
11026 let breakpoint = self
11027 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11028 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11029
11030 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11031 })
11032 // 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.
11033 .collect::<HashMap<Anchor, _>>();
11034
11035 cursors.into_iter().collect()
11036 }
11037
11038 pub fn enable_breakpoint(
11039 &mut self,
11040 _: &crate::actions::EnableBreakpoint,
11041 window: &mut Window,
11042 cx: &mut Context<Self>,
11043 ) {
11044 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11045 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11046 continue;
11047 };
11048 self.edit_breakpoint_at_anchor(
11049 anchor,
11050 breakpoint,
11051 BreakpointEditAction::InvertState,
11052 cx,
11053 );
11054 }
11055 }
11056
11057 pub fn disable_breakpoint(
11058 &mut self,
11059 _: &crate::actions::DisableBreakpoint,
11060 window: &mut Window,
11061 cx: &mut Context<Self>,
11062 ) {
11063 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11064 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11065 continue;
11066 };
11067 self.edit_breakpoint_at_anchor(
11068 anchor,
11069 breakpoint,
11070 BreakpointEditAction::InvertState,
11071 cx,
11072 );
11073 }
11074 }
11075
11076 pub fn toggle_breakpoint(
11077 &mut self,
11078 _: &crate::actions::ToggleBreakpoint,
11079 window: &mut Window,
11080 cx: &mut Context<Self>,
11081 ) {
11082 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11083 if let Some(breakpoint) = breakpoint {
11084 self.edit_breakpoint_at_anchor(
11085 anchor,
11086 breakpoint,
11087 BreakpointEditAction::Toggle,
11088 cx,
11089 );
11090 } else {
11091 self.edit_breakpoint_at_anchor(
11092 anchor,
11093 Breakpoint::new_standard(),
11094 BreakpointEditAction::Toggle,
11095 cx,
11096 );
11097 }
11098 }
11099 }
11100
11101 pub fn edit_breakpoint_at_anchor(
11102 &mut self,
11103 breakpoint_position: Anchor,
11104 breakpoint: Breakpoint,
11105 edit_action: BreakpointEditAction,
11106 cx: &mut Context<Self>,
11107 ) {
11108 let Some(breakpoint_store) = &self.breakpoint_store else {
11109 return;
11110 };
11111
11112 let Some(buffer) = self
11113 .buffer
11114 .read(cx)
11115 .buffer_for_anchor(breakpoint_position, cx)
11116 else {
11117 return;
11118 };
11119
11120 breakpoint_store.update(cx, |breakpoint_store, cx| {
11121 breakpoint_store.toggle_breakpoint(
11122 buffer,
11123 BreakpointWithPosition {
11124 position: breakpoint_position.text_anchor,
11125 bp: breakpoint,
11126 },
11127 edit_action,
11128 cx,
11129 );
11130 });
11131
11132 cx.notify();
11133 }
11134
11135 #[cfg(any(test, feature = "test-support"))]
11136 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11137 self.breakpoint_store.clone()
11138 }
11139
11140 pub fn prepare_restore_change(
11141 &self,
11142 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11143 hunk: &MultiBufferDiffHunk,
11144 cx: &mut App,
11145 ) -> Option<()> {
11146 if hunk.is_created_file() {
11147 return None;
11148 }
11149 let buffer = self.buffer.read(cx);
11150 let diff = buffer.diff_for(hunk.buffer_id)?;
11151 let buffer = buffer.buffer(hunk.buffer_id)?;
11152 let buffer = buffer.read(cx);
11153 let original_text = diff
11154 .read(cx)
11155 .base_text()
11156 .as_rope()
11157 .slice(hunk.diff_base_byte_range.clone());
11158 let buffer_snapshot = buffer.snapshot();
11159 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11160 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11161 probe
11162 .0
11163 .start
11164 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11165 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11166 }) {
11167 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11168 Some(())
11169 } else {
11170 None
11171 }
11172 }
11173
11174 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11175 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11176 }
11177
11178 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11179 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11180 }
11181
11182 fn manipulate_lines<M>(
11183 &mut self,
11184 window: &mut Window,
11185 cx: &mut Context<Self>,
11186 mut manipulate: M,
11187 ) where
11188 M: FnMut(&str) -> LineManipulationResult,
11189 {
11190 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11191
11192 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11193 let buffer = self.buffer.read(cx).snapshot(cx);
11194
11195 let mut edits = Vec::new();
11196
11197 let selections = self.selections.all::<Point>(cx);
11198 let mut selections = selections.iter().peekable();
11199 let mut contiguous_row_selections = Vec::new();
11200 let mut new_selections = Vec::new();
11201 let mut added_lines = 0;
11202 let mut removed_lines = 0;
11203
11204 while let Some(selection) = selections.next() {
11205 let (start_row, end_row) = consume_contiguous_rows(
11206 &mut contiguous_row_selections,
11207 selection,
11208 &display_map,
11209 &mut selections,
11210 );
11211
11212 let start_point = Point::new(start_row.0, 0);
11213 let end_point = Point::new(
11214 end_row.previous_row().0,
11215 buffer.line_len(end_row.previous_row()),
11216 );
11217 let text = buffer
11218 .text_for_range(start_point..end_point)
11219 .collect::<String>();
11220
11221 let LineManipulationResult {
11222 new_text,
11223 line_count_before,
11224 line_count_after,
11225 } = manipulate(&text);
11226
11227 edits.push((start_point..end_point, new_text));
11228
11229 // Selections must change based on added and removed line count
11230 let start_row =
11231 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11232 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11233 new_selections.push(Selection {
11234 id: selection.id,
11235 start: start_row,
11236 end: end_row,
11237 goal: SelectionGoal::None,
11238 reversed: selection.reversed,
11239 });
11240
11241 if line_count_after > line_count_before {
11242 added_lines += line_count_after - line_count_before;
11243 } else if line_count_before > line_count_after {
11244 removed_lines += line_count_before - line_count_after;
11245 }
11246 }
11247
11248 self.transact(window, cx, |this, window, cx| {
11249 let buffer = this.buffer.update(cx, |buffer, cx| {
11250 buffer.edit(edits, None, cx);
11251 buffer.snapshot(cx)
11252 });
11253
11254 // Recalculate offsets on newly edited buffer
11255 let new_selections = new_selections
11256 .iter()
11257 .map(|s| {
11258 let start_point = Point::new(s.start.0, 0);
11259 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11260 Selection {
11261 id: s.id,
11262 start: buffer.point_to_offset(start_point),
11263 end: buffer.point_to_offset(end_point),
11264 goal: s.goal,
11265 reversed: s.reversed,
11266 }
11267 })
11268 .collect();
11269
11270 this.change_selections(Default::default(), window, cx, |s| {
11271 s.select(new_selections);
11272 });
11273
11274 this.request_autoscroll(Autoscroll::fit(), cx);
11275 });
11276 }
11277
11278 fn manipulate_immutable_lines<Fn>(
11279 &mut self,
11280 window: &mut Window,
11281 cx: &mut Context<Self>,
11282 mut callback: Fn,
11283 ) where
11284 Fn: FnMut(&mut Vec<&str>),
11285 {
11286 self.manipulate_lines(window, cx, |text| {
11287 let mut lines: Vec<&str> = text.split('\n').collect();
11288 let line_count_before = lines.len();
11289
11290 callback(&mut lines);
11291
11292 LineManipulationResult {
11293 new_text: lines.join("\n"),
11294 line_count_before,
11295 line_count_after: lines.len(),
11296 }
11297 });
11298 }
11299
11300 fn manipulate_mutable_lines<Fn>(
11301 &mut self,
11302 window: &mut Window,
11303 cx: &mut Context<Self>,
11304 mut callback: Fn,
11305 ) where
11306 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11307 {
11308 self.manipulate_lines(window, cx, |text| {
11309 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11310 let line_count_before = lines.len();
11311
11312 callback(&mut lines);
11313
11314 LineManipulationResult {
11315 new_text: lines.join("\n"),
11316 line_count_before,
11317 line_count_after: lines.len(),
11318 }
11319 });
11320 }
11321
11322 pub fn convert_indentation_to_spaces(
11323 &mut self,
11324 _: &ConvertIndentationToSpaces,
11325 window: &mut Window,
11326 cx: &mut Context<Self>,
11327 ) {
11328 let settings = self.buffer.read(cx).language_settings(cx);
11329 let tab_size = settings.tab_size.get() as usize;
11330
11331 self.manipulate_mutable_lines(window, cx, |lines| {
11332 // Allocates a reasonably sized scratch buffer once for the whole loop
11333 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11334 // Avoids recomputing spaces that could be inserted many times
11335 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11336 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11337 .collect();
11338
11339 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11340 let mut chars = line.as_ref().chars();
11341 let mut col = 0;
11342 let mut changed = false;
11343
11344 for ch in chars.by_ref() {
11345 match ch {
11346 ' ' => {
11347 reindented_line.push(' ');
11348 col += 1;
11349 }
11350 '\t' => {
11351 // \t are converted to spaces depending on the current column
11352 let spaces_len = tab_size - (col % tab_size);
11353 reindented_line.extend(&space_cache[spaces_len - 1]);
11354 col += spaces_len;
11355 changed = true;
11356 }
11357 _ => {
11358 // If we dont append before break, the character is consumed
11359 reindented_line.push(ch);
11360 break;
11361 }
11362 }
11363 }
11364
11365 if !changed {
11366 reindented_line.clear();
11367 continue;
11368 }
11369 // Append the rest of the line and replace old reference with new one
11370 reindented_line.extend(chars);
11371 *line = Cow::Owned(reindented_line.clone());
11372 reindented_line.clear();
11373 }
11374 });
11375 }
11376
11377 pub fn convert_indentation_to_tabs(
11378 &mut self,
11379 _: &ConvertIndentationToTabs,
11380 window: &mut Window,
11381 cx: &mut Context<Self>,
11382 ) {
11383 let settings = self.buffer.read(cx).language_settings(cx);
11384 let tab_size = settings.tab_size.get() as usize;
11385
11386 self.manipulate_mutable_lines(window, cx, |lines| {
11387 // Allocates a reasonably sized buffer once for the whole loop
11388 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11389 // Avoids recomputing spaces that could be inserted many times
11390 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11391 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11392 .collect();
11393
11394 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11395 let mut chars = line.chars();
11396 let mut spaces_count = 0;
11397 let mut first_non_indent_char = None;
11398 let mut changed = false;
11399
11400 for ch in chars.by_ref() {
11401 match ch {
11402 ' ' => {
11403 // Keep track of spaces. Append \t when we reach tab_size
11404 spaces_count += 1;
11405 changed = true;
11406 if spaces_count == tab_size {
11407 reindented_line.push('\t');
11408 spaces_count = 0;
11409 }
11410 }
11411 '\t' => {
11412 reindented_line.push('\t');
11413 spaces_count = 0;
11414 }
11415 _ => {
11416 // Dont append it yet, we might have remaining spaces
11417 first_non_indent_char = Some(ch);
11418 break;
11419 }
11420 }
11421 }
11422
11423 if !changed {
11424 reindented_line.clear();
11425 continue;
11426 }
11427 // Remaining spaces that didn't make a full tab stop
11428 if spaces_count > 0 {
11429 reindented_line.extend(&space_cache[spaces_count - 1]);
11430 }
11431 // If we consume an extra character that was not indentation, add it back
11432 if let Some(extra_char) = first_non_indent_char {
11433 reindented_line.push(extra_char);
11434 }
11435 // Append the rest of the line and replace old reference with new one
11436 reindented_line.extend(chars);
11437 *line = Cow::Owned(reindented_line.clone());
11438 reindented_line.clear();
11439 }
11440 });
11441 }
11442
11443 pub fn convert_to_upper_case(
11444 &mut self,
11445 _: &ConvertToUpperCase,
11446 window: &mut Window,
11447 cx: &mut Context<Self>,
11448 ) {
11449 self.manipulate_text(window, cx, |text| text.to_uppercase())
11450 }
11451
11452 pub fn convert_to_lower_case(
11453 &mut self,
11454 _: &ConvertToLowerCase,
11455 window: &mut Window,
11456 cx: &mut Context<Self>,
11457 ) {
11458 self.manipulate_text(window, cx, |text| text.to_lowercase())
11459 }
11460
11461 pub fn convert_to_title_case(
11462 &mut self,
11463 _: &ConvertToTitleCase,
11464 window: &mut Window,
11465 cx: &mut Context<Self>,
11466 ) {
11467 self.manipulate_text(window, cx, |text| {
11468 text.split('\n')
11469 .map(|line| line.to_case(Case::Title))
11470 .join("\n")
11471 })
11472 }
11473
11474 pub fn convert_to_snake_case(
11475 &mut self,
11476 _: &ConvertToSnakeCase,
11477 window: &mut Window,
11478 cx: &mut Context<Self>,
11479 ) {
11480 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11481 }
11482
11483 pub fn convert_to_kebab_case(
11484 &mut self,
11485 _: &ConvertToKebabCase,
11486 window: &mut Window,
11487 cx: &mut Context<Self>,
11488 ) {
11489 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11490 }
11491
11492 pub fn convert_to_upper_camel_case(
11493 &mut self,
11494 _: &ConvertToUpperCamelCase,
11495 window: &mut Window,
11496 cx: &mut Context<Self>,
11497 ) {
11498 self.manipulate_text(window, cx, |text| {
11499 text.split('\n')
11500 .map(|line| line.to_case(Case::UpperCamel))
11501 .join("\n")
11502 })
11503 }
11504
11505 pub fn convert_to_lower_camel_case(
11506 &mut self,
11507 _: &ConvertToLowerCamelCase,
11508 window: &mut Window,
11509 cx: &mut Context<Self>,
11510 ) {
11511 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11512 }
11513
11514 pub fn convert_to_opposite_case(
11515 &mut self,
11516 _: &ConvertToOppositeCase,
11517 window: &mut Window,
11518 cx: &mut Context<Self>,
11519 ) {
11520 self.manipulate_text(window, cx, |text| {
11521 text.chars()
11522 .fold(String::with_capacity(text.len()), |mut t, c| {
11523 if c.is_uppercase() {
11524 t.extend(c.to_lowercase());
11525 } else {
11526 t.extend(c.to_uppercase());
11527 }
11528 t
11529 })
11530 })
11531 }
11532
11533 pub fn convert_to_sentence_case(
11534 &mut self,
11535 _: &ConvertToSentenceCase,
11536 window: &mut Window,
11537 cx: &mut Context<Self>,
11538 ) {
11539 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11540 }
11541
11542 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11543 self.manipulate_text(window, cx, |text| {
11544 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11545 if has_upper_case_characters {
11546 text.to_lowercase()
11547 } else {
11548 text.to_uppercase()
11549 }
11550 })
11551 }
11552
11553 pub fn convert_to_rot13(
11554 &mut self,
11555 _: &ConvertToRot13,
11556 window: &mut Window,
11557 cx: &mut Context<Self>,
11558 ) {
11559 self.manipulate_text(window, cx, |text| {
11560 text.chars()
11561 .map(|c| match c {
11562 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11563 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11564 _ => c,
11565 })
11566 .collect()
11567 })
11568 }
11569
11570 pub fn convert_to_rot47(
11571 &mut self,
11572 _: &ConvertToRot47,
11573 window: &mut Window,
11574 cx: &mut Context<Self>,
11575 ) {
11576 self.manipulate_text(window, cx, |text| {
11577 text.chars()
11578 .map(|c| {
11579 let code_point = c as u32;
11580 if code_point >= 33 && code_point <= 126 {
11581 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11582 }
11583 c
11584 })
11585 .collect()
11586 })
11587 }
11588
11589 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11590 where
11591 Fn: FnMut(&str) -> String,
11592 {
11593 let buffer = self.buffer.read(cx).snapshot(cx);
11594
11595 let mut new_selections = Vec::new();
11596 let mut edits = Vec::new();
11597 let mut selection_adjustment = 0i32;
11598
11599 for selection in self.selections.all_adjusted(cx) {
11600 let selection_is_empty = selection.is_empty();
11601
11602 let (start, end) = if selection_is_empty {
11603 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11604 (word_range.start, word_range.end)
11605 } else {
11606 (
11607 buffer.point_to_offset(selection.start),
11608 buffer.point_to_offset(selection.end),
11609 )
11610 };
11611
11612 let text = buffer.text_for_range(start..end).collect::<String>();
11613 let old_length = text.len() as i32;
11614 let text = callback(&text);
11615
11616 new_selections.push(Selection {
11617 start: (start as i32 - selection_adjustment) as usize,
11618 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11619 goal: SelectionGoal::None,
11620 id: selection.id,
11621 reversed: selection.reversed,
11622 });
11623
11624 selection_adjustment += old_length - text.len() as i32;
11625
11626 edits.push((start..end, text));
11627 }
11628
11629 self.transact(window, cx, |this, window, cx| {
11630 this.buffer.update(cx, |buffer, cx| {
11631 buffer.edit(edits, None, cx);
11632 });
11633
11634 this.change_selections(Default::default(), window, cx, |s| {
11635 s.select(new_selections);
11636 });
11637
11638 this.request_autoscroll(Autoscroll::fit(), cx);
11639 });
11640 }
11641
11642 pub fn move_selection_on_drop(
11643 &mut self,
11644 selection: &Selection<Anchor>,
11645 target: DisplayPoint,
11646 is_cut: bool,
11647 window: &mut Window,
11648 cx: &mut Context<Self>,
11649 ) {
11650 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11651 let buffer = display_map.buffer_snapshot();
11652 let mut edits = Vec::new();
11653 let insert_point = display_map
11654 .clip_point(target, Bias::Left)
11655 .to_point(&display_map);
11656 let text = buffer
11657 .text_for_range(selection.start..selection.end)
11658 .collect::<String>();
11659 if is_cut {
11660 edits.push(((selection.start..selection.end), String::new()));
11661 }
11662 let insert_anchor = buffer.anchor_before(insert_point);
11663 edits.push(((insert_anchor..insert_anchor), text));
11664 let last_edit_start = insert_anchor.bias_left(buffer);
11665 let last_edit_end = insert_anchor.bias_right(buffer);
11666 self.transact(window, cx, |this, window, cx| {
11667 this.buffer.update(cx, |buffer, cx| {
11668 buffer.edit(edits, None, cx);
11669 });
11670 this.change_selections(Default::default(), window, cx, |s| {
11671 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11672 });
11673 });
11674 }
11675
11676 pub fn clear_selection_drag_state(&mut self) {
11677 self.selection_drag_state = SelectionDragState::None;
11678 }
11679
11680 pub fn duplicate(
11681 &mut self,
11682 upwards: bool,
11683 whole_lines: bool,
11684 window: &mut Window,
11685 cx: &mut Context<Self>,
11686 ) {
11687 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11688
11689 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11690 let buffer = display_map.buffer_snapshot();
11691 let selections = self.selections.all::<Point>(cx);
11692
11693 let mut edits = Vec::new();
11694 let mut selections_iter = selections.iter().peekable();
11695 while let Some(selection) = selections_iter.next() {
11696 let mut rows = selection.spanned_rows(false, &display_map);
11697 // duplicate line-wise
11698 if whole_lines || selection.start == selection.end {
11699 // Avoid duplicating the same lines twice.
11700 while let Some(next_selection) = selections_iter.peek() {
11701 let next_rows = next_selection.spanned_rows(false, &display_map);
11702 if next_rows.start < rows.end {
11703 rows.end = next_rows.end;
11704 selections_iter.next().unwrap();
11705 } else {
11706 break;
11707 }
11708 }
11709
11710 // Copy the text from the selected row region and splice it either at the start
11711 // or end of the region.
11712 let start = Point::new(rows.start.0, 0);
11713 let end = Point::new(
11714 rows.end.previous_row().0,
11715 buffer.line_len(rows.end.previous_row()),
11716 );
11717
11718 let mut text = buffer.text_for_range(start..end).collect::<String>();
11719
11720 let insert_location = if upwards {
11721 // When duplicating upward, we need to insert before the current line.
11722 // If we're on the last line and it doesn't end with a newline,
11723 // we need to add a newline before the duplicated content.
11724 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11725 && buffer.max_point().column > 0
11726 && !text.ends_with('\n');
11727
11728 if needs_leading_newline {
11729 text.insert(0, '\n');
11730 end
11731 } else {
11732 text.push('\n');
11733 Point::new(rows.end.0, 0)
11734 }
11735 } else {
11736 text.push('\n');
11737 start
11738 };
11739 edits.push((insert_location..insert_location, text));
11740 } else {
11741 // duplicate character-wise
11742 let start = selection.start;
11743 let end = selection.end;
11744 let text = buffer.text_for_range(start..end).collect::<String>();
11745 edits.push((selection.end..selection.end, text));
11746 }
11747 }
11748
11749 self.transact(window, cx, |this, _, cx| {
11750 this.buffer.update(cx, |buffer, cx| {
11751 buffer.edit(edits, None, cx);
11752 });
11753
11754 this.request_autoscroll(Autoscroll::fit(), cx);
11755 });
11756 }
11757
11758 pub fn duplicate_line_up(
11759 &mut self,
11760 _: &DuplicateLineUp,
11761 window: &mut Window,
11762 cx: &mut Context<Self>,
11763 ) {
11764 self.duplicate(true, true, window, cx);
11765 }
11766
11767 pub fn duplicate_line_down(
11768 &mut self,
11769 _: &DuplicateLineDown,
11770 window: &mut Window,
11771 cx: &mut Context<Self>,
11772 ) {
11773 self.duplicate(false, true, window, cx);
11774 }
11775
11776 pub fn duplicate_selection(
11777 &mut self,
11778 _: &DuplicateSelection,
11779 window: &mut Window,
11780 cx: &mut Context<Self>,
11781 ) {
11782 self.duplicate(false, false, window, cx);
11783 }
11784
11785 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11786 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11787 if self.mode.is_single_line() {
11788 cx.propagate();
11789 return;
11790 }
11791
11792 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11793 let buffer = self.buffer.read(cx).snapshot(cx);
11794
11795 let mut edits = Vec::new();
11796 let mut unfold_ranges = Vec::new();
11797 let mut refold_creases = Vec::new();
11798
11799 let selections = self.selections.all::<Point>(cx);
11800 let mut selections = selections.iter().peekable();
11801 let mut contiguous_row_selections = Vec::new();
11802 let mut new_selections = Vec::new();
11803
11804 while let Some(selection) = selections.next() {
11805 // Find all the selections that span a contiguous row range
11806 let (start_row, end_row) = consume_contiguous_rows(
11807 &mut contiguous_row_selections,
11808 selection,
11809 &display_map,
11810 &mut selections,
11811 );
11812
11813 // Move the text spanned by the row range to be before the line preceding the row range
11814 if start_row.0 > 0 {
11815 let range_to_move = Point::new(
11816 start_row.previous_row().0,
11817 buffer.line_len(start_row.previous_row()),
11818 )
11819 ..Point::new(
11820 end_row.previous_row().0,
11821 buffer.line_len(end_row.previous_row()),
11822 );
11823 let insertion_point = display_map
11824 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11825 .0;
11826
11827 // Don't move lines across excerpts
11828 if buffer
11829 .excerpt_containing(insertion_point..range_to_move.end)
11830 .is_some()
11831 {
11832 let text = buffer
11833 .text_for_range(range_to_move.clone())
11834 .flat_map(|s| s.chars())
11835 .skip(1)
11836 .chain(['\n'])
11837 .collect::<String>();
11838
11839 edits.push((
11840 buffer.anchor_after(range_to_move.start)
11841 ..buffer.anchor_before(range_to_move.end),
11842 String::new(),
11843 ));
11844 let insertion_anchor = buffer.anchor_after(insertion_point);
11845 edits.push((insertion_anchor..insertion_anchor, text));
11846
11847 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11848
11849 // Move selections up
11850 new_selections.extend(contiguous_row_selections.drain(..).map(
11851 |mut selection| {
11852 selection.start.row -= row_delta;
11853 selection.end.row -= row_delta;
11854 selection
11855 },
11856 ));
11857
11858 // Move folds up
11859 unfold_ranges.push(range_to_move.clone());
11860 for fold in display_map.folds_in_range(
11861 buffer.anchor_before(range_to_move.start)
11862 ..buffer.anchor_after(range_to_move.end),
11863 ) {
11864 let mut start = fold.range.start.to_point(&buffer);
11865 let mut end = fold.range.end.to_point(&buffer);
11866 start.row -= row_delta;
11867 end.row -= row_delta;
11868 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11869 }
11870 }
11871 }
11872
11873 // If we didn't move line(s), preserve the existing selections
11874 new_selections.append(&mut contiguous_row_selections);
11875 }
11876
11877 self.transact(window, cx, |this, window, cx| {
11878 this.unfold_ranges(&unfold_ranges, true, true, cx);
11879 this.buffer.update(cx, |buffer, cx| {
11880 for (range, text) in edits {
11881 buffer.edit([(range, text)], None, cx);
11882 }
11883 });
11884 this.fold_creases(refold_creases, true, window, cx);
11885 this.change_selections(Default::default(), window, cx, |s| {
11886 s.select(new_selections);
11887 })
11888 });
11889 }
11890
11891 pub fn move_line_down(
11892 &mut self,
11893 _: &MoveLineDown,
11894 window: &mut Window,
11895 cx: &mut Context<Self>,
11896 ) {
11897 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11898 if self.mode.is_single_line() {
11899 cx.propagate();
11900 return;
11901 }
11902
11903 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11904 let buffer = self.buffer.read(cx).snapshot(cx);
11905
11906 let mut edits = Vec::new();
11907 let mut unfold_ranges = Vec::new();
11908 let mut refold_creases = Vec::new();
11909
11910 let selections = self.selections.all::<Point>(cx);
11911 let mut selections = selections.iter().peekable();
11912 let mut contiguous_row_selections = Vec::new();
11913 let mut new_selections = Vec::new();
11914
11915 while let Some(selection) = selections.next() {
11916 // Find all the selections that span a contiguous row range
11917 let (start_row, end_row) = consume_contiguous_rows(
11918 &mut contiguous_row_selections,
11919 selection,
11920 &display_map,
11921 &mut selections,
11922 );
11923
11924 // Move the text spanned by the row range to be after the last line of the row range
11925 if end_row.0 <= buffer.max_point().row {
11926 let range_to_move =
11927 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11928 let insertion_point = display_map
11929 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11930 .0;
11931
11932 // Don't move lines across excerpt boundaries
11933 if buffer
11934 .excerpt_containing(range_to_move.start..insertion_point)
11935 .is_some()
11936 {
11937 let mut text = String::from("\n");
11938 text.extend(buffer.text_for_range(range_to_move.clone()));
11939 text.pop(); // Drop trailing newline
11940 edits.push((
11941 buffer.anchor_after(range_to_move.start)
11942 ..buffer.anchor_before(range_to_move.end),
11943 String::new(),
11944 ));
11945 let insertion_anchor = buffer.anchor_after(insertion_point);
11946 edits.push((insertion_anchor..insertion_anchor, text));
11947
11948 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11949
11950 // Move selections down
11951 new_selections.extend(contiguous_row_selections.drain(..).map(
11952 |mut selection| {
11953 selection.start.row += row_delta;
11954 selection.end.row += row_delta;
11955 selection
11956 },
11957 ));
11958
11959 // Move folds down
11960 unfold_ranges.push(range_to_move.clone());
11961 for fold in display_map.folds_in_range(
11962 buffer.anchor_before(range_to_move.start)
11963 ..buffer.anchor_after(range_to_move.end),
11964 ) {
11965 let mut start = fold.range.start.to_point(&buffer);
11966 let mut end = fold.range.end.to_point(&buffer);
11967 start.row += row_delta;
11968 end.row += row_delta;
11969 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11970 }
11971 }
11972 }
11973
11974 // If we didn't move line(s), preserve the existing selections
11975 new_selections.append(&mut contiguous_row_selections);
11976 }
11977
11978 self.transact(window, cx, |this, window, cx| {
11979 this.unfold_ranges(&unfold_ranges, true, true, cx);
11980 this.buffer.update(cx, |buffer, cx| {
11981 for (range, text) in edits {
11982 buffer.edit([(range, text)], None, cx);
11983 }
11984 });
11985 this.fold_creases(refold_creases, true, window, cx);
11986 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11987 });
11988 }
11989
11990 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11991 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11992 let text_layout_details = &self.text_layout_details(window);
11993 self.transact(window, cx, |this, window, cx| {
11994 let edits = this.change_selections(Default::default(), window, cx, |s| {
11995 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11996 s.move_with(|display_map, selection| {
11997 if !selection.is_empty() {
11998 return;
11999 }
12000
12001 let mut head = selection.head();
12002 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12003 if head.column() == display_map.line_len(head.row()) {
12004 transpose_offset = display_map
12005 .buffer_snapshot()
12006 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12007 }
12008
12009 if transpose_offset == 0 {
12010 return;
12011 }
12012
12013 *head.column_mut() += 1;
12014 head = display_map.clip_point(head, Bias::Right);
12015 let goal = SelectionGoal::HorizontalPosition(
12016 display_map
12017 .x_for_display_point(head, text_layout_details)
12018 .into(),
12019 );
12020 selection.collapse_to(head, goal);
12021
12022 let transpose_start = display_map
12023 .buffer_snapshot()
12024 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12025 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12026 let transpose_end = display_map
12027 .buffer_snapshot()
12028 .clip_offset(transpose_offset + 1, Bias::Right);
12029 if let Some(ch) = display_map
12030 .buffer_snapshot()
12031 .chars_at(transpose_start)
12032 .next()
12033 {
12034 edits.push((transpose_start..transpose_offset, String::new()));
12035 edits.push((transpose_end..transpose_end, ch.to_string()));
12036 }
12037 }
12038 });
12039 edits
12040 });
12041 this.buffer
12042 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12043 let selections = this.selections.all::<usize>(cx);
12044 this.change_selections(Default::default(), window, cx, |s| {
12045 s.select(selections);
12046 });
12047 });
12048 }
12049
12050 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12051 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12052 if self.mode.is_single_line() {
12053 cx.propagate();
12054 return;
12055 }
12056
12057 self.rewrap_impl(RewrapOptions::default(), cx)
12058 }
12059
12060 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12061 let buffer = self.buffer.read(cx).snapshot(cx);
12062 let selections = self.selections.all::<Point>(cx);
12063
12064 #[derive(Clone, Debug, PartialEq)]
12065 enum CommentFormat {
12066 /// single line comment, with prefix for line
12067 Line(String),
12068 /// single line within a block comment, with prefix for line
12069 BlockLine(String),
12070 /// a single line of a block comment that includes the initial delimiter
12071 BlockCommentWithStart(BlockCommentConfig),
12072 /// a single line of a block comment that includes the ending delimiter
12073 BlockCommentWithEnd(BlockCommentConfig),
12074 }
12075
12076 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12077 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12078 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12079 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12080 .peekable();
12081
12082 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12083 row
12084 } else {
12085 return Vec::new();
12086 };
12087
12088 let language_settings = buffer.language_settings_at(selection.head(), cx);
12089 let language_scope = buffer.language_scope_at(selection.head());
12090
12091 let indent_and_prefix_for_row =
12092 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12093 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12094 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12095 &language_scope
12096 {
12097 let indent_end = Point::new(row, indent.len);
12098 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12099 let line_text_after_indent = buffer
12100 .text_for_range(indent_end..line_end)
12101 .collect::<String>();
12102
12103 let is_within_comment_override = buffer
12104 .language_scope_at(indent_end)
12105 .is_some_and(|scope| scope.override_name() == Some("comment"));
12106 let comment_delimiters = if is_within_comment_override {
12107 // we are within a comment syntax node, but we don't
12108 // yet know what kind of comment: block, doc or line
12109 match (
12110 language_scope.documentation_comment(),
12111 language_scope.block_comment(),
12112 ) {
12113 (Some(config), _) | (_, Some(config))
12114 if buffer.contains_str_at(indent_end, &config.start) =>
12115 {
12116 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12117 }
12118 (Some(config), _) | (_, Some(config))
12119 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12120 {
12121 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12122 }
12123 (Some(config), _) | (_, Some(config))
12124 if buffer.contains_str_at(indent_end, &config.prefix) =>
12125 {
12126 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12127 }
12128 (_, _) => language_scope
12129 .line_comment_prefixes()
12130 .iter()
12131 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12132 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12133 }
12134 } else {
12135 // we not in an overridden comment node, but we may
12136 // be within a non-overridden line comment node
12137 language_scope
12138 .line_comment_prefixes()
12139 .iter()
12140 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12141 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12142 };
12143
12144 let rewrap_prefix = language_scope
12145 .rewrap_prefixes()
12146 .iter()
12147 .find_map(|prefix_regex| {
12148 prefix_regex.find(&line_text_after_indent).map(|mat| {
12149 if mat.start() == 0 {
12150 Some(mat.as_str().to_string())
12151 } else {
12152 None
12153 }
12154 })
12155 })
12156 .flatten();
12157 (comment_delimiters, rewrap_prefix)
12158 } else {
12159 (None, None)
12160 };
12161 (indent, comment_prefix, rewrap_prefix)
12162 };
12163
12164 let mut ranges = Vec::new();
12165 let from_empty_selection = selection.is_empty();
12166
12167 let mut current_range_start = first_row;
12168 let mut prev_row = first_row;
12169 let (
12170 mut current_range_indent,
12171 mut current_range_comment_delimiters,
12172 mut current_range_rewrap_prefix,
12173 ) = indent_and_prefix_for_row(first_row);
12174
12175 for row in non_blank_rows_iter.skip(1) {
12176 let has_paragraph_break = row > prev_row + 1;
12177
12178 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12179 indent_and_prefix_for_row(row);
12180
12181 let has_indent_change = row_indent != current_range_indent;
12182 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12183
12184 let has_boundary_change = has_comment_change
12185 || row_rewrap_prefix.is_some()
12186 || (has_indent_change && current_range_comment_delimiters.is_some());
12187
12188 if has_paragraph_break || has_boundary_change {
12189 ranges.push((
12190 language_settings.clone(),
12191 Point::new(current_range_start, 0)
12192 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12193 current_range_indent,
12194 current_range_comment_delimiters.clone(),
12195 current_range_rewrap_prefix.clone(),
12196 from_empty_selection,
12197 ));
12198 current_range_start = row;
12199 current_range_indent = row_indent;
12200 current_range_comment_delimiters = row_comment_delimiters;
12201 current_range_rewrap_prefix = row_rewrap_prefix;
12202 }
12203 prev_row = row;
12204 }
12205
12206 ranges.push((
12207 language_settings.clone(),
12208 Point::new(current_range_start, 0)
12209 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12210 current_range_indent,
12211 current_range_comment_delimiters,
12212 current_range_rewrap_prefix,
12213 from_empty_selection,
12214 ));
12215
12216 ranges
12217 });
12218
12219 let mut edits = Vec::new();
12220 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12221
12222 for (
12223 language_settings,
12224 wrap_range,
12225 mut indent_size,
12226 comment_prefix,
12227 rewrap_prefix,
12228 from_empty_selection,
12229 ) in wrap_ranges
12230 {
12231 let mut start_row = wrap_range.start.row;
12232 let mut end_row = wrap_range.end.row;
12233
12234 // Skip selections that overlap with a range that has already been rewrapped.
12235 let selection_range = start_row..end_row;
12236 if rewrapped_row_ranges
12237 .iter()
12238 .any(|range| range.overlaps(&selection_range))
12239 {
12240 continue;
12241 }
12242
12243 let tab_size = language_settings.tab_size;
12244
12245 let (line_prefix, inside_comment) = match &comment_prefix {
12246 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12247 (Some(prefix.as_str()), true)
12248 }
12249 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12250 (Some(prefix.as_ref()), true)
12251 }
12252 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12253 start: _,
12254 end: _,
12255 prefix,
12256 tab_size,
12257 })) => {
12258 indent_size.len += tab_size;
12259 (Some(prefix.as_ref()), true)
12260 }
12261 None => (None, false),
12262 };
12263 let indent_prefix = indent_size.chars().collect::<String>();
12264 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12265
12266 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12267 RewrapBehavior::InComments => inside_comment,
12268 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12269 RewrapBehavior::Anywhere => true,
12270 };
12271
12272 let should_rewrap = options.override_language_settings
12273 || allow_rewrap_based_on_language
12274 || self.hard_wrap.is_some();
12275 if !should_rewrap {
12276 continue;
12277 }
12278
12279 if from_empty_selection {
12280 'expand_upwards: while start_row > 0 {
12281 let prev_row = start_row - 1;
12282 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12283 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12284 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12285 {
12286 start_row = prev_row;
12287 } else {
12288 break 'expand_upwards;
12289 }
12290 }
12291
12292 'expand_downwards: while end_row < buffer.max_point().row {
12293 let next_row = end_row + 1;
12294 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12295 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12296 && !buffer.is_line_blank(MultiBufferRow(next_row))
12297 {
12298 end_row = next_row;
12299 } else {
12300 break 'expand_downwards;
12301 }
12302 }
12303 }
12304
12305 let start = Point::new(start_row, 0);
12306 let start_offset = ToOffset::to_offset(&start, &buffer);
12307 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12308 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12309 let mut first_line_delimiter = None;
12310 let mut last_line_delimiter = None;
12311 let Some(lines_without_prefixes) = selection_text
12312 .lines()
12313 .enumerate()
12314 .map(|(ix, line)| {
12315 let line_trimmed = line.trim_start();
12316 if rewrap_prefix.is_some() && ix > 0 {
12317 Ok(line_trimmed)
12318 } else if let Some(
12319 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12320 start,
12321 prefix,
12322 end,
12323 tab_size,
12324 })
12325 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12326 start,
12327 prefix,
12328 end,
12329 tab_size,
12330 }),
12331 ) = &comment_prefix
12332 {
12333 let line_trimmed = line_trimmed
12334 .strip_prefix(start.as_ref())
12335 .map(|s| {
12336 let mut indent_size = indent_size;
12337 indent_size.len -= tab_size;
12338 let indent_prefix: String = indent_size.chars().collect();
12339 first_line_delimiter = Some((indent_prefix, start));
12340 s.trim_start()
12341 })
12342 .unwrap_or(line_trimmed);
12343 let line_trimmed = line_trimmed
12344 .strip_suffix(end.as_ref())
12345 .map(|s| {
12346 last_line_delimiter = Some(end);
12347 s.trim_end()
12348 })
12349 .unwrap_or(line_trimmed);
12350 let line_trimmed = line_trimmed
12351 .strip_prefix(prefix.as_ref())
12352 .unwrap_or(line_trimmed);
12353 Ok(line_trimmed)
12354 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12355 line_trimmed.strip_prefix(prefix).with_context(|| {
12356 format!("line did not start with prefix {prefix:?}: {line:?}")
12357 })
12358 } else {
12359 line_trimmed
12360 .strip_prefix(&line_prefix.trim_start())
12361 .with_context(|| {
12362 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12363 })
12364 }
12365 })
12366 .collect::<Result<Vec<_>, _>>()
12367 .log_err()
12368 else {
12369 continue;
12370 };
12371
12372 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12373 buffer
12374 .language_settings_at(Point::new(start_row, 0), cx)
12375 .preferred_line_length as usize
12376 });
12377
12378 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12379 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12380 } else {
12381 line_prefix.clone()
12382 };
12383
12384 let wrapped_text = {
12385 let mut wrapped_text = wrap_with_prefix(
12386 line_prefix,
12387 subsequent_lines_prefix,
12388 lines_without_prefixes.join("\n"),
12389 wrap_column,
12390 tab_size,
12391 options.preserve_existing_whitespace,
12392 );
12393
12394 if let Some((indent, delimiter)) = first_line_delimiter {
12395 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12396 }
12397 if let Some(last_line) = last_line_delimiter {
12398 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12399 }
12400
12401 wrapped_text
12402 };
12403
12404 // TODO: should always use char-based diff while still supporting cursor behavior that
12405 // matches vim.
12406 let mut diff_options = DiffOptions::default();
12407 if options.override_language_settings {
12408 diff_options.max_word_diff_len = 0;
12409 diff_options.max_word_diff_line_count = 0;
12410 } else {
12411 diff_options.max_word_diff_len = usize::MAX;
12412 diff_options.max_word_diff_line_count = usize::MAX;
12413 }
12414
12415 for (old_range, new_text) in
12416 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12417 {
12418 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12419 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12420 edits.push((edit_start..edit_end, new_text));
12421 }
12422
12423 rewrapped_row_ranges.push(start_row..=end_row);
12424 }
12425
12426 self.buffer
12427 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12428 }
12429
12430 pub fn cut_common(
12431 &mut self,
12432 cut_no_selection_line: bool,
12433 window: &mut Window,
12434 cx: &mut Context<Self>,
12435 ) -> ClipboardItem {
12436 let mut text = String::new();
12437 let buffer = self.buffer.read(cx).snapshot(cx);
12438 let mut selections = self.selections.all::<Point>(cx);
12439 let mut clipboard_selections = Vec::with_capacity(selections.len());
12440 {
12441 let max_point = buffer.max_point();
12442 let mut is_first = true;
12443 for selection in &mut selections {
12444 let is_entire_line =
12445 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12446 if is_entire_line {
12447 selection.start = Point::new(selection.start.row, 0);
12448 if !selection.is_empty() && selection.end.column == 0 {
12449 selection.end = cmp::min(max_point, selection.end);
12450 } else {
12451 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12452 }
12453 selection.goal = SelectionGoal::None;
12454 }
12455 if is_first {
12456 is_first = false;
12457 } else {
12458 text += "\n";
12459 }
12460 let mut len = 0;
12461 for chunk in buffer.text_for_range(selection.start..selection.end) {
12462 text.push_str(chunk);
12463 len += chunk.len();
12464 }
12465 clipboard_selections.push(ClipboardSelection {
12466 len,
12467 is_entire_line,
12468 first_line_indent: buffer
12469 .indent_size_for_line(MultiBufferRow(selection.start.row))
12470 .len,
12471 });
12472 }
12473 }
12474
12475 self.transact(window, cx, |this, window, cx| {
12476 this.change_selections(Default::default(), window, cx, |s| {
12477 s.select(selections);
12478 });
12479 this.insert("", window, cx);
12480 });
12481 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12482 }
12483
12484 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12485 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12486 let item = self.cut_common(true, window, cx);
12487 cx.write_to_clipboard(item);
12488 }
12489
12490 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12491 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12492 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12493 s.move_with(|snapshot, sel| {
12494 if sel.is_empty() {
12495 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12496 }
12497 if sel.is_empty() {
12498 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12499 }
12500 });
12501 });
12502 let item = self.cut_common(false, window, cx);
12503 cx.set_global(KillRing(item))
12504 }
12505
12506 pub fn kill_ring_yank(
12507 &mut self,
12508 _: &KillRingYank,
12509 window: &mut Window,
12510 cx: &mut Context<Self>,
12511 ) {
12512 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12513 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12514 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12515 (kill_ring.text().to_string(), kill_ring.metadata_json())
12516 } else {
12517 return;
12518 }
12519 } else {
12520 return;
12521 };
12522 self.do_paste(&text, metadata, false, window, cx);
12523 }
12524
12525 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12526 self.do_copy(true, cx);
12527 }
12528
12529 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12530 self.do_copy(false, cx);
12531 }
12532
12533 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12534 let selections = self.selections.all::<Point>(cx);
12535 let buffer = self.buffer.read(cx).read(cx);
12536 let mut text = String::new();
12537
12538 let mut clipboard_selections = Vec::with_capacity(selections.len());
12539 {
12540 let max_point = buffer.max_point();
12541 let mut is_first = true;
12542 for selection in &selections {
12543 let mut start = selection.start;
12544 let mut end = selection.end;
12545 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12546 let mut add_trailing_newline = false;
12547 if is_entire_line {
12548 start = Point::new(start.row, 0);
12549 let next_line_start = Point::new(end.row + 1, 0);
12550 if next_line_start <= max_point {
12551 end = next_line_start;
12552 } else {
12553 // We're on the last line without a trailing newline.
12554 // Copy to the end of the line and add a newline afterwards.
12555 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12556 add_trailing_newline = true;
12557 }
12558 }
12559
12560 let mut trimmed_selections = Vec::new();
12561 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12562 let row = MultiBufferRow(start.row);
12563 let first_indent = buffer.indent_size_for_line(row);
12564 if first_indent.len == 0 || start.column > first_indent.len {
12565 trimmed_selections.push(start..end);
12566 } else {
12567 trimmed_selections.push(
12568 Point::new(row.0, first_indent.len)
12569 ..Point::new(row.0, buffer.line_len(row)),
12570 );
12571 for row in start.row + 1..=end.row {
12572 let mut line_len = buffer.line_len(MultiBufferRow(row));
12573 if row == end.row {
12574 line_len = end.column;
12575 }
12576 if line_len == 0 {
12577 trimmed_selections
12578 .push(Point::new(row, 0)..Point::new(row, line_len));
12579 continue;
12580 }
12581 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12582 if row_indent_size.len >= first_indent.len {
12583 trimmed_selections.push(
12584 Point::new(row, first_indent.len)..Point::new(row, line_len),
12585 );
12586 } else {
12587 trimmed_selections.clear();
12588 trimmed_selections.push(start..end);
12589 break;
12590 }
12591 }
12592 }
12593 } else {
12594 trimmed_selections.push(start..end);
12595 }
12596
12597 for trimmed_range in trimmed_selections {
12598 if is_first {
12599 is_first = false;
12600 } else {
12601 text += "\n";
12602 }
12603 let mut len = 0;
12604 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12605 text.push_str(chunk);
12606 len += chunk.len();
12607 }
12608 if add_trailing_newline {
12609 text.push('\n');
12610 len += 1;
12611 }
12612 clipboard_selections.push(ClipboardSelection {
12613 len,
12614 is_entire_line,
12615 first_line_indent: buffer
12616 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12617 .len,
12618 });
12619 }
12620 }
12621 }
12622
12623 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12624 text,
12625 clipboard_selections,
12626 ));
12627 }
12628
12629 pub fn do_paste(
12630 &mut self,
12631 text: &String,
12632 clipboard_selections: Option<Vec<ClipboardSelection>>,
12633 handle_entire_lines: bool,
12634 window: &mut Window,
12635 cx: &mut Context<Self>,
12636 ) {
12637 if self.read_only(cx) {
12638 return;
12639 }
12640
12641 let clipboard_text = Cow::Borrowed(text.as_str());
12642
12643 self.transact(window, cx, |this, window, cx| {
12644 let had_active_edit_prediction = this.has_active_edit_prediction();
12645 let old_selections = this.selections.all::<usize>(cx);
12646 let cursor_offset = this.selections.last::<usize>(cx).head();
12647
12648 if let Some(mut clipboard_selections) = clipboard_selections {
12649 let all_selections_were_entire_line =
12650 clipboard_selections.iter().all(|s| s.is_entire_line);
12651 let first_selection_indent_column =
12652 clipboard_selections.first().map(|s| s.first_line_indent);
12653 if clipboard_selections.len() != old_selections.len() {
12654 clipboard_selections.drain(..);
12655 }
12656 let mut auto_indent_on_paste = true;
12657
12658 this.buffer.update(cx, |buffer, cx| {
12659 let snapshot = buffer.read(cx);
12660 auto_indent_on_paste = snapshot
12661 .language_settings_at(cursor_offset, cx)
12662 .auto_indent_on_paste;
12663
12664 let mut start_offset = 0;
12665 let mut edits = Vec::new();
12666 let mut original_indent_columns = Vec::new();
12667 for (ix, selection) in old_selections.iter().enumerate() {
12668 let to_insert;
12669 let entire_line;
12670 let original_indent_column;
12671 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12672 let end_offset = start_offset + clipboard_selection.len;
12673 to_insert = &clipboard_text[start_offset..end_offset];
12674 entire_line = clipboard_selection.is_entire_line;
12675 start_offset = end_offset + 1;
12676 original_indent_column = Some(clipboard_selection.first_line_indent);
12677 } else {
12678 to_insert = &*clipboard_text;
12679 entire_line = all_selections_were_entire_line;
12680 original_indent_column = first_selection_indent_column
12681 }
12682
12683 let (range, to_insert) =
12684 if selection.is_empty() && handle_entire_lines && entire_line {
12685 // If the corresponding selection was empty when this slice of the
12686 // clipboard text was written, then the entire line containing the
12687 // selection was copied. If this selection is also currently empty,
12688 // then paste the line before the current line of the buffer.
12689 let column = selection.start.to_point(&snapshot).column as usize;
12690 let line_start = selection.start - column;
12691 (line_start..line_start, Cow::Borrowed(to_insert))
12692 } else {
12693 let language = snapshot.language_at(selection.head());
12694 let range = selection.range();
12695 if let Some(language) = language
12696 && language.name() == "Markdown".into()
12697 {
12698 edit_for_markdown_paste(
12699 &snapshot,
12700 range,
12701 to_insert,
12702 url::Url::parse(to_insert).ok(),
12703 )
12704 } else {
12705 (range, Cow::Borrowed(to_insert))
12706 }
12707 };
12708
12709 edits.push((range, to_insert));
12710 original_indent_columns.push(original_indent_column);
12711 }
12712 drop(snapshot);
12713
12714 buffer.edit(
12715 edits,
12716 if auto_indent_on_paste {
12717 Some(AutoindentMode::Block {
12718 original_indent_columns,
12719 })
12720 } else {
12721 None
12722 },
12723 cx,
12724 );
12725 });
12726
12727 let selections = this.selections.all::<usize>(cx);
12728 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12729 } else {
12730 let url = url::Url::parse(&clipboard_text).ok();
12731
12732 let auto_indent_mode = if !clipboard_text.is_empty() {
12733 Some(AutoindentMode::Block {
12734 original_indent_columns: Vec::new(),
12735 })
12736 } else {
12737 None
12738 };
12739
12740 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12741 let snapshot = buffer.snapshot(cx);
12742
12743 let anchors = old_selections
12744 .iter()
12745 .map(|s| {
12746 let anchor = snapshot.anchor_after(s.head());
12747 s.map(|_| anchor)
12748 })
12749 .collect::<Vec<_>>();
12750
12751 let mut edits = Vec::new();
12752
12753 for selection in old_selections.iter() {
12754 let language = snapshot.language_at(selection.head());
12755 let range = selection.range();
12756
12757 let (edit_range, edit_text) = if let Some(language) = language
12758 && language.name() == "Markdown".into()
12759 {
12760 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12761 } else {
12762 (range, clipboard_text.clone())
12763 };
12764
12765 edits.push((edit_range, edit_text));
12766 }
12767
12768 drop(snapshot);
12769 buffer.edit(edits, auto_indent_mode, cx);
12770
12771 anchors
12772 });
12773
12774 this.change_selections(Default::default(), window, cx, |s| {
12775 s.select_anchors(selection_anchors);
12776 });
12777 }
12778
12779 let trigger_in_words =
12780 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12781
12782 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12783 });
12784 }
12785
12786 pub fn diff_clipboard_with_selection(
12787 &mut self,
12788 _: &DiffClipboardWithSelection,
12789 window: &mut Window,
12790 cx: &mut Context<Self>,
12791 ) {
12792 let selections = self.selections.all::<usize>(cx);
12793
12794 if selections.is_empty() {
12795 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12796 return;
12797 };
12798
12799 let clipboard_text = match cx.read_from_clipboard() {
12800 Some(item) => match item.entries().first() {
12801 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12802 _ => None,
12803 },
12804 None => None,
12805 };
12806
12807 let Some(clipboard_text) = clipboard_text else {
12808 log::warn!("Clipboard doesn't contain text.");
12809 return;
12810 };
12811
12812 window.dispatch_action(
12813 Box::new(DiffClipboardWithSelectionData {
12814 clipboard_text,
12815 editor: cx.entity(),
12816 }),
12817 cx,
12818 );
12819 }
12820
12821 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12822 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12823 if let Some(item) = cx.read_from_clipboard() {
12824 let entries = item.entries();
12825
12826 match entries.first() {
12827 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12828 // of all the pasted entries.
12829 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12830 .do_paste(
12831 clipboard_string.text(),
12832 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12833 true,
12834 window,
12835 cx,
12836 ),
12837 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12838 }
12839 }
12840 }
12841
12842 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12843 if self.read_only(cx) {
12844 return;
12845 }
12846
12847 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12848
12849 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12850 if let Some((selections, _)) =
12851 self.selection_history.transaction(transaction_id).cloned()
12852 {
12853 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12854 s.select_anchors(selections.to_vec());
12855 });
12856 } else {
12857 log::error!(
12858 "No entry in selection_history found for undo. \
12859 This may correspond to a bug where undo does not update the selection. \
12860 If this is occurring, please add details to \
12861 https://github.com/zed-industries/zed/issues/22692"
12862 );
12863 }
12864 self.request_autoscroll(Autoscroll::fit(), cx);
12865 self.unmark_text(window, cx);
12866 self.refresh_edit_prediction(true, false, window, cx);
12867 cx.emit(EditorEvent::Edited { transaction_id });
12868 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12869 }
12870 }
12871
12872 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12873 if self.read_only(cx) {
12874 return;
12875 }
12876
12877 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12878
12879 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12880 if let Some((_, Some(selections))) =
12881 self.selection_history.transaction(transaction_id).cloned()
12882 {
12883 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12884 s.select_anchors(selections.to_vec());
12885 });
12886 } else {
12887 log::error!(
12888 "No entry in selection_history found for redo. \
12889 This may correspond to a bug where undo does not update the selection. \
12890 If this is occurring, please add details to \
12891 https://github.com/zed-industries/zed/issues/22692"
12892 );
12893 }
12894 self.request_autoscroll(Autoscroll::fit(), cx);
12895 self.unmark_text(window, cx);
12896 self.refresh_edit_prediction(true, false, window, cx);
12897 cx.emit(EditorEvent::Edited { transaction_id });
12898 }
12899 }
12900
12901 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12902 self.buffer
12903 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12904 }
12905
12906 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12907 self.buffer
12908 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12909 }
12910
12911 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12912 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12913 self.change_selections(Default::default(), window, cx, |s| {
12914 s.move_with(|map, selection| {
12915 let cursor = if selection.is_empty() {
12916 movement::left(map, selection.start)
12917 } else {
12918 selection.start
12919 };
12920 selection.collapse_to(cursor, SelectionGoal::None);
12921 });
12922 })
12923 }
12924
12925 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12926 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12927 self.change_selections(Default::default(), window, cx, |s| {
12928 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12929 })
12930 }
12931
12932 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12933 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12934 self.change_selections(Default::default(), window, cx, |s| {
12935 s.move_with(|map, selection| {
12936 let cursor = if selection.is_empty() {
12937 movement::right(map, selection.end)
12938 } else {
12939 selection.end
12940 };
12941 selection.collapse_to(cursor, SelectionGoal::None)
12942 });
12943 })
12944 }
12945
12946 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12947 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12948 self.change_selections(Default::default(), window, cx, |s| {
12949 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12950 });
12951 }
12952
12953 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12954 if self.take_rename(true, window, cx).is_some() {
12955 return;
12956 }
12957
12958 if self.mode.is_single_line() {
12959 cx.propagate();
12960 return;
12961 }
12962
12963 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12964
12965 let text_layout_details = &self.text_layout_details(window);
12966 let selection_count = self.selections.count();
12967 let first_selection = self.selections.first_anchor();
12968
12969 self.change_selections(Default::default(), window, cx, |s| {
12970 s.move_with(|map, selection| {
12971 if !selection.is_empty() {
12972 selection.goal = SelectionGoal::None;
12973 }
12974 let (cursor, goal) = movement::up(
12975 map,
12976 selection.start,
12977 selection.goal,
12978 false,
12979 text_layout_details,
12980 );
12981 selection.collapse_to(cursor, goal);
12982 });
12983 });
12984
12985 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12986 {
12987 cx.propagate();
12988 }
12989 }
12990
12991 pub fn move_up_by_lines(
12992 &mut self,
12993 action: &MoveUpByLines,
12994 window: &mut Window,
12995 cx: &mut Context<Self>,
12996 ) {
12997 if self.take_rename(true, window, cx).is_some() {
12998 return;
12999 }
13000
13001 if self.mode.is_single_line() {
13002 cx.propagate();
13003 return;
13004 }
13005
13006 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13007
13008 let text_layout_details = &self.text_layout_details(window);
13009
13010 self.change_selections(Default::default(), window, cx, |s| {
13011 s.move_with(|map, selection| {
13012 if !selection.is_empty() {
13013 selection.goal = SelectionGoal::None;
13014 }
13015 let (cursor, goal) = movement::up_by_rows(
13016 map,
13017 selection.start,
13018 action.lines,
13019 selection.goal,
13020 false,
13021 text_layout_details,
13022 );
13023 selection.collapse_to(cursor, goal);
13024 });
13025 })
13026 }
13027
13028 pub fn move_down_by_lines(
13029 &mut self,
13030 action: &MoveDownByLines,
13031 window: &mut Window,
13032 cx: &mut Context<Self>,
13033 ) {
13034 if self.take_rename(true, window, cx).is_some() {
13035 return;
13036 }
13037
13038 if self.mode.is_single_line() {
13039 cx.propagate();
13040 return;
13041 }
13042
13043 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13044
13045 let text_layout_details = &self.text_layout_details(window);
13046
13047 self.change_selections(Default::default(), window, cx, |s| {
13048 s.move_with(|map, selection| {
13049 if !selection.is_empty() {
13050 selection.goal = SelectionGoal::None;
13051 }
13052 let (cursor, goal) = movement::down_by_rows(
13053 map,
13054 selection.start,
13055 action.lines,
13056 selection.goal,
13057 false,
13058 text_layout_details,
13059 );
13060 selection.collapse_to(cursor, goal);
13061 });
13062 })
13063 }
13064
13065 pub fn select_down_by_lines(
13066 &mut self,
13067 action: &SelectDownByLines,
13068 window: &mut Window,
13069 cx: &mut Context<Self>,
13070 ) {
13071 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13072 let text_layout_details = &self.text_layout_details(window);
13073 self.change_selections(Default::default(), window, cx, |s| {
13074 s.move_heads_with(|map, head, goal| {
13075 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13076 })
13077 })
13078 }
13079
13080 pub fn select_up_by_lines(
13081 &mut self,
13082 action: &SelectUpByLines,
13083 window: &mut Window,
13084 cx: &mut Context<Self>,
13085 ) {
13086 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13087 let text_layout_details = &self.text_layout_details(window);
13088 self.change_selections(Default::default(), window, cx, |s| {
13089 s.move_heads_with(|map, head, goal| {
13090 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13091 })
13092 })
13093 }
13094
13095 pub fn select_page_up(
13096 &mut self,
13097 _: &SelectPageUp,
13098 window: &mut Window,
13099 cx: &mut Context<Self>,
13100 ) {
13101 let Some(row_count) = self.visible_row_count() else {
13102 return;
13103 };
13104
13105 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13106
13107 let text_layout_details = &self.text_layout_details(window);
13108
13109 self.change_selections(Default::default(), window, cx, |s| {
13110 s.move_heads_with(|map, head, goal| {
13111 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13112 })
13113 })
13114 }
13115
13116 pub fn move_page_up(
13117 &mut self,
13118 action: &MovePageUp,
13119 window: &mut Window,
13120 cx: &mut Context<Self>,
13121 ) {
13122 if self.take_rename(true, window, cx).is_some() {
13123 return;
13124 }
13125
13126 if self
13127 .context_menu
13128 .borrow_mut()
13129 .as_mut()
13130 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13131 .unwrap_or(false)
13132 {
13133 return;
13134 }
13135
13136 if matches!(self.mode, EditorMode::SingleLine) {
13137 cx.propagate();
13138 return;
13139 }
13140
13141 let Some(row_count) = self.visible_row_count() else {
13142 return;
13143 };
13144
13145 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13146
13147 let effects = if action.center_cursor {
13148 SelectionEffects::scroll(Autoscroll::center())
13149 } else {
13150 SelectionEffects::default()
13151 };
13152
13153 let text_layout_details = &self.text_layout_details(window);
13154
13155 self.change_selections(effects, window, cx, |s| {
13156 s.move_with(|map, selection| {
13157 if !selection.is_empty() {
13158 selection.goal = SelectionGoal::None;
13159 }
13160 let (cursor, goal) = movement::up_by_rows(
13161 map,
13162 selection.end,
13163 row_count,
13164 selection.goal,
13165 false,
13166 text_layout_details,
13167 );
13168 selection.collapse_to(cursor, goal);
13169 });
13170 });
13171 }
13172
13173 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13174 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13175 let text_layout_details = &self.text_layout_details(window);
13176 self.change_selections(Default::default(), window, cx, |s| {
13177 s.move_heads_with(|map, head, goal| {
13178 movement::up(map, head, goal, false, text_layout_details)
13179 })
13180 })
13181 }
13182
13183 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13184 self.take_rename(true, window, cx);
13185
13186 if self.mode.is_single_line() {
13187 cx.propagate();
13188 return;
13189 }
13190
13191 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13192
13193 let text_layout_details = &self.text_layout_details(window);
13194 let selection_count = self.selections.count();
13195 let first_selection = self.selections.first_anchor();
13196
13197 self.change_selections(Default::default(), window, cx, |s| {
13198 s.move_with(|map, selection| {
13199 if !selection.is_empty() {
13200 selection.goal = SelectionGoal::None;
13201 }
13202 let (cursor, goal) = movement::down(
13203 map,
13204 selection.end,
13205 selection.goal,
13206 false,
13207 text_layout_details,
13208 );
13209 selection.collapse_to(cursor, goal);
13210 });
13211 });
13212
13213 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13214 {
13215 cx.propagate();
13216 }
13217 }
13218
13219 pub fn select_page_down(
13220 &mut self,
13221 _: &SelectPageDown,
13222 window: &mut Window,
13223 cx: &mut Context<Self>,
13224 ) {
13225 let Some(row_count) = self.visible_row_count() else {
13226 return;
13227 };
13228
13229 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13230
13231 let text_layout_details = &self.text_layout_details(window);
13232
13233 self.change_selections(Default::default(), window, cx, |s| {
13234 s.move_heads_with(|map, head, goal| {
13235 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13236 })
13237 })
13238 }
13239
13240 pub fn move_page_down(
13241 &mut self,
13242 action: &MovePageDown,
13243 window: &mut Window,
13244 cx: &mut Context<Self>,
13245 ) {
13246 if self.take_rename(true, window, cx).is_some() {
13247 return;
13248 }
13249
13250 if self
13251 .context_menu
13252 .borrow_mut()
13253 .as_mut()
13254 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13255 .unwrap_or(false)
13256 {
13257 return;
13258 }
13259
13260 if matches!(self.mode, EditorMode::SingleLine) {
13261 cx.propagate();
13262 return;
13263 }
13264
13265 let Some(row_count) = self.visible_row_count() else {
13266 return;
13267 };
13268
13269 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13270
13271 let effects = if action.center_cursor {
13272 SelectionEffects::scroll(Autoscroll::center())
13273 } else {
13274 SelectionEffects::default()
13275 };
13276
13277 let text_layout_details = &self.text_layout_details(window);
13278 self.change_selections(effects, window, cx, |s| {
13279 s.move_with(|map, selection| {
13280 if !selection.is_empty() {
13281 selection.goal = SelectionGoal::None;
13282 }
13283 let (cursor, goal) = movement::down_by_rows(
13284 map,
13285 selection.end,
13286 row_count,
13287 selection.goal,
13288 false,
13289 text_layout_details,
13290 );
13291 selection.collapse_to(cursor, goal);
13292 });
13293 });
13294 }
13295
13296 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13297 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13298 let text_layout_details = &self.text_layout_details(window);
13299 self.change_selections(Default::default(), window, cx, |s| {
13300 s.move_heads_with(|map, head, goal| {
13301 movement::down(map, head, goal, false, text_layout_details)
13302 })
13303 });
13304 }
13305
13306 pub fn context_menu_first(
13307 &mut self,
13308 _: &ContextMenuFirst,
13309 window: &mut Window,
13310 cx: &mut Context<Self>,
13311 ) {
13312 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13313 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13314 }
13315 }
13316
13317 pub fn context_menu_prev(
13318 &mut self,
13319 _: &ContextMenuPrevious,
13320 window: &mut Window,
13321 cx: &mut Context<Self>,
13322 ) {
13323 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13324 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13325 }
13326 }
13327
13328 pub fn context_menu_next(
13329 &mut self,
13330 _: &ContextMenuNext,
13331 window: &mut Window,
13332 cx: &mut Context<Self>,
13333 ) {
13334 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13335 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13336 }
13337 }
13338
13339 pub fn context_menu_last(
13340 &mut self,
13341 _: &ContextMenuLast,
13342 window: &mut Window,
13343 cx: &mut Context<Self>,
13344 ) {
13345 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13346 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13347 }
13348 }
13349
13350 pub fn signature_help_prev(
13351 &mut self,
13352 _: &SignatureHelpPrevious,
13353 _: &mut Window,
13354 cx: &mut Context<Self>,
13355 ) {
13356 if let Some(popover) = self.signature_help_state.popover_mut() {
13357 if popover.current_signature == 0 {
13358 popover.current_signature = popover.signatures.len() - 1;
13359 } else {
13360 popover.current_signature -= 1;
13361 }
13362 cx.notify();
13363 }
13364 }
13365
13366 pub fn signature_help_next(
13367 &mut self,
13368 _: &SignatureHelpNext,
13369 _: &mut Window,
13370 cx: &mut Context<Self>,
13371 ) {
13372 if let Some(popover) = self.signature_help_state.popover_mut() {
13373 if popover.current_signature + 1 == popover.signatures.len() {
13374 popover.current_signature = 0;
13375 } else {
13376 popover.current_signature += 1;
13377 }
13378 cx.notify();
13379 }
13380 }
13381
13382 pub fn move_to_previous_word_start(
13383 &mut self,
13384 _: &MoveToPreviousWordStart,
13385 window: &mut Window,
13386 cx: &mut Context<Self>,
13387 ) {
13388 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13389 self.change_selections(Default::default(), window, cx, |s| {
13390 s.move_cursors_with(|map, head, _| {
13391 (
13392 movement::previous_word_start(map, head),
13393 SelectionGoal::None,
13394 )
13395 });
13396 })
13397 }
13398
13399 pub fn move_to_previous_subword_start(
13400 &mut self,
13401 _: &MoveToPreviousSubwordStart,
13402 window: &mut Window,
13403 cx: &mut Context<Self>,
13404 ) {
13405 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13406 self.change_selections(Default::default(), window, cx, |s| {
13407 s.move_cursors_with(|map, head, _| {
13408 (
13409 movement::previous_subword_start(map, head),
13410 SelectionGoal::None,
13411 )
13412 });
13413 })
13414 }
13415
13416 pub fn select_to_previous_word_start(
13417 &mut self,
13418 _: &SelectToPreviousWordStart,
13419 window: &mut Window,
13420 cx: &mut Context<Self>,
13421 ) {
13422 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13423 self.change_selections(Default::default(), window, cx, |s| {
13424 s.move_heads_with(|map, head, _| {
13425 (
13426 movement::previous_word_start(map, head),
13427 SelectionGoal::None,
13428 )
13429 });
13430 })
13431 }
13432
13433 pub fn select_to_previous_subword_start(
13434 &mut self,
13435 _: &SelectToPreviousSubwordStart,
13436 window: &mut Window,
13437 cx: &mut Context<Self>,
13438 ) {
13439 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13440 self.change_selections(Default::default(), window, cx, |s| {
13441 s.move_heads_with(|map, head, _| {
13442 (
13443 movement::previous_subword_start(map, head),
13444 SelectionGoal::None,
13445 )
13446 });
13447 })
13448 }
13449
13450 pub fn delete_to_previous_word_start(
13451 &mut self,
13452 action: &DeleteToPreviousWordStart,
13453 window: &mut Window,
13454 cx: &mut Context<Self>,
13455 ) {
13456 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13457 self.transact(window, cx, |this, window, cx| {
13458 this.select_autoclose_pair(window, cx);
13459 this.change_selections(Default::default(), window, cx, |s| {
13460 s.move_with(|map, selection| {
13461 if selection.is_empty() {
13462 let mut cursor = if action.ignore_newlines {
13463 movement::previous_word_start(map, selection.head())
13464 } else {
13465 movement::previous_word_start_or_newline(map, selection.head())
13466 };
13467 cursor = movement::adjust_greedy_deletion(
13468 map,
13469 selection.head(),
13470 cursor,
13471 action.ignore_brackets,
13472 );
13473 selection.set_head(cursor, SelectionGoal::None);
13474 }
13475 });
13476 });
13477 this.insert("", window, cx);
13478 });
13479 }
13480
13481 pub fn delete_to_previous_subword_start(
13482 &mut self,
13483 _: &DeleteToPreviousSubwordStart,
13484 window: &mut Window,
13485 cx: &mut Context<Self>,
13486 ) {
13487 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13488 self.transact(window, cx, |this, window, cx| {
13489 this.select_autoclose_pair(window, cx);
13490 this.change_selections(Default::default(), window, cx, |s| {
13491 s.move_with(|map, selection| {
13492 if selection.is_empty() {
13493 let mut cursor = movement::previous_subword_start(map, selection.head());
13494 cursor =
13495 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13496 selection.set_head(cursor, SelectionGoal::None);
13497 }
13498 });
13499 });
13500 this.insert("", window, cx);
13501 });
13502 }
13503
13504 pub fn move_to_next_word_end(
13505 &mut self,
13506 _: &MoveToNextWordEnd,
13507 window: &mut Window,
13508 cx: &mut Context<Self>,
13509 ) {
13510 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13511 self.change_selections(Default::default(), window, cx, |s| {
13512 s.move_cursors_with(|map, head, _| {
13513 (movement::next_word_end(map, head), SelectionGoal::None)
13514 });
13515 })
13516 }
13517
13518 pub fn move_to_next_subword_end(
13519 &mut self,
13520 _: &MoveToNextSubwordEnd,
13521 window: &mut Window,
13522 cx: &mut Context<Self>,
13523 ) {
13524 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13525 self.change_selections(Default::default(), window, cx, |s| {
13526 s.move_cursors_with(|map, head, _| {
13527 (movement::next_subword_end(map, head), SelectionGoal::None)
13528 });
13529 })
13530 }
13531
13532 pub fn select_to_next_word_end(
13533 &mut self,
13534 _: &SelectToNextWordEnd,
13535 window: &mut Window,
13536 cx: &mut Context<Self>,
13537 ) {
13538 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13539 self.change_selections(Default::default(), window, cx, |s| {
13540 s.move_heads_with(|map, head, _| {
13541 (movement::next_word_end(map, head), SelectionGoal::None)
13542 });
13543 })
13544 }
13545
13546 pub fn select_to_next_subword_end(
13547 &mut self,
13548 _: &SelectToNextSubwordEnd,
13549 window: &mut Window,
13550 cx: &mut Context<Self>,
13551 ) {
13552 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13553 self.change_selections(Default::default(), window, cx, |s| {
13554 s.move_heads_with(|map, head, _| {
13555 (movement::next_subword_end(map, head), SelectionGoal::None)
13556 });
13557 })
13558 }
13559
13560 pub fn delete_to_next_word_end(
13561 &mut self,
13562 action: &DeleteToNextWordEnd,
13563 window: &mut Window,
13564 cx: &mut Context<Self>,
13565 ) {
13566 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13567 self.transact(window, cx, |this, window, cx| {
13568 this.change_selections(Default::default(), window, cx, |s| {
13569 s.move_with(|map, selection| {
13570 if selection.is_empty() {
13571 let mut cursor = if action.ignore_newlines {
13572 movement::next_word_end(map, selection.head())
13573 } else {
13574 movement::next_word_end_or_newline(map, selection.head())
13575 };
13576 cursor = movement::adjust_greedy_deletion(
13577 map,
13578 selection.head(),
13579 cursor,
13580 action.ignore_brackets,
13581 );
13582 selection.set_head(cursor, SelectionGoal::None);
13583 }
13584 });
13585 });
13586 this.insert("", window, cx);
13587 });
13588 }
13589
13590 pub fn delete_to_next_subword_end(
13591 &mut self,
13592 _: &DeleteToNextSubwordEnd,
13593 window: &mut Window,
13594 cx: &mut Context<Self>,
13595 ) {
13596 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13597 self.transact(window, cx, |this, window, cx| {
13598 this.change_selections(Default::default(), window, cx, |s| {
13599 s.move_with(|map, selection| {
13600 if selection.is_empty() {
13601 let mut cursor = movement::next_subword_end(map, selection.head());
13602 cursor =
13603 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13604 selection.set_head(cursor, SelectionGoal::None);
13605 }
13606 });
13607 });
13608 this.insert("", window, cx);
13609 });
13610 }
13611
13612 pub fn move_to_beginning_of_line(
13613 &mut self,
13614 action: &MoveToBeginningOfLine,
13615 window: &mut Window,
13616 cx: &mut Context<Self>,
13617 ) {
13618 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13619 self.change_selections(Default::default(), window, cx, |s| {
13620 s.move_cursors_with(|map, head, _| {
13621 (
13622 movement::indented_line_beginning(
13623 map,
13624 head,
13625 action.stop_at_soft_wraps,
13626 action.stop_at_indent,
13627 ),
13628 SelectionGoal::None,
13629 )
13630 });
13631 })
13632 }
13633
13634 pub fn select_to_beginning_of_line(
13635 &mut self,
13636 action: &SelectToBeginningOfLine,
13637 window: &mut Window,
13638 cx: &mut Context<Self>,
13639 ) {
13640 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13641 self.change_selections(Default::default(), window, cx, |s| {
13642 s.move_heads_with(|map, head, _| {
13643 (
13644 movement::indented_line_beginning(
13645 map,
13646 head,
13647 action.stop_at_soft_wraps,
13648 action.stop_at_indent,
13649 ),
13650 SelectionGoal::None,
13651 )
13652 });
13653 });
13654 }
13655
13656 pub fn delete_to_beginning_of_line(
13657 &mut self,
13658 action: &DeleteToBeginningOfLine,
13659 window: &mut Window,
13660 cx: &mut Context<Self>,
13661 ) {
13662 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13663 self.transact(window, cx, |this, window, cx| {
13664 this.change_selections(Default::default(), window, cx, |s| {
13665 s.move_with(|_, selection| {
13666 selection.reversed = true;
13667 });
13668 });
13669
13670 this.select_to_beginning_of_line(
13671 &SelectToBeginningOfLine {
13672 stop_at_soft_wraps: false,
13673 stop_at_indent: action.stop_at_indent,
13674 },
13675 window,
13676 cx,
13677 );
13678 this.backspace(&Backspace, window, cx);
13679 });
13680 }
13681
13682 pub fn move_to_end_of_line(
13683 &mut self,
13684 action: &MoveToEndOfLine,
13685 window: &mut Window,
13686 cx: &mut Context<Self>,
13687 ) {
13688 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13689 self.change_selections(Default::default(), window, cx, |s| {
13690 s.move_cursors_with(|map, head, _| {
13691 (
13692 movement::line_end(map, head, action.stop_at_soft_wraps),
13693 SelectionGoal::None,
13694 )
13695 });
13696 })
13697 }
13698
13699 pub fn select_to_end_of_line(
13700 &mut self,
13701 action: &SelectToEndOfLine,
13702 window: &mut Window,
13703 cx: &mut Context<Self>,
13704 ) {
13705 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13706 self.change_selections(Default::default(), window, cx, |s| {
13707 s.move_heads_with(|map, head, _| {
13708 (
13709 movement::line_end(map, head, action.stop_at_soft_wraps),
13710 SelectionGoal::None,
13711 )
13712 });
13713 })
13714 }
13715
13716 pub fn delete_to_end_of_line(
13717 &mut self,
13718 _: &DeleteToEndOfLine,
13719 window: &mut Window,
13720 cx: &mut Context<Self>,
13721 ) {
13722 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13723 self.transact(window, cx, |this, window, cx| {
13724 this.select_to_end_of_line(
13725 &SelectToEndOfLine {
13726 stop_at_soft_wraps: false,
13727 },
13728 window,
13729 cx,
13730 );
13731 this.delete(&Delete, window, cx);
13732 });
13733 }
13734
13735 pub fn cut_to_end_of_line(
13736 &mut self,
13737 action: &CutToEndOfLine,
13738 window: &mut Window,
13739 cx: &mut Context<Self>,
13740 ) {
13741 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13742 self.transact(window, cx, |this, window, cx| {
13743 this.select_to_end_of_line(
13744 &SelectToEndOfLine {
13745 stop_at_soft_wraps: false,
13746 },
13747 window,
13748 cx,
13749 );
13750 if !action.stop_at_newlines {
13751 this.change_selections(Default::default(), window, cx, |s| {
13752 s.move_with(|_, sel| {
13753 if sel.is_empty() {
13754 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13755 }
13756 });
13757 });
13758 }
13759 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13760 let item = this.cut_common(false, window, cx);
13761 cx.write_to_clipboard(item);
13762 });
13763 }
13764
13765 pub fn move_to_start_of_paragraph(
13766 &mut self,
13767 _: &MoveToStartOfParagraph,
13768 window: &mut Window,
13769 cx: &mut Context<Self>,
13770 ) {
13771 if matches!(self.mode, EditorMode::SingleLine) {
13772 cx.propagate();
13773 return;
13774 }
13775 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13776 self.change_selections(Default::default(), window, cx, |s| {
13777 s.move_with(|map, selection| {
13778 selection.collapse_to(
13779 movement::start_of_paragraph(map, selection.head(), 1),
13780 SelectionGoal::None,
13781 )
13782 });
13783 })
13784 }
13785
13786 pub fn move_to_end_of_paragraph(
13787 &mut self,
13788 _: &MoveToEndOfParagraph,
13789 window: &mut Window,
13790 cx: &mut Context<Self>,
13791 ) {
13792 if matches!(self.mode, EditorMode::SingleLine) {
13793 cx.propagate();
13794 return;
13795 }
13796 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13797 self.change_selections(Default::default(), window, cx, |s| {
13798 s.move_with(|map, selection| {
13799 selection.collapse_to(
13800 movement::end_of_paragraph(map, selection.head(), 1),
13801 SelectionGoal::None,
13802 )
13803 });
13804 })
13805 }
13806
13807 pub fn select_to_start_of_paragraph(
13808 &mut self,
13809 _: &SelectToStartOfParagraph,
13810 window: &mut Window,
13811 cx: &mut Context<Self>,
13812 ) {
13813 if matches!(self.mode, EditorMode::SingleLine) {
13814 cx.propagate();
13815 return;
13816 }
13817 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13818 self.change_selections(Default::default(), window, cx, |s| {
13819 s.move_heads_with(|map, head, _| {
13820 (
13821 movement::start_of_paragraph(map, head, 1),
13822 SelectionGoal::None,
13823 )
13824 });
13825 })
13826 }
13827
13828 pub fn select_to_end_of_paragraph(
13829 &mut self,
13830 _: &SelectToEndOfParagraph,
13831 window: &mut Window,
13832 cx: &mut Context<Self>,
13833 ) {
13834 if matches!(self.mode, EditorMode::SingleLine) {
13835 cx.propagate();
13836 return;
13837 }
13838 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13839 self.change_selections(Default::default(), window, cx, |s| {
13840 s.move_heads_with(|map, head, _| {
13841 (
13842 movement::end_of_paragraph(map, head, 1),
13843 SelectionGoal::None,
13844 )
13845 });
13846 })
13847 }
13848
13849 pub fn move_to_start_of_excerpt(
13850 &mut self,
13851 _: &MoveToStartOfExcerpt,
13852 window: &mut Window,
13853 cx: &mut Context<Self>,
13854 ) {
13855 if matches!(self.mode, EditorMode::SingleLine) {
13856 cx.propagate();
13857 return;
13858 }
13859 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13860 self.change_selections(Default::default(), window, cx, |s| {
13861 s.move_with(|map, selection| {
13862 selection.collapse_to(
13863 movement::start_of_excerpt(
13864 map,
13865 selection.head(),
13866 workspace::searchable::Direction::Prev,
13867 ),
13868 SelectionGoal::None,
13869 )
13870 });
13871 })
13872 }
13873
13874 pub fn move_to_start_of_next_excerpt(
13875 &mut self,
13876 _: &MoveToStartOfNextExcerpt,
13877 window: &mut Window,
13878 cx: &mut Context<Self>,
13879 ) {
13880 if matches!(self.mode, EditorMode::SingleLine) {
13881 cx.propagate();
13882 return;
13883 }
13884
13885 self.change_selections(Default::default(), window, cx, |s| {
13886 s.move_with(|map, selection| {
13887 selection.collapse_to(
13888 movement::start_of_excerpt(
13889 map,
13890 selection.head(),
13891 workspace::searchable::Direction::Next,
13892 ),
13893 SelectionGoal::None,
13894 )
13895 });
13896 })
13897 }
13898
13899 pub fn move_to_end_of_excerpt(
13900 &mut self,
13901 _: &MoveToEndOfExcerpt,
13902 window: &mut Window,
13903 cx: &mut Context<Self>,
13904 ) {
13905 if matches!(self.mode, EditorMode::SingleLine) {
13906 cx.propagate();
13907 return;
13908 }
13909 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13910 self.change_selections(Default::default(), window, cx, |s| {
13911 s.move_with(|map, selection| {
13912 selection.collapse_to(
13913 movement::end_of_excerpt(
13914 map,
13915 selection.head(),
13916 workspace::searchable::Direction::Next,
13917 ),
13918 SelectionGoal::None,
13919 )
13920 });
13921 })
13922 }
13923
13924 pub fn move_to_end_of_previous_excerpt(
13925 &mut self,
13926 _: &MoveToEndOfPreviousExcerpt,
13927 window: &mut Window,
13928 cx: &mut Context<Self>,
13929 ) {
13930 if matches!(self.mode, EditorMode::SingleLine) {
13931 cx.propagate();
13932 return;
13933 }
13934 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13935 self.change_selections(Default::default(), window, cx, |s| {
13936 s.move_with(|map, selection| {
13937 selection.collapse_to(
13938 movement::end_of_excerpt(
13939 map,
13940 selection.head(),
13941 workspace::searchable::Direction::Prev,
13942 ),
13943 SelectionGoal::None,
13944 )
13945 });
13946 })
13947 }
13948
13949 pub fn select_to_start_of_excerpt(
13950 &mut self,
13951 _: &SelectToStartOfExcerpt,
13952 window: &mut Window,
13953 cx: &mut Context<Self>,
13954 ) {
13955 if matches!(self.mode, EditorMode::SingleLine) {
13956 cx.propagate();
13957 return;
13958 }
13959 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13960 self.change_selections(Default::default(), window, cx, |s| {
13961 s.move_heads_with(|map, head, _| {
13962 (
13963 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13964 SelectionGoal::None,
13965 )
13966 });
13967 })
13968 }
13969
13970 pub fn select_to_start_of_next_excerpt(
13971 &mut self,
13972 _: &SelectToStartOfNextExcerpt,
13973 window: &mut Window,
13974 cx: &mut Context<Self>,
13975 ) {
13976 if matches!(self.mode, EditorMode::SingleLine) {
13977 cx.propagate();
13978 return;
13979 }
13980 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13981 self.change_selections(Default::default(), window, cx, |s| {
13982 s.move_heads_with(|map, head, _| {
13983 (
13984 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13985 SelectionGoal::None,
13986 )
13987 });
13988 })
13989 }
13990
13991 pub fn select_to_end_of_excerpt(
13992 &mut self,
13993 _: &SelectToEndOfExcerpt,
13994 window: &mut Window,
13995 cx: &mut Context<Self>,
13996 ) {
13997 if matches!(self.mode, EditorMode::SingleLine) {
13998 cx.propagate();
13999 return;
14000 }
14001 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14002 self.change_selections(Default::default(), window, cx, |s| {
14003 s.move_heads_with(|map, head, _| {
14004 (
14005 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14006 SelectionGoal::None,
14007 )
14008 });
14009 })
14010 }
14011
14012 pub fn select_to_end_of_previous_excerpt(
14013 &mut self,
14014 _: &SelectToEndOfPreviousExcerpt,
14015 window: &mut Window,
14016 cx: &mut Context<Self>,
14017 ) {
14018 if matches!(self.mode, EditorMode::SingleLine) {
14019 cx.propagate();
14020 return;
14021 }
14022 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14023 self.change_selections(Default::default(), window, cx, |s| {
14024 s.move_heads_with(|map, head, _| {
14025 (
14026 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14027 SelectionGoal::None,
14028 )
14029 });
14030 })
14031 }
14032
14033 pub fn move_to_beginning(
14034 &mut self,
14035 _: &MoveToBeginning,
14036 window: &mut Window,
14037 cx: &mut Context<Self>,
14038 ) {
14039 if matches!(self.mode, EditorMode::SingleLine) {
14040 cx.propagate();
14041 return;
14042 }
14043 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14044 self.change_selections(Default::default(), window, cx, |s| {
14045 s.select_ranges(vec![0..0]);
14046 });
14047 }
14048
14049 pub fn select_to_beginning(
14050 &mut self,
14051 _: &SelectToBeginning,
14052 window: &mut Window,
14053 cx: &mut Context<Self>,
14054 ) {
14055 let mut selection = self.selections.last::<Point>(cx);
14056 selection.set_head(Point::zero(), SelectionGoal::None);
14057 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14058 self.change_selections(Default::default(), window, cx, |s| {
14059 s.select(vec![selection]);
14060 });
14061 }
14062
14063 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14064 if matches!(self.mode, EditorMode::SingleLine) {
14065 cx.propagate();
14066 return;
14067 }
14068 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14069 let cursor = self.buffer.read(cx).read(cx).len();
14070 self.change_selections(Default::default(), window, cx, |s| {
14071 s.select_ranges(vec![cursor..cursor])
14072 });
14073 }
14074
14075 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14076 self.nav_history = nav_history;
14077 }
14078
14079 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14080 self.nav_history.as_ref()
14081 }
14082
14083 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14084 self.push_to_nav_history(
14085 self.selections.newest_anchor().head(),
14086 None,
14087 false,
14088 true,
14089 cx,
14090 );
14091 }
14092
14093 fn push_to_nav_history(
14094 &mut self,
14095 cursor_anchor: Anchor,
14096 new_position: Option<Point>,
14097 is_deactivate: bool,
14098 always: bool,
14099 cx: &mut Context<Self>,
14100 ) {
14101 if let Some(nav_history) = self.nav_history.as_mut() {
14102 let buffer = self.buffer.read(cx).read(cx);
14103 let cursor_position = cursor_anchor.to_point(&buffer);
14104 let scroll_state = self.scroll_manager.anchor();
14105 let scroll_top_row = scroll_state.top_row(&buffer);
14106 drop(buffer);
14107
14108 if let Some(new_position) = new_position {
14109 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14110 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14111 return;
14112 }
14113 }
14114
14115 nav_history.push(
14116 Some(NavigationData {
14117 cursor_anchor,
14118 cursor_position,
14119 scroll_anchor: scroll_state,
14120 scroll_top_row,
14121 }),
14122 cx,
14123 );
14124 cx.emit(EditorEvent::PushedToNavHistory {
14125 anchor: cursor_anchor,
14126 is_deactivate,
14127 })
14128 }
14129 }
14130
14131 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14132 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14133 let buffer = self.buffer.read(cx).snapshot(cx);
14134 let mut selection = self.selections.first::<usize>(cx);
14135 selection.set_head(buffer.len(), SelectionGoal::None);
14136 self.change_selections(Default::default(), window, cx, |s| {
14137 s.select(vec![selection]);
14138 });
14139 }
14140
14141 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14142 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14143 let end = self.buffer.read(cx).read(cx).len();
14144 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14145 s.select_ranges(vec![0..end]);
14146 });
14147 }
14148
14149 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14150 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14151 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14152 let mut selections = self.selections.all::<Point>(cx);
14153 let max_point = display_map.buffer_snapshot().max_point();
14154 for selection in &mut selections {
14155 let rows = selection.spanned_rows(true, &display_map);
14156 selection.start = Point::new(rows.start.0, 0);
14157 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14158 selection.reversed = false;
14159 }
14160 self.change_selections(Default::default(), window, cx, |s| {
14161 s.select(selections);
14162 });
14163 }
14164
14165 pub fn split_selection_into_lines(
14166 &mut self,
14167 action: &SplitSelectionIntoLines,
14168 window: &mut Window,
14169 cx: &mut Context<Self>,
14170 ) {
14171 let selections = self
14172 .selections
14173 .all::<Point>(cx)
14174 .into_iter()
14175 .map(|selection| selection.start..selection.end)
14176 .collect::<Vec<_>>();
14177 self.unfold_ranges(&selections, true, true, cx);
14178
14179 let mut new_selection_ranges = Vec::new();
14180 {
14181 let buffer = self.buffer.read(cx).read(cx);
14182 for selection in selections {
14183 for row in selection.start.row..selection.end.row {
14184 let line_start = Point::new(row, 0);
14185 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14186
14187 if action.keep_selections {
14188 // Keep the selection range for each line
14189 let selection_start = if row == selection.start.row {
14190 selection.start
14191 } else {
14192 line_start
14193 };
14194 new_selection_ranges.push(selection_start..line_end);
14195 } else {
14196 // Collapse to cursor at end of line
14197 new_selection_ranges.push(line_end..line_end);
14198 }
14199 }
14200
14201 let is_multiline_selection = selection.start.row != selection.end.row;
14202 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14203 // so this action feels more ergonomic when paired with other selection operations
14204 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14205 if !should_skip_last {
14206 if action.keep_selections {
14207 if is_multiline_selection {
14208 let line_start = Point::new(selection.end.row, 0);
14209 new_selection_ranges.push(line_start..selection.end);
14210 } else {
14211 new_selection_ranges.push(selection.start..selection.end);
14212 }
14213 } else {
14214 new_selection_ranges.push(selection.end..selection.end);
14215 }
14216 }
14217 }
14218 }
14219 self.change_selections(Default::default(), window, cx, |s| {
14220 s.select_ranges(new_selection_ranges);
14221 });
14222 }
14223
14224 pub fn add_selection_above(
14225 &mut self,
14226 action: &AddSelectionAbove,
14227 window: &mut Window,
14228 cx: &mut Context<Self>,
14229 ) {
14230 self.add_selection(true, action.skip_soft_wrap, window, cx);
14231 }
14232
14233 pub fn add_selection_below(
14234 &mut self,
14235 action: &AddSelectionBelow,
14236 window: &mut Window,
14237 cx: &mut Context<Self>,
14238 ) {
14239 self.add_selection(false, action.skip_soft_wrap, window, cx);
14240 }
14241
14242 fn add_selection(
14243 &mut self,
14244 above: bool,
14245 skip_soft_wrap: bool,
14246 window: &mut Window,
14247 cx: &mut Context<Self>,
14248 ) {
14249 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14250
14251 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14252 let all_selections = self.selections.all::<Point>(cx);
14253 let text_layout_details = self.text_layout_details(window);
14254
14255 let (mut columnar_selections, new_selections_to_columnarize) = {
14256 if let Some(state) = self.add_selections_state.as_ref() {
14257 let columnar_selection_ids: HashSet<_> = state
14258 .groups
14259 .iter()
14260 .flat_map(|group| group.stack.iter())
14261 .copied()
14262 .collect();
14263
14264 all_selections
14265 .into_iter()
14266 .partition(|s| columnar_selection_ids.contains(&s.id))
14267 } else {
14268 (Vec::new(), all_selections)
14269 }
14270 };
14271
14272 let mut state = self
14273 .add_selections_state
14274 .take()
14275 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14276
14277 for selection in new_selections_to_columnarize {
14278 let range = selection.display_range(&display_map).sorted();
14279 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14280 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14281 let positions = start_x.min(end_x)..start_x.max(end_x);
14282 let mut stack = Vec::new();
14283 for row in range.start.row().0..=range.end.row().0 {
14284 if let Some(selection) = self.selections.build_columnar_selection(
14285 &display_map,
14286 DisplayRow(row),
14287 &positions,
14288 selection.reversed,
14289 &text_layout_details,
14290 ) {
14291 stack.push(selection.id);
14292 columnar_selections.push(selection);
14293 }
14294 }
14295 if !stack.is_empty() {
14296 if above {
14297 stack.reverse();
14298 }
14299 state.groups.push(AddSelectionsGroup { above, stack });
14300 }
14301 }
14302
14303 let mut final_selections = Vec::new();
14304 let end_row = if above {
14305 DisplayRow(0)
14306 } else {
14307 display_map.max_point().row()
14308 };
14309
14310 let mut last_added_item_per_group = HashMap::default();
14311 for group in state.groups.iter_mut() {
14312 if let Some(last_id) = group.stack.last() {
14313 last_added_item_per_group.insert(*last_id, group);
14314 }
14315 }
14316
14317 for selection in columnar_selections {
14318 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14319 if above == group.above {
14320 let range = selection.display_range(&display_map).sorted();
14321 debug_assert_eq!(range.start.row(), range.end.row());
14322 let mut row = range.start.row();
14323 let positions =
14324 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14325 Pixels::from(start)..Pixels::from(end)
14326 } else {
14327 let start_x =
14328 display_map.x_for_display_point(range.start, &text_layout_details);
14329 let end_x =
14330 display_map.x_for_display_point(range.end, &text_layout_details);
14331 start_x.min(end_x)..start_x.max(end_x)
14332 };
14333
14334 let mut maybe_new_selection = None;
14335 let direction = if above { -1 } else { 1 };
14336
14337 while row != end_row {
14338 if skip_soft_wrap {
14339 row = display_map
14340 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14341 .row();
14342 } else if above {
14343 row.0 -= 1;
14344 } else {
14345 row.0 += 1;
14346 }
14347
14348 if let Some(new_selection) = self.selections.build_columnar_selection(
14349 &display_map,
14350 row,
14351 &positions,
14352 selection.reversed,
14353 &text_layout_details,
14354 ) {
14355 maybe_new_selection = Some(new_selection);
14356 break;
14357 }
14358 }
14359
14360 if let Some(new_selection) = maybe_new_selection {
14361 group.stack.push(new_selection.id);
14362 if above {
14363 final_selections.push(new_selection);
14364 final_selections.push(selection);
14365 } else {
14366 final_selections.push(selection);
14367 final_selections.push(new_selection);
14368 }
14369 } else {
14370 final_selections.push(selection);
14371 }
14372 } else {
14373 group.stack.pop();
14374 }
14375 } else {
14376 final_selections.push(selection);
14377 }
14378 }
14379
14380 self.change_selections(Default::default(), window, cx, |s| {
14381 s.select(final_selections);
14382 });
14383
14384 let final_selection_ids: HashSet<_> = self
14385 .selections
14386 .all::<Point>(cx)
14387 .iter()
14388 .map(|s| s.id)
14389 .collect();
14390 state.groups.retain_mut(|group| {
14391 // selections might get merged above so we remove invalid items from stacks
14392 group.stack.retain(|id| final_selection_ids.contains(id));
14393
14394 // single selection in stack can be treated as initial state
14395 group.stack.len() > 1
14396 });
14397
14398 if !state.groups.is_empty() {
14399 self.add_selections_state = Some(state);
14400 }
14401 }
14402
14403 fn select_match_ranges(
14404 &mut self,
14405 range: Range<usize>,
14406 reversed: bool,
14407 replace_newest: bool,
14408 auto_scroll: Option<Autoscroll>,
14409 window: &mut Window,
14410 cx: &mut Context<Editor>,
14411 ) {
14412 self.unfold_ranges(
14413 std::slice::from_ref(&range),
14414 false,
14415 auto_scroll.is_some(),
14416 cx,
14417 );
14418 let effects = if let Some(scroll) = auto_scroll {
14419 SelectionEffects::scroll(scroll)
14420 } else {
14421 SelectionEffects::no_scroll()
14422 };
14423 self.change_selections(effects, window, cx, |s| {
14424 if replace_newest {
14425 s.delete(s.newest_anchor().id);
14426 }
14427 if reversed {
14428 s.insert_range(range.end..range.start);
14429 } else {
14430 s.insert_range(range);
14431 }
14432 });
14433 }
14434
14435 pub fn select_next_match_internal(
14436 &mut self,
14437 display_map: &DisplaySnapshot,
14438 replace_newest: bool,
14439 autoscroll: Option<Autoscroll>,
14440 window: &mut Window,
14441 cx: &mut Context<Self>,
14442 ) -> Result<()> {
14443 let buffer = display_map.buffer_snapshot();
14444 let mut selections = self.selections.all::<usize>(cx);
14445 if let Some(mut select_next_state) = self.select_next_state.take() {
14446 let query = &select_next_state.query;
14447 if !select_next_state.done {
14448 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14449 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14450 let mut next_selected_range = None;
14451
14452 // Collect and sort selection ranges for efficient overlap checking
14453 let mut selection_ranges: Vec<_> = selections.iter().map(|s| s.range()).collect();
14454 selection_ranges.sort_by_key(|r| r.start);
14455
14456 let bytes_after_last_selection =
14457 buffer.bytes_in_range(last_selection.end..buffer.len());
14458 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14459 let query_matches = query
14460 .stream_find_iter(bytes_after_last_selection)
14461 .map(|result| (last_selection.end, result))
14462 .chain(
14463 query
14464 .stream_find_iter(bytes_before_first_selection)
14465 .map(|result| (0, result)),
14466 );
14467
14468 for (start_offset, query_match) in query_matches {
14469 let query_match = query_match.unwrap(); // can only fail due to I/O
14470 let offset_range =
14471 start_offset + query_match.start()..start_offset + query_match.end();
14472
14473 if !select_next_state.wordwise
14474 || (!buffer.is_inside_word(offset_range.start, None)
14475 && !buffer.is_inside_word(offset_range.end, None))
14476 {
14477 // Use binary search to check for overlap (O(log n))
14478 let overlaps = selection_ranges
14479 .binary_search_by(|range| {
14480 if range.end <= offset_range.start {
14481 std::cmp::Ordering::Less
14482 } else if range.start >= offset_range.end {
14483 std::cmp::Ordering::Greater
14484 } else {
14485 std::cmp::Ordering::Equal
14486 }
14487 })
14488 .is_ok();
14489
14490 if !overlaps {
14491 next_selected_range = Some(offset_range);
14492 break;
14493 }
14494 }
14495 }
14496
14497 if let Some(next_selected_range) = next_selected_range {
14498 self.select_match_ranges(
14499 next_selected_range,
14500 last_selection.reversed,
14501 replace_newest,
14502 autoscroll,
14503 window,
14504 cx,
14505 );
14506 } else {
14507 select_next_state.done = true;
14508 }
14509 }
14510
14511 self.select_next_state = Some(select_next_state);
14512 } else {
14513 let mut only_carets = true;
14514 let mut same_text_selected = true;
14515 let mut selected_text = None;
14516
14517 let mut selections_iter = selections.iter().peekable();
14518 while let Some(selection) = selections_iter.next() {
14519 if selection.start != selection.end {
14520 only_carets = false;
14521 }
14522
14523 if same_text_selected {
14524 if selected_text.is_none() {
14525 selected_text =
14526 Some(buffer.text_for_range(selection.range()).collect::<String>());
14527 }
14528
14529 if let Some(next_selection) = selections_iter.peek() {
14530 if next_selection.range().len() == selection.range().len() {
14531 let next_selected_text = buffer
14532 .text_for_range(next_selection.range())
14533 .collect::<String>();
14534 if Some(next_selected_text) != selected_text {
14535 same_text_selected = false;
14536 selected_text = None;
14537 }
14538 } else {
14539 same_text_selected = false;
14540 selected_text = None;
14541 }
14542 }
14543 }
14544 }
14545
14546 if only_carets {
14547 for selection in &mut selections {
14548 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14549 selection.start = word_range.start;
14550 selection.end = word_range.end;
14551 selection.goal = SelectionGoal::None;
14552 selection.reversed = false;
14553 self.select_match_ranges(
14554 selection.start..selection.end,
14555 selection.reversed,
14556 replace_newest,
14557 autoscroll,
14558 window,
14559 cx,
14560 );
14561 }
14562
14563 if selections.len() == 1 {
14564 let selection = selections
14565 .last()
14566 .expect("ensured that there's only one selection");
14567 let query = buffer
14568 .text_for_range(selection.start..selection.end)
14569 .collect::<String>();
14570 let is_empty = query.is_empty();
14571 let select_state = SelectNextState {
14572 query: AhoCorasick::new(&[query])?,
14573 wordwise: true,
14574 done: is_empty,
14575 };
14576 self.select_next_state = Some(select_state);
14577 } else {
14578 self.select_next_state = None;
14579 }
14580 } else if let Some(selected_text) = selected_text {
14581 self.select_next_state = Some(SelectNextState {
14582 query: AhoCorasick::new(&[selected_text])?,
14583 wordwise: false,
14584 done: false,
14585 });
14586 self.select_next_match_internal(
14587 display_map,
14588 replace_newest,
14589 autoscroll,
14590 window,
14591 cx,
14592 )?;
14593 }
14594 }
14595 Ok(())
14596 }
14597
14598 pub fn select_all_matches(
14599 &mut self,
14600 _action: &SelectAllMatches,
14601 window: &mut Window,
14602 cx: &mut Context<Self>,
14603 ) -> Result<()> {
14604 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14605
14606 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14607
14608 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14609 let Some(select_next_state) = self.select_next_state.as_mut() else {
14610 return Ok(());
14611 };
14612 if select_next_state.done {
14613 return Ok(());
14614 }
14615
14616 let mut new_selections = Vec::new();
14617
14618 let reversed = self.selections.oldest::<usize>(cx).reversed;
14619 let buffer = display_map.buffer_snapshot();
14620 let query_matches = select_next_state
14621 .query
14622 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14623
14624 for query_match in query_matches.into_iter() {
14625 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14626 let offset_range = if reversed {
14627 query_match.end()..query_match.start()
14628 } else {
14629 query_match.start()..query_match.end()
14630 };
14631
14632 if !select_next_state.wordwise
14633 || (!buffer.is_inside_word(offset_range.start, None)
14634 && !buffer.is_inside_word(offset_range.end, None))
14635 {
14636 new_selections.push(offset_range.start..offset_range.end);
14637 }
14638 }
14639
14640 select_next_state.done = true;
14641
14642 if new_selections.is_empty() {
14643 log::error!("bug: new_selections is empty in select_all_matches");
14644 return Ok(());
14645 }
14646
14647 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14648 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14649 selections.select_ranges(new_selections)
14650 });
14651
14652 Ok(())
14653 }
14654
14655 pub fn select_next(
14656 &mut self,
14657 action: &SelectNext,
14658 window: &mut Window,
14659 cx: &mut Context<Self>,
14660 ) -> Result<()> {
14661 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14662 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14663 self.select_next_match_internal(
14664 &display_map,
14665 action.replace_newest,
14666 Some(Autoscroll::newest()),
14667 window,
14668 cx,
14669 )?;
14670 Ok(())
14671 }
14672
14673 pub fn select_previous(
14674 &mut self,
14675 action: &SelectPrevious,
14676 window: &mut Window,
14677 cx: &mut Context<Self>,
14678 ) -> Result<()> {
14679 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14680 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14681 let buffer = display_map.buffer_snapshot();
14682 let mut selections = self.selections.all::<usize>(cx);
14683 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14684 let query = &select_prev_state.query;
14685 if !select_prev_state.done {
14686 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14687 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14688 let mut next_selected_range = None;
14689 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14690 let bytes_before_last_selection =
14691 buffer.reversed_bytes_in_range(0..last_selection.start);
14692 let bytes_after_first_selection =
14693 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14694 let query_matches = query
14695 .stream_find_iter(bytes_before_last_selection)
14696 .map(|result| (last_selection.start, result))
14697 .chain(
14698 query
14699 .stream_find_iter(bytes_after_first_selection)
14700 .map(|result| (buffer.len(), result)),
14701 );
14702 for (end_offset, query_match) in query_matches {
14703 let query_match = query_match.unwrap(); // can only fail due to I/O
14704 let offset_range =
14705 end_offset - query_match.end()..end_offset - query_match.start();
14706
14707 if !select_prev_state.wordwise
14708 || (!buffer.is_inside_word(offset_range.start, None)
14709 && !buffer.is_inside_word(offset_range.end, None))
14710 {
14711 next_selected_range = Some(offset_range);
14712 break;
14713 }
14714 }
14715
14716 if let Some(next_selected_range) = next_selected_range {
14717 self.select_match_ranges(
14718 next_selected_range,
14719 last_selection.reversed,
14720 action.replace_newest,
14721 Some(Autoscroll::newest()),
14722 window,
14723 cx,
14724 );
14725 } else {
14726 select_prev_state.done = true;
14727 }
14728 }
14729
14730 self.select_prev_state = Some(select_prev_state);
14731 } else {
14732 let mut only_carets = true;
14733 let mut same_text_selected = true;
14734 let mut selected_text = None;
14735
14736 let mut selections_iter = selections.iter().peekable();
14737 while let Some(selection) = selections_iter.next() {
14738 if selection.start != selection.end {
14739 only_carets = false;
14740 }
14741
14742 if same_text_selected {
14743 if selected_text.is_none() {
14744 selected_text =
14745 Some(buffer.text_for_range(selection.range()).collect::<String>());
14746 }
14747
14748 if let Some(next_selection) = selections_iter.peek() {
14749 if next_selection.range().len() == selection.range().len() {
14750 let next_selected_text = buffer
14751 .text_for_range(next_selection.range())
14752 .collect::<String>();
14753 if Some(next_selected_text) != selected_text {
14754 same_text_selected = false;
14755 selected_text = None;
14756 }
14757 } else {
14758 same_text_selected = false;
14759 selected_text = None;
14760 }
14761 }
14762 }
14763 }
14764
14765 if only_carets {
14766 for selection in &mut selections {
14767 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14768 selection.start = word_range.start;
14769 selection.end = word_range.end;
14770 selection.goal = SelectionGoal::None;
14771 selection.reversed = false;
14772 self.select_match_ranges(
14773 selection.start..selection.end,
14774 selection.reversed,
14775 action.replace_newest,
14776 Some(Autoscroll::newest()),
14777 window,
14778 cx,
14779 );
14780 }
14781 if selections.len() == 1 {
14782 let selection = selections
14783 .last()
14784 .expect("ensured that there's only one selection");
14785 let query = buffer
14786 .text_for_range(selection.start..selection.end)
14787 .collect::<String>();
14788 let is_empty = query.is_empty();
14789 let select_state = SelectNextState {
14790 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14791 wordwise: true,
14792 done: is_empty,
14793 };
14794 self.select_prev_state = Some(select_state);
14795 } else {
14796 self.select_prev_state = None;
14797 }
14798 } else if let Some(selected_text) = selected_text {
14799 self.select_prev_state = Some(SelectNextState {
14800 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14801 wordwise: false,
14802 done: false,
14803 });
14804 self.select_previous(action, window, cx)?;
14805 }
14806 }
14807 Ok(())
14808 }
14809
14810 pub fn find_next_match(
14811 &mut self,
14812 _: &FindNextMatch,
14813 window: &mut Window,
14814 cx: &mut Context<Self>,
14815 ) -> Result<()> {
14816 let selections = self.selections.disjoint_anchors_arc();
14817 match selections.first() {
14818 Some(first) if selections.len() >= 2 => {
14819 self.change_selections(Default::default(), window, cx, |s| {
14820 s.select_ranges([first.range()]);
14821 });
14822 }
14823 _ => self.select_next(
14824 &SelectNext {
14825 replace_newest: true,
14826 },
14827 window,
14828 cx,
14829 )?,
14830 }
14831 Ok(())
14832 }
14833
14834 pub fn find_previous_match(
14835 &mut self,
14836 _: &FindPreviousMatch,
14837 window: &mut Window,
14838 cx: &mut Context<Self>,
14839 ) -> Result<()> {
14840 let selections = self.selections.disjoint_anchors_arc();
14841 match selections.last() {
14842 Some(last) if selections.len() >= 2 => {
14843 self.change_selections(Default::default(), window, cx, |s| {
14844 s.select_ranges([last.range()]);
14845 });
14846 }
14847 _ => self.select_previous(
14848 &SelectPrevious {
14849 replace_newest: true,
14850 },
14851 window,
14852 cx,
14853 )?,
14854 }
14855 Ok(())
14856 }
14857
14858 pub fn toggle_comments(
14859 &mut self,
14860 action: &ToggleComments,
14861 window: &mut Window,
14862 cx: &mut Context<Self>,
14863 ) {
14864 if self.read_only(cx) {
14865 return;
14866 }
14867 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14868 let text_layout_details = &self.text_layout_details(window);
14869 self.transact(window, cx, |this, window, cx| {
14870 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14871 let mut edits = Vec::new();
14872 let mut selection_edit_ranges = Vec::new();
14873 let mut last_toggled_row = None;
14874 let snapshot = this.buffer.read(cx).read(cx);
14875 let empty_str: Arc<str> = Arc::default();
14876 let mut suffixes_inserted = Vec::new();
14877 let ignore_indent = action.ignore_indent;
14878
14879 fn comment_prefix_range(
14880 snapshot: &MultiBufferSnapshot,
14881 row: MultiBufferRow,
14882 comment_prefix: &str,
14883 comment_prefix_whitespace: &str,
14884 ignore_indent: bool,
14885 ) -> Range<Point> {
14886 let indent_size = if ignore_indent {
14887 0
14888 } else {
14889 snapshot.indent_size_for_line(row).len
14890 };
14891
14892 let start = Point::new(row.0, indent_size);
14893
14894 let mut line_bytes = snapshot
14895 .bytes_in_range(start..snapshot.max_point())
14896 .flatten()
14897 .copied();
14898
14899 // If this line currently begins with the line comment prefix, then record
14900 // the range containing the prefix.
14901 if line_bytes
14902 .by_ref()
14903 .take(comment_prefix.len())
14904 .eq(comment_prefix.bytes())
14905 {
14906 // Include any whitespace that matches the comment prefix.
14907 let matching_whitespace_len = line_bytes
14908 .zip(comment_prefix_whitespace.bytes())
14909 .take_while(|(a, b)| a == b)
14910 .count() as u32;
14911 let end = Point::new(
14912 start.row,
14913 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14914 );
14915 start..end
14916 } else {
14917 start..start
14918 }
14919 }
14920
14921 fn comment_suffix_range(
14922 snapshot: &MultiBufferSnapshot,
14923 row: MultiBufferRow,
14924 comment_suffix: &str,
14925 comment_suffix_has_leading_space: bool,
14926 ) -> Range<Point> {
14927 let end = Point::new(row.0, snapshot.line_len(row));
14928 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14929
14930 let mut line_end_bytes = snapshot
14931 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14932 .flatten()
14933 .copied();
14934
14935 let leading_space_len = if suffix_start_column > 0
14936 && line_end_bytes.next() == Some(b' ')
14937 && comment_suffix_has_leading_space
14938 {
14939 1
14940 } else {
14941 0
14942 };
14943
14944 // If this line currently begins with the line comment prefix, then record
14945 // the range containing the prefix.
14946 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14947 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14948 start..end
14949 } else {
14950 end..end
14951 }
14952 }
14953
14954 // TODO: Handle selections that cross excerpts
14955 for selection in &mut selections {
14956 let start_column = snapshot
14957 .indent_size_for_line(MultiBufferRow(selection.start.row))
14958 .len;
14959 let language = if let Some(language) =
14960 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14961 {
14962 language
14963 } else {
14964 continue;
14965 };
14966
14967 selection_edit_ranges.clear();
14968
14969 // If multiple selections contain a given row, avoid processing that
14970 // row more than once.
14971 let mut start_row = MultiBufferRow(selection.start.row);
14972 if last_toggled_row == Some(start_row) {
14973 start_row = start_row.next_row();
14974 }
14975 let end_row =
14976 if selection.end.row > selection.start.row && selection.end.column == 0 {
14977 MultiBufferRow(selection.end.row - 1)
14978 } else {
14979 MultiBufferRow(selection.end.row)
14980 };
14981 last_toggled_row = Some(end_row);
14982
14983 if start_row > end_row {
14984 continue;
14985 }
14986
14987 // If the language has line comments, toggle those.
14988 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14989
14990 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14991 if ignore_indent {
14992 full_comment_prefixes = full_comment_prefixes
14993 .into_iter()
14994 .map(|s| Arc::from(s.trim_end()))
14995 .collect();
14996 }
14997
14998 if !full_comment_prefixes.is_empty() {
14999 let first_prefix = full_comment_prefixes
15000 .first()
15001 .expect("prefixes is non-empty");
15002 let prefix_trimmed_lengths = full_comment_prefixes
15003 .iter()
15004 .map(|p| p.trim_end_matches(' ').len())
15005 .collect::<SmallVec<[usize; 4]>>();
15006
15007 let mut all_selection_lines_are_comments = true;
15008
15009 for row in start_row.0..=end_row.0 {
15010 let row = MultiBufferRow(row);
15011 if start_row < end_row && snapshot.is_line_blank(row) {
15012 continue;
15013 }
15014
15015 let prefix_range = full_comment_prefixes
15016 .iter()
15017 .zip(prefix_trimmed_lengths.iter().copied())
15018 .map(|(prefix, trimmed_prefix_len)| {
15019 comment_prefix_range(
15020 snapshot.deref(),
15021 row,
15022 &prefix[..trimmed_prefix_len],
15023 &prefix[trimmed_prefix_len..],
15024 ignore_indent,
15025 )
15026 })
15027 .max_by_key(|range| range.end.column - range.start.column)
15028 .expect("prefixes is non-empty");
15029
15030 if prefix_range.is_empty() {
15031 all_selection_lines_are_comments = false;
15032 }
15033
15034 selection_edit_ranges.push(prefix_range);
15035 }
15036
15037 if all_selection_lines_are_comments {
15038 edits.extend(
15039 selection_edit_ranges
15040 .iter()
15041 .cloned()
15042 .map(|range| (range, empty_str.clone())),
15043 );
15044 } else {
15045 let min_column = selection_edit_ranges
15046 .iter()
15047 .map(|range| range.start.column)
15048 .min()
15049 .unwrap_or(0);
15050 edits.extend(selection_edit_ranges.iter().map(|range| {
15051 let position = Point::new(range.start.row, min_column);
15052 (position..position, first_prefix.clone())
15053 }));
15054 }
15055 } else if let Some(BlockCommentConfig {
15056 start: full_comment_prefix,
15057 end: comment_suffix,
15058 ..
15059 }) = language.block_comment()
15060 {
15061 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15062 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15063 let prefix_range = comment_prefix_range(
15064 snapshot.deref(),
15065 start_row,
15066 comment_prefix,
15067 comment_prefix_whitespace,
15068 ignore_indent,
15069 );
15070 let suffix_range = comment_suffix_range(
15071 snapshot.deref(),
15072 end_row,
15073 comment_suffix.trim_start_matches(' '),
15074 comment_suffix.starts_with(' '),
15075 );
15076
15077 if prefix_range.is_empty() || suffix_range.is_empty() {
15078 edits.push((
15079 prefix_range.start..prefix_range.start,
15080 full_comment_prefix.clone(),
15081 ));
15082 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15083 suffixes_inserted.push((end_row, comment_suffix.len()));
15084 } else {
15085 edits.push((prefix_range, empty_str.clone()));
15086 edits.push((suffix_range, empty_str.clone()));
15087 }
15088 } else {
15089 continue;
15090 }
15091 }
15092
15093 drop(snapshot);
15094 this.buffer.update(cx, |buffer, cx| {
15095 buffer.edit(edits, None, cx);
15096 });
15097
15098 // Adjust selections so that they end before any comment suffixes that
15099 // were inserted.
15100 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15101 let mut selections = this.selections.all::<Point>(cx);
15102 let snapshot = this.buffer.read(cx).read(cx);
15103 for selection in &mut selections {
15104 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15105 match row.cmp(&MultiBufferRow(selection.end.row)) {
15106 Ordering::Less => {
15107 suffixes_inserted.next();
15108 continue;
15109 }
15110 Ordering::Greater => break,
15111 Ordering::Equal => {
15112 if selection.end.column == snapshot.line_len(row) {
15113 if selection.is_empty() {
15114 selection.start.column -= suffix_len as u32;
15115 }
15116 selection.end.column -= suffix_len as u32;
15117 }
15118 break;
15119 }
15120 }
15121 }
15122 }
15123
15124 drop(snapshot);
15125 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15126
15127 let selections = this.selections.all::<Point>(cx);
15128 let selections_on_single_row = selections.windows(2).all(|selections| {
15129 selections[0].start.row == selections[1].start.row
15130 && selections[0].end.row == selections[1].end.row
15131 && selections[0].start.row == selections[0].end.row
15132 });
15133 let selections_selecting = selections
15134 .iter()
15135 .any(|selection| selection.start != selection.end);
15136 let advance_downwards = action.advance_downwards
15137 && selections_on_single_row
15138 && !selections_selecting
15139 && !matches!(this.mode, EditorMode::SingleLine);
15140
15141 if advance_downwards {
15142 let snapshot = this.buffer.read(cx).snapshot(cx);
15143
15144 this.change_selections(Default::default(), window, cx, |s| {
15145 s.move_cursors_with(|display_snapshot, display_point, _| {
15146 let mut point = display_point.to_point(display_snapshot);
15147 point.row += 1;
15148 point = snapshot.clip_point(point, Bias::Left);
15149 let display_point = point.to_display_point(display_snapshot);
15150 let goal = SelectionGoal::HorizontalPosition(
15151 display_snapshot
15152 .x_for_display_point(display_point, text_layout_details)
15153 .into(),
15154 );
15155 (display_point, goal)
15156 })
15157 });
15158 }
15159 });
15160 }
15161
15162 pub fn select_enclosing_symbol(
15163 &mut self,
15164 _: &SelectEnclosingSymbol,
15165 window: &mut Window,
15166 cx: &mut Context<Self>,
15167 ) {
15168 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15169
15170 let buffer = self.buffer.read(cx).snapshot(cx);
15171 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
15172
15173 fn update_selection(
15174 selection: &Selection<usize>,
15175 buffer_snap: &MultiBufferSnapshot,
15176 ) -> Option<Selection<usize>> {
15177 let cursor = selection.head();
15178 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15179 for symbol in symbols.iter().rev() {
15180 let start = symbol.range.start.to_offset(buffer_snap);
15181 let end = symbol.range.end.to_offset(buffer_snap);
15182 let new_range = start..end;
15183 if start < selection.start || end > selection.end {
15184 return Some(Selection {
15185 id: selection.id,
15186 start: new_range.start,
15187 end: new_range.end,
15188 goal: SelectionGoal::None,
15189 reversed: selection.reversed,
15190 });
15191 }
15192 }
15193 None
15194 }
15195
15196 let mut selected_larger_symbol = false;
15197 let new_selections = old_selections
15198 .iter()
15199 .map(|selection| match update_selection(selection, &buffer) {
15200 Some(new_selection) => {
15201 if new_selection.range() != selection.range() {
15202 selected_larger_symbol = true;
15203 }
15204 new_selection
15205 }
15206 None => selection.clone(),
15207 })
15208 .collect::<Vec<_>>();
15209
15210 if selected_larger_symbol {
15211 self.change_selections(Default::default(), window, cx, |s| {
15212 s.select(new_selections);
15213 });
15214 }
15215 }
15216
15217 pub fn select_larger_syntax_node(
15218 &mut self,
15219 _: &SelectLargerSyntaxNode,
15220 window: &mut Window,
15221 cx: &mut Context<Self>,
15222 ) {
15223 let Some(visible_row_count) = self.visible_row_count() else {
15224 return;
15225 };
15226 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15227 if old_selections.is_empty() {
15228 return;
15229 }
15230
15231 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15232
15233 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15234 let buffer = self.buffer.read(cx).snapshot(cx);
15235
15236 let mut selected_larger_node = false;
15237 let mut new_selections = old_selections
15238 .iter()
15239 .map(|selection| {
15240 let old_range = selection.start..selection.end;
15241
15242 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15243 // manually select word at selection
15244 if ["string_content", "inline"].contains(&node.kind()) {
15245 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15246 // ignore if word is already selected
15247 if !word_range.is_empty() && old_range != word_range {
15248 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15249 // only select word if start and end point belongs to same word
15250 if word_range == last_word_range {
15251 selected_larger_node = true;
15252 return Selection {
15253 id: selection.id,
15254 start: word_range.start,
15255 end: word_range.end,
15256 goal: SelectionGoal::None,
15257 reversed: selection.reversed,
15258 };
15259 }
15260 }
15261 }
15262 }
15263
15264 let mut new_range = old_range.clone();
15265 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15266 new_range = range;
15267 if !node.is_named() {
15268 continue;
15269 }
15270 if !display_map.intersects_fold(new_range.start)
15271 && !display_map.intersects_fold(new_range.end)
15272 {
15273 break;
15274 }
15275 }
15276
15277 selected_larger_node |= new_range != old_range;
15278 Selection {
15279 id: selection.id,
15280 start: new_range.start,
15281 end: new_range.end,
15282 goal: SelectionGoal::None,
15283 reversed: selection.reversed,
15284 }
15285 })
15286 .collect::<Vec<_>>();
15287
15288 if !selected_larger_node {
15289 return; // don't put this call in the history
15290 }
15291
15292 // scroll based on transformation done to the last selection created by the user
15293 let (last_old, last_new) = old_selections
15294 .last()
15295 .zip(new_selections.last().cloned())
15296 .expect("old_selections isn't empty");
15297
15298 // revert selection
15299 let is_selection_reversed = {
15300 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15301 new_selections.last_mut().expect("checked above").reversed =
15302 should_newest_selection_be_reversed;
15303 should_newest_selection_be_reversed
15304 };
15305
15306 if selected_larger_node {
15307 self.select_syntax_node_history.disable_clearing = true;
15308 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15309 s.select(new_selections.clone());
15310 });
15311 self.select_syntax_node_history.disable_clearing = false;
15312 }
15313
15314 let start_row = last_new.start.to_display_point(&display_map).row().0;
15315 let end_row = last_new.end.to_display_point(&display_map).row().0;
15316 let selection_height = end_row - start_row + 1;
15317 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15318
15319 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15320 let scroll_behavior = if fits_on_the_screen {
15321 self.request_autoscroll(Autoscroll::fit(), cx);
15322 SelectSyntaxNodeScrollBehavior::FitSelection
15323 } else if is_selection_reversed {
15324 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15325 SelectSyntaxNodeScrollBehavior::CursorTop
15326 } else {
15327 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15328 SelectSyntaxNodeScrollBehavior::CursorBottom
15329 };
15330
15331 self.select_syntax_node_history.push((
15332 old_selections,
15333 scroll_behavior,
15334 is_selection_reversed,
15335 ));
15336 }
15337
15338 pub fn select_smaller_syntax_node(
15339 &mut self,
15340 _: &SelectSmallerSyntaxNode,
15341 window: &mut Window,
15342 cx: &mut Context<Self>,
15343 ) {
15344 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15345
15346 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15347 self.select_syntax_node_history.pop()
15348 {
15349 if let Some(selection) = selections.last_mut() {
15350 selection.reversed = is_selection_reversed;
15351 }
15352
15353 self.select_syntax_node_history.disable_clearing = true;
15354 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15355 s.select(selections.to_vec());
15356 });
15357 self.select_syntax_node_history.disable_clearing = false;
15358
15359 match scroll_behavior {
15360 SelectSyntaxNodeScrollBehavior::CursorTop => {
15361 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15362 }
15363 SelectSyntaxNodeScrollBehavior::FitSelection => {
15364 self.request_autoscroll(Autoscroll::fit(), cx);
15365 }
15366 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15367 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15368 }
15369 }
15370 }
15371 }
15372
15373 pub fn unwrap_syntax_node(
15374 &mut self,
15375 _: &UnwrapSyntaxNode,
15376 window: &mut Window,
15377 cx: &mut Context<Self>,
15378 ) {
15379 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15380
15381 let buffer = self.buffer.read(cx).snapshot(cx);
15382 let selections = self
15383 .selections
15384 .all::<usize>(cx)
15385 .into_iter()
15386 // subtracting the offset requires sorting
15387 .sorted_by_key(|i| i.start);
15388
15389 let full_edits = selections
15390 .into_iter()
15391 .filter_map(|selection| {
15392 let child = if selection.is_empty()
15393 && let Some((_, ancestor_range)) =
15394 buffer.syntax_ancestor(selection.start..selection.end)
15395 {
15396 ancestor_range
15397 } else {
15398 selection.range()
15399 };
15400
15401 let mut parent = child.clone();
15402 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15403 parent = ancestor_range;
15404 if parent.start < child.start || parent.end > child.end {
15405 break;
15406 }
15407 }
15408
15409 if parent == child {
15410 return None;
15411 }
15412 let text = buffer.text_for_range(child).collect::<String>();
15413 Some((selection.id, parent, text))
15414 })
15415 .collect::<Vec<_>>();
15416 if full_edits.is_empty() {
15417 return;
15418 }
15419
15420 self.transact(window, cx, |this, window, cx| {
15421 this.buffer.update(cx, |buffer, cx| {
15422 buffer.edit(
15423 full_edits
15424 .iter()
15425 .map(|(_, p, t)| (p.clone(), t.clone()))
15426 .collect::<Vec<_>>(),
15427 None,
15428 cx,
15429 );
15430 });
15431 this.change_selections(Default::default(), window, cx, |s| {
15432 let mut offset = 0;
15433 let mut selections = vec![];
15434 for (id, parent, text) in full_edits {
15435 let start = parent.start - offset;
15436 offset += parent.len() - text.len();
15437 selections.push(Selection {
15438 id,
15439 start,
15440 end: start + text.len(),
15441 reversed: false,
15442 goal: Default::default(),
15443 });
15444 }
15445 s.select(selections);
15446 });
15447 });
15448 }
15449
15450 pub fn select_next_syntax_node(
15451 &mut self,
15452 _: &SelectNextSyntaxNode,
15453 window: &mut Window,
15454 cx: &mut Context<Self>,
15455 ) {
15456 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15457 if old_selections.is_empty() {
15458 return;
15459 }
15460
15461 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15462
15463 let buffer = self.buffer.read(cx).snapshot(cx);
15464 let mut selected_sibling = false;
15465
15466 let new_selections = old_selections
15467 .iter()
15468 .map(|selection| {
15469 let old_range = selection.start..selection.end;
15470
15471 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15472 let new_range = node.byte_range();
15473 selected_sibling = true;
15474 Selection {
15475 id: selection.id,
15476 start: new_range.start,
15477 end: new_range.end,
15478 goal: SelectionGoal::None,
15479 reversed: selection.reversed,
15480 }
15481 } else {
15482 selection.clone()
15483 }
15484 })
15485 .collect::<Vec<_>>();
15486
15487 if selected_sibling {
15488 self.change_selections(
15489 SelectionEffects::scroll(Autoscroll::fit()),
15490 window,
15491 cx,
15492 |s| {
15493 s.select(new_selections);
15494 },
15495 );
15496 }
15497 }
15498
15499 pub fn select_prev_syntax_node(
15500 &mut self,
15501 _: &SelectPreviousSyntaxNode,
15502 window: &mut Window,
15503 cx: &mut Context<Self>,
15504 ) {
15505 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15506 if old_selections.is_empty() {
15507 return;
15508 }
15509
15510 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15511
15512 let buffer = self.buffer.read(cx).snapshot(cx);
15513 let mut selected_sibling = false;
15514
15515 let new_selections = old_selections
15516 .iter()
15517 .map(|selection| {
15518 let old_range = selection.start..selection.end;
15519
15520 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15521 let new_range = node.byte_range();
15522 selected_sibling = true;
15523 Selection {
15524 id: selection.id,
15525 start: new_range.start,
15526 end: new_range.end,
15527 goal: SelectionGoal::None,
15528 reversed: selection.reversed,
15529 }
15530 } else {
15531 selection.clone()
15532 }
15533 })
15534 .collect::<Vec<_>>();
15535
15536 if selected_sibling {
15537 self.change_selections(
15538 SelectionEffects::scroll(Autoscroll::fit()),
15539 window,
15540 cx,
15541 |s| {
15542 s.select(new_selections);
15543 },
15544 );
15545 }
15546 }
15547
15548 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15549 if !EditorSettings::get_global(cx).gutter.runnables {
15550 self.clear_tasks();
15551 return Task::ready(());
15552 }
15553 let project = self.project().map(Entity::downgrade);
15554 let task_sources = self.lsp_task_sources(cx);
15555 let multi_buffer = self.buffer.downgrade();
15556 cx.spawn_in(window, async move |editor, cx| {
15557 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15558 let Some(project) = project.and_then(|p| p.upgrade()) else {
15559 return;
15560 };
15561 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15562 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15563 }) else {
15564 return;
15565 };
15566
15567 let hide_runnables = project
15568 .update(cx, |project, _| project.is_via_collab())
15569 .unwrap_or(true);
15570 if hide_runnables {
15571 return;
15572 }
15573 let new_rows =
15574 cx.background_spawn({
15575 let snapshot = display_snapshot.clone();
15576 async move {
15577 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15578 }
15579 })
15580 .await;
15581 let Ok(lsp_tasks) =
15582 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15583 else {
15584 return;
15585 };
15586 let lsp_tasks = lsp_tasks.await;
15587
15588 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15589 lsp_tasks
15590 .into_iter()
15591 .flat_map(|(kind, tasks)| {
15592 tasks.into_iter().filter_map(move |(location, task)| {
15593 Some((kind.clone(), location?, task))
15594 })
15595 })
15596 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15597 let buffer = location.target.buffer;
15598 let buffer_snapshot = buffer.read(cx).snapshot();
15599 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15600 |(excerpt_id, snapshot, _)| {
15601 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15602 display_snapshot
15603 .buffer_snapshot()
15604 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15605 } else {
15606 None
15607 }
15608 },
15609 );
15610 if let Some(offset) = offset {
15611 let task_buffer_range =
15612 location.target.range.to_point(&buffer_snapshot);
15613 let context_buffer_range =
15614 task_buffer_range.to_offset(&buffer_snapshot);
15615 let context_range = BufferOffset(context_buffer_range.start)
15616 ..BufferOffset(context_buffer_range.end);
15617
15618 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15619 .or_insert_with(|| RunnableTasks {
15620 templates: Vec::new(),
15621 offset,
15622 column: task_buffer_range.start.column,
15623 extra_variables: HashMap::default(),
15624 context_range,
15625 })
15626 .templates
15627 .push((kind, task.original_task().clone()));
15628 }
15629
15630 acc
15631 })
15632 }) else {
15633 return;
15634 };
15635
15636 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15637 buffer.language_settings(cx).tasks.prefer_lsp
15638 }) else {
15639 return;
15640 };
15641
15642 let rows = Self::runnable_rows(
15643 project,
15644 display_snapshot,
15645 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15646 new_rows,
15647 cx.clone(),
15648 )
15649 .await;
15650 editor
15651 .update(cx, |editor, _| {
15652 editor.clear_tasks();
15653 for (key, mut value) in rows {
15654 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15655 value.templates.extend(lsp_tasks.templates);
15656 }
15657
15658 editor.insert_tasks(key, value);
15659 }
15660 for (key, value) in lsp_tasks_by_rows {
15661 editor.insert_tasks(key, value);
15662 }
15663 })
15664 .ok();
15665 })
15666 }
15667 fn fetch_runnable_ranges(
15668 snapshot: &DisplaySnapshot,
15669 range: Range<Anchor>,
15670 ) -> Vec<language::RunnableRange> {
15671 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15672 }
15673
15674 fn runnable_rows(
15675 project: Entity<Project>,
15676 snapshot: DisplaySnapshot,
15677 prefer_lsp: bool,
15678 runnable_ranges: Vec<RunnableRange>,
15679 cx: AsyncWindowContext,
15680 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15681 cx.spawn(async move |cx| {
15682 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15683 for mut runnable in runnable_ranges {
15684 let Some(tasks) = cx
15685 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15686 .ok()
15687 else {
15688 continue;
15689 };
15690 let mut tasks = tasks.await;
15691
15692 if prefer_lsp {
15693 tasks.retain(|(task_kind, _)| {
15694 !matches!(task_kind, TaskSourceKind::Language { .. })
15695 });
15696 }
15697 if tasks.is_empty() {
15698 continue;
15699 }
15700
15701 let point = runnable
15702 .run_range
15703 .start
15704 .to_point(&snapshot.buffer_snapshot());
15705 let Some(row) = snapshot
15706 .buffer_snapshot()
15707 .buffer_line_for_row(MultiBufferRow(point.row))
15708 .map(|(_, range)| range.start.row)
15709 else {
15710 continue;
15711 };
15712
15713 let context_range =
15714 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15715 runnable_rows.push((
15716 (runnable.buffer_id, row),
15717 RunnableTasks {
15718 templates: tasks,
15719 offset: snapshot
15720 .buffer_snapshot()
15721 .anchor_before(runnable.run_range.start),
15722 context_range,
15723 column: point.column,
15724 extra_variables: runnable.extra_captures,
15725 },
15726 ));
15727 }
15728 runnable_rows
15729 })
15730 }
15731
15732 fn templates_with_tags(
15733 project: &Entity<Project>,
15734 runnable: &mut Runnable,
15735 cx: &mut App,
15736 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15737 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15738 let (worktree_id, file) = project
15739 .buffer_for_id(runnable.buffer, cx)
15740 .and_then(|buffer| buffer.read(cx).file())
15741 .map(|file| (file.worktree_id(cx), file.clone()))
15742 .unzip();
15743
15744 (
15745 project.task_store().read(cx).task_inventory().cloned(),
15746 worktree_id,
15747 file,
15748 )
15749 });
15750
15751 let tags = mem::take(&mut runnable.tags);
15752 let language = runnable.language.clone();
15753 cx.spawn(async move |cx| {
15754 let mut templates_with_tags = Vec::new();
15755 if let Some(inventory) = inventory {
15756 for RunnableTag(tag) in tags {
15757 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15758 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15759 }) else {
15760 return templates_with_tags;
15761 };
15762 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15763 move |(_, template)| {
15764 template.tags.iter().any(|source_tag| source_tag == &tag)
15765 },
15766 ));
15767 }
15768 }
15769 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15770
15771 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15772 // Strongest source wins; if we have worktree tag binding, prefer that to
15773 // global and language bindings;
15774 // if we have a global binding, prefer that to language binding.
15775 let first_mismatch = templates_with_tags
15776 .iter()
15777 .position(|(tag_source, _)| tag_source != leading_tag_source);
15778 if let Some(index) = first_mismatch {
15779 templates_with_tags.truncate(index);
15780 }
15781 }
15782
15783 templates_with_tags
15784 })
15785 }
15786
15787 pub fn move_to_enclosing_bracket(
15788 &mut self,
15789 _: &MoveToEnclosingBracket,
15790 window: &mut Window,
15791 cx: &mut Context<Self>,
15792 ) {
15793 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15794 self.change_selections(Default::default(), window, cx, |s| {
15795 s.move_offsets_with(|snapshot, selection| {
15796 let Some(enclosing_bracket_ranges) =
15797 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15798 else {
15799 return;
15800 };
15801
15802 let mut best_length = usize::MAX;
15803 let mut best_inside = false;
15804 let mut best_in_bracket_range = false;
15805 let mut best_destination = None;
15806 for (open, close) in enclosing_bracket_ranges {
15807 let close = close.to_inclusive();
15808 let length = close.end() - open.start;
15809 let inside = selection.start >= open.end && selection.end <= *close.start();
15810 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15811 || close.contains(&selection.head());
15812
15813 // If best is next to a bracket and current isn't, skip
15814 if !in_bracket_range && best_in_bracket_range {
15815 continue;
15816 }
15817
15818 // Prefer smaller lengths unless best is inside and current isn't
15819 if length > best_length && (best_inside || !inside) {
15820 continue;
15821 }
15822
15823 best_length = length;
15824 best_inside = inside;
15825 best_in_bracket_range = in_bracket_range;
15826 best_destination = Some(
15827 if close.contains(&selection.start) && close.contains(&selection.end) {
15828 if inside { open.end } else { open.start }
15829 } else if inside {
15830 *close.start()
15831 } else {
15832 *close.end()
15833 },
15834 );
15835 }
15836
15837 if let Some(destination) = best_destination {
15838 selection.collapse_to(destination, SelectionGoal::None);
15839 }
15840 })
15841 });
15842 }
15843
15844 pub fn undo_selection(
15845 &mut self,
15846 _: &UndoSelection,
15847 window: &mut Window,
15848 cx: &mut Context<Self>,
15849 ) {
15850 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15851 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15852 self.selection_history.mode = SelectionHistoryMode::Undoing;
15853 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15854 this.end_selection(window, cx);
15855 this.change_selections(
15856 SelectionEffects::scroll(Autoscroll::newest()),
15857 window,
15858 cx,
15859 |s| s.select_anchors(entry.selections.to_vec()),
15860 );
15861 });
15862 self.selection_history.mode = SelectionHistoryMode::Normal;
15863
15864 self.select_next_state = entry.select_next_state;
15865 self.select_prev_state = entry.select_prev_state;
15866 self.add_selections_state = entry.add_selections_state;
15867 }
15868 }
15869
15870 pub fn redo_selection(
15871 &mut self,
15872 _: &RedoSelection,
15873 window: &mut Window,
15874 cx: &mut Context<Self>,
15875 ) {
15876 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15877 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15878 self.selection_history.mode = SelectionHistoryMode::Redoing;
15879 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15880 this.end_selection(window, cx);
15881 this.change_selections(
15882 SelectionEffects::scroll(Autoscroll::newest()),
15883 window,
15884 cx,
15885 |s| s.select_anchors(entry.selections.to_vec()),
15886 );
15887 });
15888 self.selection_history.mode = SelectionHistoryMode::Normal;
15889
15890 self.select_next_state = entry.select_next_state;
15891 self.select_prev_state = entry.select_prev_state;
15892 self.add_selections_state = entry.add_selections_state;
15893 }
15894 }
15895
15896 pub fn expand_excerpts(
15897 &mut self,
15898 action: &ExpandExcerpts,
15899 _: &mut Window,
15900 cx: &mut Context<Self>,
15901 ) {
15902 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15903 }
15904
15905 pub fn expand_excerpts_down(
15906 &mut self,
15907 action: &ExpandExcerptsDown,
15908 _: &mut Window,
15909 cx: &mut Context<Self>,
15910 ) {
15911 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15912 }
15913
15914 pub fn expand_excerpts_up(
15915 &mut self,
15916 action: &ExpandExcerptsUp,
15917 _: &mut Window,
15918 cx: &mut Context<Self>,
15919 ) {
15920 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15921 }
15922
15923 pub fn expand_excerpts_for_direction(
15924 &mut self,
15925 lines: u32,
15926 direction: ExpandExcerptDirection,
15927
15928 cx: &mut Context<Self>,
15929 ) {
15930 let selections = self.selections.disjoint_anchors_arc();
15931
15932 let lines = if lines == 0 {
15933 EditorSettings::get_global(cx).expand_excerpt_lines
15934 } else {
15935 lines
15936 };
15937
15938 self.buffer.update(cx, |buffer, cx| {
15939 let snapshot = buffer.snapshot(cx);
15940 let mut excerpt_ids = selections
15941 .iter()
15942 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15943 .collect::<Vec<_>>();
15944 excerpt_ids.sort();
15945 excerpt_ids.dedup();
15946 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15947 })
15948 }
15949
15950 pub fn expand_excerpt(
15951 &mut self,
15952 excerpt: ExcerptId,
15953 direction: ExpandExcerptDirection,
15954 window: &mut Window,
15955 cx: &mut Context<Self>,
15956 ) {
15957 let current_scroll_position = self.scroll_position(cx);
15958 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15959 let mut should_scroll_up = false;
15960
15961 if direction == ExpandExcerptDirection::Down {
15962 let multi_buffer = self.buffer.read(cx);
15963 let snapshot = multi_buffer.snapshot(cx);
15964 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15965 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15966 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
15967 {
15968 let buffer_snapshot = buffer.read(cx).snapshot();
15969 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15970 let last_row = buffer_snapshot.max_point().row;
15971 let lines_below = last_row.saturating_sub(excerpt_end_row);
15972 should_scroll_up = lines_below >= lines_to_expand;
15973 }
15974 }
15975
15976 self.buffer.update(cx, |buffer, cx| {
15977 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15978 });
15979
15980 if should_scroll_up {
15981 let new_scroll_position =
15982 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as ScrollOffset);
15983 self.set_scroll_position(new_scroll_position, window, cx);
15984 }
15985 }
15986
15987 pub fn go_to_singleton_buffer_point(
15988 &mut self,
15989 point: Point,
15990 window: &mut Window,
15991 cx: &mut Context<Self>,
15992 ) {
15993 self.go_to_singleton_buffer_range(point..point, window, cx);
15994 }
15995
15996 pub fn go_to_singleton_buffer_range(
15997 &mut self,
15998 range: Range<Point>,
15999 window: &mut Window,
16000 cx: &mut Context<Self>,
16001 ) {
16002 let multibuffer = self.buffer().read(cx);
16003 let Some(buffer) = multibuffer.as_singleton() else {
16004 return;
16005 };
16006 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16007 return;
16008 };
16009 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16010 return;
16011 };
16012 self.change_selections(
16013 SelectionEffects::default().nav_history(true),
16014 window,
16015 cx,
16016 |s| s.select_anchor_ranges([start..end]),
16017 );
16018 }
16019
16020 pub fn go_to_diagnostic(
16021 &mut self,
16022 action: &GoToDiagnostic,
16023 window: &mut Window,
16024 cx: &mut Context<Self>,
16025 ) {
16026 if !self.diagnostics_enabled() {
16027 return;
16028 }
16029 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16030 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16031 }
16032
16033 pub fn go_to_prev_diagnostic(
16034 &mut self,
16035 action: &GoToPreviousDiagnostic,
16036 window: &mut Window,
16037 cx: &mut Context<Self>,
16038 ) {
16039 if !self.diagnostics_enabled() {
16040 return;
16041 }
16042 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16043 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16044 }
16045
16046 pub fn go_to_diagnostic_impl(
16047 &mut self,
16048 direction: Direction,
16049 severity: GoToDiagnosticSeverityFilter,
16050 window: &mut Window,
16051 cx: &mut Context<Self>,
16052 ) {
16053 let buffer = self.buffer.read(cx).snapshot(cx);
16054 let selection = self.selections.newest::<usize>(cx);
16055
16056 let mut active_group_id = None;
16057 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16058 && active_group.active_range.start.to_offset(&buffer) == selection.start
16059 {
16060 active_group_id = Some(active_group.group_id);
16061 }
16062
16063 fn filtered<'a>(
16064 snapshot: EditorSnapshot,
16065 severity: GoToDiagnosticSeverityFilter,
16066 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
16067 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
16068 diagnostics
16069 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16070 .filter(|entry| entry.range.start != entry.range.end)
16071 .filter(|entry| !entry.diagnostic.is_unnecessary)
16072 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
16073 }
16074
16075 let snapshot = self.snapshot(window, cx);
16076 let before = filtered(
16077 snapshot.clone(),
16078 severity,
16079 buffer
16080 .diagnostics_in_range(0..selection.start)
16081 .filter(|entry| entry.range.start <= selection.start),
16082 );
16083 let after = filtered(
16084 snapshot,
16085 severity,
16086 buffer
16087 .diagnostics_in_range(selection.start..buffer.len())
16088 .filter(|entry| entry.range.start >= selection.start),
16089 );
16090
16091 let mut found: Option<DiagnosticEntryRef<usize>> = None;
16092 if direction == Direction::Prev {
16093 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16094 {
16095 for diagnostic in prev_diagnostics.into_iter().rev() {
16096 if diagnostic.range.start != selection.start
16097 || active_group_id
16098 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16099 {
16100 found = Some(diagnostic);
16101 break 'outer;
16102 }
16103 }
16104 }
16105 } else {
16106 for diagnostic in after.chain(before) {
16107 if diagnostic.range.start != selection.start
16108 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16109 {
16110 found = Some(diagnostic);
16111 break;
16112 }
16113 }
16114 }
16115 let Some(next_diagnostic) = found else {
16116 return;
16117 };
16118
16119 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16120 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16121 return;
16122 };
16123 self.change_selections(Default::default(), window, cx, |s| {
16124 s.select_ranges(vec![
16125 next_diagnostic.range.start..next_diagnostic.range.start,
16126 ])
16127 });
16128 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16129 self.refresh_edit_prediction(false, true, window, cx);
16130 }
16131
16132 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16133 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16134 let snapshot = self.snapshot(window, cx);
16135 let selection = self.selections.newest::<Point>(cx);
16136 self.go_to_hunk_before_or_after_position(
16137 &snapshot,
16138 selection.head(),
16139 Direction::Next,
16140 window,
16141 cx,
16142 );
16143 }
16144
16145 pub fn go_to_hunk_before_or_after_position(
16146 &mut self,
16147 snapshot: &EditorSnapshot,
16148 position: Point,
16149 direction: Direction,
16150 window: &mut Window,
16151 cx: &mut Context<Editor>,
16152 ) {
16153 let row = if direction == Direction::Next {
16154 self.hunk_after_position(snapshot, position)
16155 .map(|hunk| hunk.row_range.start)
16156 } else {
16157 self.hunk_before_position(snapshot, position)
16158 };
16159
16160 if let Some(row) = row {
16161 let destination = Point::new(row.0, 0);
16162 let autoscroll = Autoscroll::center();
16163
16164 self.unfold_ranges(&[destination..destination], false, false, cx);
16165 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16166 s.select_ranges([destination..destination]);
16167 });
16168 }
16169 }
16170
16171 fn hunk_after_position(
16172 &mut self,
16173 snapshot: &EditorSnapshot,
16174 position: Point,
16175 ) -> Option<MultiBufferDiffHunk> {
16176 snapshot
16177 .buffer_snapshot()
16178 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16179 .find(|hunk| hunk.row_range.start.0 > position.row)
16180 .or_else(|| {
16181 snapshot
16182 .buffer_snapshot()
16183 .diff_hunks_in_range(Point::zero()..position)
16184 .find(|hunk| hunk.row_range.end.0 < position.row)
16185 })
16186 }
16187
16188 fn go_to_prev_hunk(
16189 &mut self,
16190 _: &GoToPreviousHunk,
16191 window: &mut Window,
16192 cx: &mut Context<Self>,
16193 ) {
16194 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16195 let snapshot = self.snapshot(window, cx);
16196 let selection = self.selections.newest::<Point>(cx);
16197 self.go_to_hunk_before_or_after_position(
16198 &snapshot,
16199 selection.head(),
16200 Direction::Prev,
16201 window,
16202 cx,
16203 );
16204 }
16205
16206 fn hunk_before_position(
16207 &mut self,
16208 snapshot: &EditorSnapshot,
16209 position: Point,
16210 ) -> Option<MultiBufferRow> {
16211 snapshot
16212 .buffer_snapshot()
16213 .diff_hunk_before(position)
16214 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16215 }
16216
16217 fn go_to_next_change(
16218 &mut self,
16219 _: &GoToNextChange,
16220 window: &mut Window,
16221 cx: &mut Context<Self>,
16222 ) {
16223 if let Some(selections) = self
16224 .change_list
16225 .next_change(1, Direction::Next)
16226 .map(|s| s.to_vec())
16227 {
16228 self.change_selections(Default::default(), window, cx, |s| {
16229 let map = s.display_map();
16230 s.select_display_ranges(selections.iter().map(|a| {
16231 let point = a.to_display_point(&map);
16232 point..point
16233 }))
16234 })
16235 }
16236 }
16237
16238 fn go_to_previous_change(
16239 &mut self,
16240 _: &GoToPreviousChange,
16241 window: &mut Window,
16242 cx: &mut Context<Self>,
16243 ) {
16244 if let Some(selections) = self
16245 .change_list
16246 .next_change(1, Direction::Prev)
16247 .map(|s| s.to_vec())
16248 {
16249 self.change_selections(Default::default(), window, cx, |s| {
16250 let map = s.display_map();
16251 s.select_display_ranges(selections.iter().map(|a| {
16252 let point = a.to_display_point(&map);
16253 point..point
16254 }))
16255 })
16256 }
16257 }
16258
16259 pub fn go_to_next_document_highlight(
16260 &mut self,
16261 _: &GoToNextDocumentHighlight,
16262 window: &mut Window,
16263 cx: &mut Context<Self>,
16264 ) {
16265 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16266 }
16267
16268 pub fn go_to_prev_document_highlight(
16269 &mut self,
16270 _: &GoToPreviousDocumentHighlight,
16271 window: &mut Window,
16272 cx: &mut Context<Self>,
16273 ) {
16274 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16275 }
16276
16277 pub fn go_to_document_highlight_before_or_after_position(
16278 &mut self,
16279 direction: Direction,
16280 window: &mut Window,
16281 cx: &mut Context<Editor>,
16282 ) {
16283 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16284 let snapshot = self.snapshot(window, cx);
16285 let buffer = &snapshot.buffer_snapshot();
16286 let position = self.selections.newest::<Point>(cx).head();
16287 let anchor_position = buffer.anchor_after(position);
16288
16289 // Get all document highlights (both read and write)
16290 let mut all_highlights = Vec::new();
16291
16292 if let Some((_, read_highlights)) = self
16293 .background_highlights
16294 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16295 {
16296 all_highlights.extend(read_highlights.iter());
16297 }
16298
16299 if let Some((_, write_highlights)) = self
16300 .background_highlights
16301 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16302 {
16303 all_highlights.extend(write_highlights.iter());
16304 }
16305
16306 if all_highlights.is_empty() {
16307 return;
16308 }
16309
16310 // Sort highlights by position
16311 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16312
16313 let target_highlight = match direction {
16314 Direction::Next => {
16315 // Find the first highlight after the current position
16316 all_highlights
16317 .iter()
16318 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16319 }
16320 Direction::Prev => {
16321 // Find the last highlight before the current position
16322 all_highlights
16323 .iter()
16324 .rev()
16325 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16326 }
16327 };
16328
16329 if let Some(highlight) = target_highlight {
16330 let destination = highlight.start.to_point(buffer);
16331 let autoscroll = Autoscroll::center();
16332
16333 self.unfold_ranges(&[destination..destination], false, false, cx);
16334 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16335 s.select_ranges([destination..destination]);
16336 });
16337 }
16338 }
16339
16340 fn go_to_line<T: 'static>(
16341 &mut self,
16342 position: Anchor,
16343 highlight_color: Option<Hsla>,
16344 window: &mut Window,
16345 cx: &mut Context<Self>,
16346 ) {
16347 let snapshot = self.snapshot(window, cx).display_snapshot;
16348 let position = position.to_point(&snapshot.buffer_snapshot());
16349 let start = snapshot
16350 .buffer_snapshot()
16351 .clip_point(Point::new(position.row, 0), Bias::Left);
16352 let end = start + Point::new(1, 0);
16353 let start = snapshot.buffer_snapshot().anchor_before(start);
16354 let end = snapshot.buffer_snapshot().anchor_before(end);
16355
16356 self.highlight_rows::<T>(
16357 start..end,
16358 highlight_color
16359 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16360 Default::default(),
16361 cx,
16362 );
16363
16364 if self.buffer.read(cx).is_singleton() {
16365 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16366 }
16367 }
16368
16369 pub fn go_to_definition(
16370 &mut self,
16371 _: &GoToDefinition,
16372 window: &mut Window,
16373 cx: &mut Context<Self>,
16374 ) -> Task<Result<Navigated>> {
16375 let definition =
16376 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16377 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16378 cx.spawn_in(window, async move |editor, cx| {
16379 if definition.await? == Navigated::Yes {
16380 return Ok(Navigated::Yes);
16381 }
16382 match fallback_strategy {
16383 GoToDefinitionFallback::None => Ok(Navigated::No),
16384 GoToDefinitionFallback::FindAllReferences => {
16385 match editor.update_in(cx, |editor, window, cx| {
16386 editor.find_all_references(&FindAllReferences, window, cx)
16387 })? {
16388 Some(references) => references.await,
16389 None => Ok(Navigated::No),
16390 }
16391 }
16392 }
16393 })
16394 }
16395
16396 pub fn go_to_declaration(
16397 &mut self,
16398 _: &GoToDeclaration,
16399 window: &mut Window,
16400 cx: &mut Context<Self>,
16401 ) -> Task<Result<Navigated>> {
16402 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16403 }
16404
16405 pub fn go_to_declaration_split(
16406 &mut self,
16407 _: &GoToDeclaration,
16408 window: &mut Window,
16409 cx: &mut Context<Self>,
16410 ) -> Task<Result<Navigated>> {
16411 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16412 }
16413
16414 pub fn go_to_implementation(
16415 &mut self,
16416 _: &GoToImplementation,
16417 window: &mut Window,
16418 cx: &mut Context<Self>,
16419 ) -> Task<Result<Navigated>> {
16420 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16421 }
16422
16423 pub fn go_to_implementation_split(
16424 &mut self,
16425 _: &GoToImplementationSplit,
16426 window: &mut Window,
16427 cx: &mut Context<Self>,
16428 ) -> Task<Result<Navigated>> {
16429 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16430 }
16431
16432 pub fn go_to_type_definition(
16433 &mut self,
16434 _: &GoToTypeDefinition,
16435 window: &mut Window,
16436 cx: &mut Context<Self>,
16437 ) -> Task<Result<Navigated>> {
16438 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16439 }
16440
16441 pub fn go_to_definition_split(
16442 &mut self,
16443 _: &GoToDefinitionSplit,
16444 window: &mut Window,
16445 cx: &mut Context<Self>,
16446 ) -> Task<Result<Navigated>> {
16447 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16448 }
16449
16450 pub fn go_to_type_definition_split(
16451 &mut self,
16452 _: &GoToTypeDefinitionSplit,
16453 window: &mut Window,
16454 cx: &mut Context<Self>,
16455 ) -> Task<Result<Navigated>> {
16456 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16457 }
16458
16459 fn go_to_definition_of_kind(
16460 &mut self,
16461 kind: GotoDefinitionKind,
16462 split: bool,
16463 window: &mut Window,
16464 cx: &mut Context<Self>,
16465 ) -> Task<Result<Navigated>> {
16466 let Some(provider) = self.semantics_provider.clone() else {
16467 return Task::ready(Ok(Navigated::No));
16468 };
16469 let head = self.selections.newest::<usize>(cx).head();
16470 let buffer = self.buffer.read(cx);
16471 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16472 return Task::ready(Ok(Navigated::No));
16473 };
16474 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16475 return Task::ready(Ok(Navigated::No));
16476 };
16477
16478 cx.spawn_in(window, async move |editor, cx| {
16479 let Some(definitions) = definitions.await? else {
16480 return Ok(Navigated::No);
16481 };
16482 let navigated = editor
16483 .update_in(cx, |editor, window, cx| {
16484 editor.navigate_to_hover_links(
16485 Some(kind),
16486 definitions
16487 .into_iter()
16488 .filter(|location| {
16489 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16490 })
16491 .map(HoverLink::Text)
16492 .collect::<Vec<_>>(),
16493 split,
16494 window,
16495 cx,
16496 )
16497 })?
16498 .await?;
16499 anyhow::Ok(navigated)
16500 })
16501 }
16502
16503 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16504 let selection = self.selections.newest_anchor();
16505 let head = selection.head();
16506 let tail = selection.tail();
16507
16508 let Some((buffer, start_position)) =
16509 self.buffer.read(cx).text_anchor_for_position(head, cx)
16510 else {
16511 return;
16512 };
16513
16514 let end_position = if head != tail {
16515 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16516 return;
16517 };
16518 Some(pos)
16519 } else {
16520 None
16521 };
16522
16523 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16524 let url = if let Some(end_pos) = end_position {
16525 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16526 } else {
16527 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16528 };
16529
16530 if let Some(url) = url {
16531 cx.update(|window, cx| {
16532 if parse_zed_link(&url, cx).is_some() {
16533 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16534 } else {
16535 cx.open_url(&url);
16536 }
16537 })?;
16538 }
16539
16540 anyhow::Ok(())
16541 });
16542
16543 url_finder.detach();
16544 }
16545
16546 pub fn open_selected_filename(
16547 &mut self,
16548 _: &OpenSelectedFilename,
16549 window: &mut Window,
16550 cx: &mut Context<Self>,
16551 ) {
16552 let Some(workspace) = self.workspace() else {
16553 return;
16554 };
16555
16556 let position = self.selections.newest_anchor().head();
16557
16558 let Some((buffer, buffer_position)) =
16559 self.buffer.read(cx).text_anchor_for_position(position, cx)
16560 else {
16561 return;
16562 };
16563
16564 let project = self.project.clone();
16565
16566 cx.spawn_in(window, async move |_, cx| {
16567 let result = find_file(&buffer, project, buffer_position, cx).await;
16568
16569 if let Some((_, path)) = result {
16570 workspace
16571 .update_in(cx, |workspace, window, cx| {
16572 workspace.open_resolved_path(path, window, cx)
16573 })?
16574 .await?;
16575 }
16576 anyhow::Ok(())
16577 })
16578 .detach();
16579 }
16580
16581 pub(crate) fn navigate_to_hover_links(
16582 &mut self,
16583 kind: Option<GotoDefinitionKind>,
16584 definitions: Vec<HoverLink>,
16585 split: bool,
16586 window: &mut Window,
16587 cx: &mut Context<Editor>,
16588 ) -> Task<Result<Navigated>> {
16589 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16590 let mut first_url_or_file = None;
16591 let definitions: Vec<_> = definitions
16592 .into_iter()
16593 .filter_map(|def| match def {
16594 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16595 HoverLink::InlayHint(lsp_location, server_id) => {
16596 let computation =
16597 self.compute_target_location(lsp_location, server_id, window, cx);
16598 Some(cx.background_spawn(computation))
16599 }
16600 HoverLink::Url(url) => {
16601 first_url_or_file = Some(Either::Left(url));
16602 None
16603 }
16604 HoverLink::File(path) => {
16605 first_url_or_file = Some(Either::Right(path));
16606 None
16607 }
16608 })
16609 .collect();
16610
16611 let workspace = self.workspace();
16612
16613 cx.spawn_in(window, async move |editor, cx| {
16614 let locations: Vec<Location> = future::join_all(definitions)
16615 .await
16616 .into_iter()
16617 .filter_map(|location| location.transpose())
16618 .collect::<Result<_>>()
16619 .context("location tasks")?;
16620 let mut locations = cx.update(|_, cx| {
16621 locations
16622 .into_iter()
16623 .map(|location| {
16624 let buffer = location.buffer.read(cx);
16625 (location.buffer, location.range.to_point(buffer))
16626 })
16627 .into_group_map()
16628 })?;
16629 let mut num_locations = 0;
16630 for ranges in locations.values_mut() {
16631 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16632 ranges.dedup();
16633 num_locations += ranges.len();
16634 }
16635
16636 if num_locations > 1 {
16637 let Some(workspace) = workspace else {
16638 return Ok(Navigated::No);
16639 };
16640
16641 let tab_kind = match kind {
16642 Some(GotoDefinitionKind::Implementation) => "Implementations",
16643 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16644 Some(GotoDefinitionKind::Declaration) => "Declarations",
16645 Some(GotoDefinitionKind::Type) => "Types",
16646 };
16647 let title = editor
16648 .update_in(cx, |_, _, cx| {
16649 let target = locations
16650 .iter()
16651 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16652 .map(|(buffer, location)| {
16653 buffer
16654 .read(cx)
16655 .text_for_range(location.clone())
16656 .collect::<String>()
16657 })
16658 .filter(|text| !text.contains('\n'))
16659 .unique()
16660 .take(3)
16661 .join(", ");
16662 if target.is_empty() {
16663 tab_kind.to_owned()
16664 } else {
16665 format!("{tab_kind} for {target}")
16666 }
16667 })
16668 .context("buffer title")?;
16669
16670 let opened = workspace
16671 .update_in(cx, |workspace, window, cx| {
16672 Self::open_locations_in_multibuffer(
16673 workspace,
16674 locations,
16675 title,
16676 split,
16677 MultibufferSelectionMode::First,
16678 window,
16679 cx,
16680 )
16681 })
16682 .is_ok();
16683
16684 anyhow::Ok(Navigated::from_bool(opened))
16685 } else if num_locations == 0 {
16686 // If there is one url or file, open it directly
16687 match first_url_or_file {
16688 Some(Either::Left(url)) => {
16689 cx.update(|_, cx| cx.open_url(&url))?;
16690 Ok(Navigated::Yes)
16691 }
16692 Some(Either::Right(path)) => {
16693 let Some(workspace) = workspace else {
16694 return Ok(Navigated::No);
16695 };
16696
16697 workspace
16698 .update_in(cx, |workspace, window, cx| {
16699 workspace.open_resolved_path(path, window, cx)
16700 })?
16701 .await?;
16702 Ok(Navigated::Yes)
16703 }
16704 None => Ok(Navigated::No),
16705 }
16706 } else {
16707 let Some(workspace) = workspace else {
16708 return Ok(Navigated::No);
16709 };
16710
16711 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16712 let target_range = target_ranges.first().unwrap().clone();
16713
16714 editor.update_in(cx, |editor, window, cx| {
16715 let range = target_range.to_point(target_buffer.read(cx));
16716 let range = editor.range_for_match(&range);
16717 let range = collapse_multiline_range(range);
16718
16719 if !split
16720 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16721 {
16722 editor.go_to_singleton_buffer_range(range, window, cx);
16723 } else {
16724 let pane = workspace.read(cx).active_pane().clone();
16725 window.defer(cx, move |window, cx| {
16726 let target_editor: Entity<Self> =
16727 workspace.update(cx, |workspace, cx| {
16728 let pane = if split {
16729 workspace.adjacent_pane(window, cx)
16730 } else {
16731 workspace.active_pane().clone()
16732 };
16733
16734 workspace.open_project_item(
16735 pane,
16736 target_buffer.clone(),
16737 true,
16738 true,
16739 window,
16740 cx,
16741 )
16742 });
16743 target_editor.update(cx, |target_editor, cx| {
16744 // When selecting a definition in a different buffer, disable the nav history
16745 // to avoid creating a history entry at the previous cursor location.
16746 pane.update(cx, |pane, _| pane.disable_history());
16747 target_editor.go_to_singleton_buffer_range(range, window, cx);
16748 pane.update(cx, |pane, _| pane.enable_history());
16749 });
16750 });
16751 }
16752 Navigated::Yes
16753 })
16754 }
16755 })
16756 }
16757
16758 fn compute_target_location(
16759 &self,
16760 lsp_location: lsp::Location,
16761 server_id: LanguageServerId,
16762 window: &mut Window,
16763 cx: &mut Context<Self>,
16764 ) -> Task<anyhow::Result<Option<Location>>> {
16765 let Some(project) = self.project.clone() else {
16766 return Task::ready(Ok(None));
16767 };
16768
16769 cx.spawn_in(window, async move |editor, cx| {
16770 let location_task = editor.update(cx, |_, cx| {
16771 project.update(cx, |project, cx| {
16772 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16773 })
16774 })?;
16775 let location = Some({
16776 let target_buffer_handle = location_task.await.context("open local buffer")?;
16777 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16778 let target_start = target_buffer
16779 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16780 let target_end = target_buffer
16781 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16782 target_buffer.anchor_after(target_start)
16783 ..target_buffer.anchor_before(target_end)
16784 })?;
16785 Location {
16786 buffer: target_buffer_handle,
16787 range,
16788 }
16789 });
16790 Ok(location)
16791 })
16792 }
16793
16794 pub fn find_all_references(
16795 &mut self,
16796 _: &FindAllReferences,
16797 window: &mut Window,
16798 cx: &mut Context<Self>,
16799 ) -> Option<Task<Result<Navigated>>> {
16800 let selection = self.selections.newest::<usize>(cx);
16801 let multi_buffer = self.buffer.read(cx);
16802 let head = selection.head();
16803
16804 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16805 let head_anchor = multi_buffer_snapshot.anchor_at(
16806 head,
16807 if head < selection.tail() {
16808 Bias::Right
16809 } else {
16810 Bias::Left
16811 },
16812 );
16813
16814 match self
16815 .find_all_references_task_sources
16816 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16817 {
16818 Ok(_) => {
16819 log::info!(
16820 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16821 );
16822 return None;
16823 }
16824 Err(i) => {
16825 self.find_all_references_task_sources.insert(i, head_anchor);
16826 }
16827 }
16828
16829 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16830 let workspace = self.workspace()?;
16831 let project = workspace.read(cx).project().clone();
16832 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16833 Some(cx.spawn_in(window, async move |editor, cx| {
16834 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16835 if let Ok(i) = editor
16836 .find_all_references_task_sources
16837 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16838 {
16839 editor.find_all_references_task_sources.remove(i);
16840 }
16841 });
16842
16843 let Some(locations) = references.await? else {
16844 return anyhow::Ok(Navigated::No);
16845 };
16846 let mut locations = cx.update(|_, cx| {
16847 locations
16848 .into_iter()
16849 .map(|location| {
16850 let buffer = location.buffer.read(cx);
16851 (location.buffer, location.range.to_point(buffer))
16852 })
16853 .into_group_map()
16854 })?;
16855 if locations.is_empty() {
16856 return anyhow::Ok(Navigated::No);
16857 }
16858 for ranges in locations.values_mut() {
16859 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16860 ranges.dedup();
16861 }
16862
16863 workspace.update_in(cx, |workspace, window, cx| {
16864 let target = locations
16865 .iter()
16866 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16867 .map(|(buffer, location)| {
16868 buffer
16869 .read(cx)
16870 .text_for_range(location.clone())
16871 .collect::<String>()
16872 })
16873 .filter(|text| !text.contains('\n'))
16874 .unique()
16875 .take(3)
16876 .join(", ");
16877 let title = if target.is_empty() {
16878 "References".to_owned()
16879 } else {
16880 format!("References to {target}")
16881 };
16882 Self::open_locations_in_multibuffer(
16883 workspace,
16884 locations,
16885 title,
16886 false,
16887 MultibufferSelectionMode::First,
16888 window,
16889 cx,
16890 );
16891 Navigated::Yes
16892 })
16893 }))
16894 }
16895
16896 /// Opens a multibuffer with the given project locations in it
16897 pub fn open_locations_in_multibuffer(
16898 workspace: &mut Workspace,
16899 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
16900 title: String,
16901 split: bool,
16902 multibuffer_selection_mode: MultibufferSelectionMode,
16903 window: &mut Window,
16904 cx: &mut Context<Workspace>,
16905 ) {
16906 if locations.is_empty() {
16907 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16908 return;
16909 }
16910
16911 let capability = workspace.project().read(cx).capability();
16912 let mut ranges = <Vec<Range<Anchor>>>::new();
16913
16914 // a key to find existing multibuffer editors with the same set of locations
16915 // to prevent us from opening more and more multibuffer tabs for searches and the like
16916 let mut key = (title.clone(), vec![]);
16917 let excerpt_buffer = cx.new(|cx| {
16918 let key = &mut key.1;
16919 let mut multibuffer = MultiBuffer::new(capability);
16920 for (buffer, mut ranges_for_buffer) in locations {
16921 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16922 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
16923 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16924 PathKey::for_buffer(&buffer, cx),
16925 buffer.clone(),
16926 ranges_for_buffer,
16927 multibuffer_context_lines(cx),
16928 cx,
16929 );
16930 ranges.extend(new_ranges)
16931 }
16932
16933 multibuffer.with_title(title)
16934 });
16935 let existing = workspace.active_pane().update(cx, |pane, cx| {
16936 pane.items()
16937 .filter_map(|item| item.downcast::<Editor>())
16938 .find(|editor| {
16939 editor
16940 .read(cx)
16941 .lookup_key
16942 .as_ref()
16943 .and_then(|it| {
16944 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
16945 })
16946 .is_some_and(|it| *it == key)
16947 })
16948 });
16949 let editor = existing.unwrap_or_else(|| {
16950 cx.new(|cx| {
16951 let mut editor = Editor::for_multibuffer(
16952 excerpt_buffer,
16953 Some(workspace.project().clone()),
16954 window,
16955 cx,
16956 );
16957 editor.lookup_key = Some(Box::new(key));
16958 editor
16959 })
16960 });
16961 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
16962 MultibufferSelectionMode::First => {
16963 if let Some(first_range) = ranges.first() {
16964 editor.change_selections(
16965 SelectionEffects::no_scroll(),
16966 window,
16967 cx,
16968 |selections| {
16969 selections.clear_disjoint();
16970 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
16971 },
16972 );
16973 }
16974 editor.highlight_background::<Self>(
16975 &ranges,
16976 |theme| theme.colors().editor_highlighted_line_background,
16977 cx,
16978 );
16979 }
16980 MultibufferSelectionMode::All => {
16981 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
16982 selections.clear_disjoint();
16983 selections.select_anchor_ranges(ranges);
16984 });
16985 }
16986 });
16987
16988 let item = Box::new(editor);
16989 let item_id = item.item_id();
16990
16991 if split {
16992 let pane = workspace.adjacent_pane(window, cx);
16993 workspace.add_item(pane, item, None, true, true, window, cx);
16994 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16995 let (preview_item_id, preview_item_idx) =
16996 workspace.active_pane().read_with(cx, |pane, _| {
16997 (pane.preview_item_id(), pane.preview_item_idx())
16998 });
16999
17000 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17001
17002 if let Some(preview_item_id) = preview_item_id {
17003 workspace.active_pane().update(cx, |pane, cx| {
17004 pane.remove_item(preview_item_id, false, false, window, cx);
17005 });
17006 }
17007 } else {
17008 workspace.add_item_to_active_pane(item, None, true, window, cx);
17009 }
17010 workspace.active_pane().update(cx, |pane, cx| {
17011 pane.set_preview_item_id(Some(item_id), cx);
17012 });
17013 }
17014
17015 pub fn rename(
17016 &mut self,
17017 _: &Rename,
17018 window: &mut Window,
17019 cx: &mut Context<Self>,
17020 ) -> Option<Task<Result<()>>> {
17021 use language::ToOffset as _;
17022
17023 let provider = self.semantics_provider.clone()?;
17024 let selection = self.selections.newest_anchor().clone();
17025 let (cursor_buffer, cursor_buffer_position) = self
17026 .buffer
17027 .read(cx)
17028 .text_anchor_for_position(selection.head(), cx)?;
17029 let (tail_buffer, cursor_buffer_position_end) = self
17030 .buffer
17031 .read(cx)
17032 .text_anchor_for_position(selection.tail(), cx)?;
17033 if tail_buffer != cursor_buffer {
17034 return None;
17035 }
17036
17037 let snapshot = cursor_buffer.read(cx).snapshot();
17038 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17039 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17040 let prepare_rename = provider
17041 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17042 .unwrap_or_else(|| Task::ready(Ok(None)));
17043 drop(snapshot);
17044
17045 Some(cx.spawn_in(window, async move |this, cx| {
17046 let rename_range = if let Some(range) = prepare_rename.await? {
17047 Some(range)
17048 } else {
17049 this.update(cx, |this, cx| {
17050 let buffer = this.buffer.read(cx).snapshot(cx);
17051 let mut buffer_highlights = this
17052 .document_highlights_for_position(selection.head(), &buffer)
17053 .filter(|highlight| {
17054 highlight.start.excerpt_id == selection.head().excerpt_id
17055 && highlight.end.excerpt_id == selection.head().excerpt_id
17056 });
17057 buffer_highlights
17058 .next()
17059 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17060 })?
17061 };
17062 if let Some(rename_range) = rename_range {
17063 this.update_in(cx, |this, window, cx| {
17064 let snapshot = cursor_buffer.read(cx).snapshot();
17065 let rename_buffer_range = rename_range.to_offset(&snapshot);
17066 let cursor_offset_in_rename_range =
17067 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17068 let cursor_offset_in_rename_range_end =
17069 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17070
17071 this.take_rename(false, window, cx);
17072 let buffer = this.buffer.read(cx).read(cx);
17073 let cursor_offset = selection.head().to_offset(&buffer);
17074 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17075 let rename_end = rename_start + rename_buffer_range.len();
17076 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17077 let mut old_highlight_id = None;
17078 let old_name: Arc<str> = buffer
17079 .chunks(rename_start..rename_end, true)
17080 .map(|chunk| {
17081 if old_highlight_id.is_none() {
17082 old_highlight_id = chunk.syntax_highlight_id;
17083 }
17084 chunk.text
17085 })
17086 .collect::<String>()
17087 .into();
17088
17089 drop(buffer);
17090
17091 // Position the selection in the rename editor so that it matches the current selection.
17092 this.show_local_selections = false;
17093 let rename_editor = cx.new(|cx| {
17094 let mut editor = Editor::single_line(window, cx);
17095 editor.buffer.update(cx, |buffer, cx| {
17096 buffer.edit([(0..0, old_name.clone())], None, cx)
17097 });
17098 let rename_selection_range = match cursor_offset_in_rename_range
17099 .cmp(&cursor_offset_in_rename_range_end)
17100 {
17101 Ordering::Equal => {
17102 editor.select_all(&SelectAll, window, cx);
17103 return editor;
17104 }
17105 Ordering::Less => {
17106 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17107 }
17108 Ordering::Greater => {
17109 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17110 }
17111 };
17112 if rename_selection_range.end > old_name.len() {
17113 editor.select_all(&SelectAll, window, cx);
17114 } else {
17115 editor.change_selections(Default::default(), window, cx, |s| {
17116 s.select_ranges([rename_selection_range]);
17117 });
17118 }
17119 editor
17120 });
17121 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17122 if e == &EditorEvent::Focused {
17123 cx.emit(EditorEvent::FocusedIn)
17124 }
17125 })
17126 .detach();
17127
17128 let write_highlights =
17129 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17130 let read_highlights =
17131 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17132 let ranges = write_highlights
17133 .iter()
17134 .flat_map(|(_, ranges)| ranges.iter())
17135 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17136 .cloned()
17137 .collect();
17138
17139 this.highlight_text::<Rename>(
17140 ranges,
17141 HighlightStyle {
17142 fade_out: Some(0.6),
17143 ..Default::default()
17144 },
17145 cx,
17146 );
17147 let rename_focus_handle = rename_editor.focus_handle(cx);
17148 window.focus(&rename_focus_handle);
17149 let block_id = this.insert_blocks(
17150 [BlockProperties {
17151 style: BlockStyle::Flex,
17152 placement: BlockPlacement::Below(range.start),
17153 height: Some(1),
17154 render: Arc::new({
17155 let rename_editor = rename_editor.clone();
17156 move |cx: &mut BlockContext| {
17157 let mut text_style = cx.editor_style.text.clone();
17158 if let Some(highlight_style) = old_highlight_id
17159 .and_then(|h| h.style(&cx.editor_style.syntax))
17160 {
17161 text_style = text_style.highlight(highlight_style);
17162 }
17163 div()
17164 .block_mouse_except_scroll()
17165 .pl(cx.anchor_x)
17166 .child(EditorElement::new(
17167 &rename_editor,
17168 EditorStyle {
17169 background: cx.theme().system().transparent,
17170 local_player: cx.editor_style.local_player,
17171 text: text_style,
17172 scrollbar_width: cx.editor_style.scrollbar_width,
17173 syntax: cx.editor_style.syntax.clone(),
17174 status: cx.editor_style.status.clone(),
17175 inlay_hints_style: HighlightStyle {
17176 font_weight: Some(FontWeight::BOLD),
17177 ..make_inlay_hints_style(cx.app)
17178 },
17179 edit_prediction_styles: make_suggestion_styles(
17180 cx.app,
17181 ),
17182 ..EditorStyle::default()
17183 },
17184 ))
17185 .into_any_element()
17186 }
17187 }),
17188 priority: 0,
17189 }],
17190 Some(Autoscroll::fit()),
17191 cx,
17192 )[0];
17193 this.pending_rename = Some(RenameState {
17194 range,
17195 old_name,
17196 editor: rename_editor,
17197 block_id,
17198 });
17199 })?;
17200 }
17201
17202 Ok(())
17203 }))
17204 }
17205
17206 pub fn confirm_rename(
17207 &mut self,
17208 _: &ConfirmRename,
17209 window: &mut Window,
17210 cx: &mut Context<Self>,
17211 ) -> Option<Task<Result<()>>> {
17212 let rename = self.take_rename(false, window, cx)?;
17213 let workspace = self.workspace()?.downgrade();
17214 let (buffer, start) = self
17215 .buffer
17216 .read(cx)
17217 .text_anchor_for_position(rename.range.start, cx)?;
17218 let (end_buffer, _) = self
17219 .buffer
17220 .read(cx)
17221 .text_anchor_for_position(rename.range.end, cx)?;
17222 if buffer != end_buffer {
17223 return None;
17224 }
17225
17226 let old_name = rename.old_name;
17227 let new_name = rename.editor.read(cx).text(cx);
17228
17229 let rename = self.semantics_provider.as_ref()?.perform_rename(
17230 &buffer,
17231 start,
17232 new_name.clone(),
17233 cx,
17234 )?;
17235
17236 Some(cx.spawn_in(window, async move |editor, cx| {
17237 let project_transaction = rename.await?;
17238 Self::open_project_transaction(
17239 &editor,
17240 workspace,
17241 project_transaction,
17242 format!("Rename: {} → {}", old_name, new_name),
17243 cx,
17244 )
17245 .await?;
17246
17247 editor.update(cx, |editor, cx| {
17248 editor.refresh_document_highlights(cx);
17249 })?;
17250 Ok(())
17251 }))
17252 }
17253
17254 fn take_rename(
17255 &mut self,
17256 moving_cursor: bool,
17257 window: &mut Window,
17258 cx: &mut Context<Self>,
17259 ) -> Option<RenameState> {
17260 let rename = self.pending_rename.take()?;
17261 if rename.editor.focus_handle(cx).is_focused(window) {
17262 window.focus(&self.focus_handle);
17263 }
17264
17265 self.remove_blocks(
17266 [rename.block_id].into_iter().collect(),
17267 Some(Autoscroll::fit()),
17268 cx,
17269 );
17270 self.clear_highlights::<Rename>(cx);
17271 self.show_local_selections = true;
17272
17273 if moving_cursor {
17274 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17275 editor.selections.newest::<usize>(cx).head()
17276 });
17277
17278 // Update the selection to match the position of the selection inside
17279 // the rename editor.
17280 let snapshot = self.buffer.read(cx).read(cx);
17281 let rename_range = rename.range.to_offset(&snapshot);
17282 let cursor_in_editor = snapshot
17283 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17284 .min(rename_range.end);
17285 drop(snapshot);
17286
17287 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17288 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17289 });
17290 } else {
17291 self.refresh_document_highlights(cx);
17292 }
17293
17294 Some(rename)
17295 }
17296
17297 pub fn pending_rename(&self) -> Option<&RenameState> {
17298 self.pending_rename.as_ref()
17299 }
17300
17301 fn format(
17302 &mut self,
17303 _: &Format,
17304 window: &mut Window,
17305 cx: &mut Context<Self>,
17306 ) -> Option<Task<Result<()>>> {
17307 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17308
17309 let project = match &self.project {
17310 Some(project) => project.clone(),
17311 None => return None,
17312 };
17313
17314 Some(self.perform_format(
17315 project,
17316 FormatTrigger::Manual,
17317 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17318 window,
17319 cx,
17320 ))
17321 }
17322
17323 fn format_selections(
17324 &mut self,
17325 _: &FormatSelections,
17326 window: &mut Window,
17327 cx: &mut Context<Self>,
17328 ) -> Option<Task<Result<()>>> {
17329 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17330
17331 let project = match &self.project {
17332 Some(project) => project.clone(),
17333 None => return None,
17334 };
17335
17336 let ranges = self
17337 .selections
17338 .all_adjusted(cx)
17339 .into_iter()
17340 .map(|selection| selection.range())
17341 .collect_vec();
17342
17343 Some(self.perform_format(
17344 project,
17345 FormatTrigger::Manual,
17346 FormatTarget::Ranges(ranges),
17347 window,
17348 cx,
17349 ))
17350 }
17351
17352 fn perform_format(
17353 &mut self,
17354 project: Entity<Project>,
17355 trigger: FormatTrigger,
17356 target: FormatTarget,
17357 window: &mut Window,
17358 cx: &mut Context<Self>,
17359 ) -> Task<Result<()>> {
17360 let buffer = self.buffer.clone();
17361 let (buffers, target) = match target {
17362 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17363 FormatTarget::Ranges(selection_ranges) => {
17364 let multi_buffer = buffer.read(cx);
17365 let snapshot = multi_buffer.read(cx);
17366 let mut buffers = HashSet::default();
17367 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17368 BTreeMap::new();
17369 for selection_range in selection_ranges {
17370 for (buffer, buffer_range, _) in
17371 snapshot.range_to_buffer_ranges(selection_range)
17372 {
17373 let buffer_id = buffer.remote_id();
17374 let start = buffer.anchor_before(buffer_range.start);
17375 let end = buffer.anchor_after(buffer_range.end);
17376 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17377 buffer_id_to_ranges
17378 .entry(buffer_id)
17379 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17380 .or_insert_with(|| vec![start..end]);
17381 }
17382 }
17383 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17384 }
17385 };
17386
17387 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17388 let selections_prev = transaction_id_prev
17389 .and_then(|transaction_id_prev| {
17390 // default to selections as they were after the last edit, if we have them,
17391 // instead of how they are now.
17392 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17393 // will take you back to where you made the last edit, instead of staying where you scrolled
17394 self.selection_history
17395 .transaction(transaction_id_prev)
17396 .map(|t| t.0.clone())
17397 })
17398 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17399
17400 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17401 let format = project.update(cx, |project, cx| {
17402 project.format(buffers, target, true, trigger, cx)
17403 });
17404
17405 cx.spawn_in(window, async move |editor, cx| {
17406 let transaction = futures::select_biased! {
17407 transaction = format.log_err().fuse() => transaction,
17408 () = timeout => {
17409 log::warn!("timed out waiting for formatting");
17410 None
17411 }
17412 };
17413
17414 buffer
17415 .update(cx, |buffer, cx| {
17416 if let Some(transaction) = transaction
17417 && !buffer.is_singleton()
17418 {
17419 buffer.push_transaction(&transaction.0, cx);
17420 }
17421 cx.notify();
17422 })
17423 .ok();
17424
17425 if let Some(transaction_id_now) =
17426 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17427 {
17428 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17429 if has_new_transaction {
17430 _ = editor.update(cx, |editor, _| {
17431 editor
17432 .selection_history
17433 .insert_transaction(transaction_id_now, selections_prev);
17434 });
17435 }
17436 }
17437
17438 Ok(())
17439 })
17440 }
17441
17442 fn organize_imports(
17443 &mut self,
17444 _: &OrganizeImports,
17445 window: &mut Window,
17446 cx: &mut Context<Self>,
17447 ) -> Option<Task<Result<()>>> {
17448 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17449 let project = match &self.project {
17450 Some(project) => project.clone(),
17451 None => return None,
17452 };
17453 Some(self.perform_code_action_kind(
17454 project,
17455 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17456 window,
17457 cx,
17458 ))
17459 }
17460
17461 fn perform_code_action_kind(
17462 &mut self,
17463 project: Entity<Project>,
17464 kind: CodeActionKind,
17465 window: &mut Window,
17466 cx: &mut Context<Self>,
17467 ) -> Task<Result<()>> {
17468 let buffer = self.buffer.clone();
17469 let buffers = buffer.read(cx).all_buffers();
17470 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17471 let apply_action = project.update(cx, |project, cx| {
17472 project.apply_code_action_kind(buffers, kind, true, cx)
17473 });
17474 cx.spawn_in(window, async move |_, cx| {
17475 let transaction = futures::select_biased! {
17476 () = timeout => {
17477 log::warn!("timed out waiting for executing code action");
17478 None
17479 }
17480 transaction = apply_action.log_err().fuse() => transaction,
17481 };
17482 buffer
17483 .update(cx, |buffer, cx| {
17484 // check if we need this
17485 if let Some(transaction) = transaction
17486 && !buffer.is_singleton()
17487 {
17488 buffer.push_transaction(&transaction.0, cx);
17489 }
17490 cx.notify();
17491 })
17492 .ok();
17493 Ok(())
17494 })
17495 }
17496
17497 pub fn restart_language_server(
17498 &mut self,
17499 _: &RestartLanguageServer,
17500 _: &mut Window,
17501 cx: &mut Context<Self>,
17502 ) {
17503 if let Some(project) = self.project.clone() {
17504 self.buffer.update(cx, |multi_buffer, cx| {
17505 project.update(cx, |project, cx| {
17506 project.restart_language_servers_for_buffers(
17507 multi_buffer.all_buffers().into_iter().collect(),
17508 HashSet::default(),
17509 cx,
17510 );
17511 });
17512 })
17513 }
17514 }
17515
17516 pub fn stop_language_server(
17517 &mut self,
17518 _: &StopLanguageServer,
17519 _: &mut Window,
17520 cx: &mut Context<Self>,
17521 ) {
17522 if let Some(project) = self.project.clone() {
17523 self.buffer.update(cx, |multi_buffer, cx| {
17524 project.update(cx, |project, cx| {
17525 project.stop_language_servers_for_buffers(
17526 multi_buffer.all_buffers().into_iter().collect(),
17527 HashSet::default(),
17528 cx,
17529 );
17530 cx.emit(project::Event::RefreshInlayHints);
17531 });
17532 });
17533 }
17534 }
17535
17536 fn cancel_language_server_work(
17537 workspace: &mut Workspace,
17538 _: &actions::CancelLanguageServerWork,
17539 _: &mut Window,
17540 cx: &mut Context<Workspace>,
17541 ) {
17542 let project = workspace.project();
17543 let buffers = workspace
17544 .active_item(cx)
17545 .and_then(|item| item.act_as::<Editor>(cx))
17546 .map_or(HashSet::default(), |editor| {
17547 editor.read(cx).buffer.read(cx).all_buffers()
17548 });
17549 project.update(cx, |project, cx| {
17550 project.cancel_language_server_work_for_buffers(buffers, cx);
17551 });
17552 }
17553
17554 fn show_character_palette(
17555 &mut self,
17556 _: &ShowCharacterPalette,
17557 window: &mut Window,
17558 _: &mut Context<Self>,
17559 ) {
17560 window.show_character_palette();
17561 }
17562
17563 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17564 if !self.diagnostics_enabled() {
17565 return;
17566 }
17567
17568 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17569 let buffer = self.buffer.read(cx).snapshot(cx);
17570 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17571 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17572 let is_valid = buffer
17573 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17574 .any(|entry| {
17575 entry.diagnostic.is_primary
17576 && !entry.range.is_empty()
17577 && entry.range.start == primary_range_start
17578 && entry.diagnostic.message == active_diagnostics.active_message
17579 });
17580
17581 if !is_valid {
17582 self.dismiss_diagnostics(cx);
17583 }
17584 }
17585 }
17586
17587 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17588 match &self.active_diagnostics {
17589 ActiveDiagnostic::Group(group) => Some(group),
17590 _ => None,
17591 }
17592 }
17593
17594 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17595 if !self.diagnostics_enabled() {
17596 return;
17597 }
17598 self.dismiss_diagnostics(cx);
17599 self.active_diagnostics = ActiveDiagnostic::All;
17600 }
17601
17602 fn activate_diagnostics(
17603 &mut self,
17604 buffer_id: BufferId,
17605 diagnostic: DiagnosticEntryRef<'_, usize>,
17606 window: &mut Window,
17607 cx: &mut Context<Self>,
17608 ) {
17609 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17610 return;
17611 }
17612 self.dismiss_diagnostics(cx);
17613 let snapshot = self.snapshot(window, cx);
17614 let buffer = self.buffer.read(cx).snapshot(cx);
17615 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17616 return;
17617 };
17618
17619 let diagnostic_group = buffer
17620 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17621 .collect::<Vec<_>>();
17622
17623 let blocks =
17624 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17625
17626 let blocks = self.display_map.update(cx, |display_map, cx| {
17627 display_map.insert_blocks(blocks, cx).into_iter().collect()
17628 });
17629 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17630 active_range: buffer.anchor_before(diagnostic.range.start)
17631 ..buffer.anchor_after(diagnostic.range.end),
17632 active_message: diagnostic.diagnostic.message.clone(),
17633 group_id: diagnostic.diagnostic.group_id,
17634 blocks,
17635 });
17636 cx.notify();
17637 }
17638
17639 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17640 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17641 return;
17642 };
17643
17644 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17645 if let ActiveDiagnostic::Group(group) = prev {
17646 self.display_map.update(cx, |display_map, cx| {
17647 display_map.remove_blocks(group.blocks, cx);
17648 });
17649 cx.notify();
17650 }
17651 }
17652
17653 /// Disable inline diagnostics rendering for this editor.
17654 pub fn disable_inline_diagnostics(&mut self) {
17655 self.inline_diagnostics_enabled = false;
17656 self.inline_diagnostics_update = Task::ready(());
17657 self.inline_diagnostics.clear();
17658 }
17659
17660 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17661 self.diagnostics_enabled = false;
17662 self.dismiss_diagnostics(cx);
17663 self.inline_diagnostics_update = Task::ready(());
17664 self.inline_diagnostics.clear();
17665 }
17666
17667 pub fn disable_word_completions(&mut self) {
17668 self.word_completions_enabled = false;
17669 }
17670
17671 pub fn diagnostics_enabled(&self) -> bool {
17672 self.diagnostics_enabled && self.mode.is_full()
17673 }
17674
17675 pub fn inline_diagnostics_enabled(&self) -> bool {
17676 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17677 }
17678
17679 pub fn show_inline_diagnostics(&self) -> bool {
17680 self.show_inline_diagnostics
17681 }
17682
17683 pub fn toggle_inline_diagnostics(
17684 &mut self,
17685 _: &ToggleInlineDiagnostics,
17686 window: &mut Window,
17687 cx: &mut Context<Editor>,
17688 ) {
17689 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17690 self.refresh_inline_diagnostics(false, window, cx);
17691 }
17692
17693 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17694 self.diagnostics_max_severity = severity;
17695 self.display_map.update(cx, |display_map, _| {
17696 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17697 });
17698 }
17699
17700 pub fn toggle_diagnostics(
17701 &mut self,
17702 _: &ToggleDiagnostics,
17703 window: &mut Window,
17704 cx: &mut Context<Editor>,
17705 ) {
17706 if !self.diagnostics_enabled() {
17707 return;
17708 }
17709
17710 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17711 EditorSettings::get_global(cx)
17712 .diagnostics_max_severity
17713 .filter(|severity| severity != &DiagnosticSeverity::Off)
17714 .unwrap_or(DiagnosticSeverity::Hint)
17715 } else {
17716 DiagnosticSeverity::Off
17717 };
17718 self.set_max_diagnostics_severity(new_severity, cx);
17719 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17720 self.active_diagnostics = ActiveDiagnostic::None;
17721 self.inline_diagnostics_update = Task::ready(());
17722 self.inline_diagnostics.clear();
17723 } else {
17724 self.refresh_inline_diagnostics(false, window, cx);
17725 }
17726
17727 cx.notify();
17728 }
17729
17730 pub fn toggle_minimap(
17731 &mut self,
17732 _: &ToggleMinimap,
17733 window: &mut Window,
17734 cx: &mut Context<Editor>,
17735 ) {
17736 if self.supports_minimap(cx) {
17737 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17738 }
17739 }
17740
17741 fn refresh_inline_diagnostics(
17742 &mut self,
17743 debounce: bool,
17744 window: &mut Window,
17745 cx: &mut Context<Self>,
17746 ) {
17747 let max_severity = ProjectSettings::get_global(cx)
17748 .diagnostics
17749 .inline
17750 .max_severity
17751 .unwrap_or(self.diagnostics_max_severity);
17752
17753 if !self.inline_diagnostics_enabled()
17754 || !self.show_inline_diagnostics
17755 || max_severity == DiagnosticSeverity::Off
17756 {
17757 self.inline_diagnostics_update = Task::ready(());
17758 self.inline_diagnostics.clear();
17759 return;
17760 }
17761
17762 let debounce_ms = ProjectSettings::get_global(cx)
17763 .diagnostics
17764 .inline
17765 .update_debounce_ms;
17766 let debounce = if debounce && debounce_ms > 0 {
17767 Some(Duration::from_millis(debounce_ms))
17768 } else {
17769 None
17770 };
17771 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17772 if let Some(debounce) = debounce {
17773 cx.background_executor().timer(debounce).await;
17774 }
17775 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17776 editor
17777 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17778 .ok()
17779 }) else {
17780 return;
17781 };
17782
17783 let new_inline_diagnostics = cx
17784 .background_spawn(async move {
17785 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17786 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17787 let message = diagnostic_entry
17788 .diagnostic
17789 .message
17790 .split_once('\n')
17791 .map(|(line, _)| line)
17792 .map(SharedString::new)
17793 .unwrap_or_else(|| {
17794 SharedString::new(&*diagnostic_entry.diagnostic.message)
17795 });
17796 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17797 let (Ok(i) | Err(i)) = inline_diagnostics
17798 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17799 inline_diagnostics.insert(
17800 i,
17801 (
17802 start_anchor,
17803 InlineDiagnostic {
17804 message,
17805 group_id: diagnostic_entry.diagnostic.group_id,
17806 start: diagnostic_entry.range.start.to_point(&snapshot),
17807 is_primary: diagnostic_entry.diagnostic.is_primary,
17808 severity: diagnostic_entry.diagnostic.severity,
17809 },
17810 ),
17811 );
17812 }
17813 inline_diagnostics
17814 })
17815 .await;
17816
17817 editor
17818 .update(cx, |editor, cx| {
17819 editor.inline_diagnostics = new_inline_diagnostics;
17820 cx.notify();
17821 })
17822 .ok();
17823 });
17824 }
17825
17826 fn pull_diagnostics(
17827 &mut self,
17828 buffer_id: Option<BufferId>,
17829 window: &Window,
17830 cx: &mut Context<Self>,
17831 ) -> Option<()> {
17832 if self.ignore_lsp_data() {
17833 return None;
17834 }
17835 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17836 .diagnostics
17837 .lsp_pull_diagnostics;
17838 if !pull_diagnostics_settings.enabled {
17839 return None;
17840 }
17841 let project = self.project()?.downgrade();
17842 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17843 let mut buffers = self.buffer.read(cx).all_buffers();
17844 buffers.retain(|buffer| {
17845 let buffer_id_to_retain = buffer.read(cx).remote_id();
17846 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
17847 && self.registered_buffers.contains_key(&buffer_id_to_retain)
17848 });
17849 if buffers.is_empty() {
17850 self.pull_diagnostics_task = Task::ready(());
17851 return None;
17852 }
17853
17854 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17855 cx.background_executor().timer(debounce).await;
17856
17857 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17858 buffers
17859 .into_iter()
17860 .filter_map(|buffer| {
17861 project
17862 .update(cx, |project, cx| {
17863 project.lsp_store().update(cx, |lsp_store, cx| {
17864 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17865 })
17866 })
17867 .ok()
17868 })
17869 .collect::<FuturesUnordered<_>>()
17870 }) else {
17871 return;
17872 };
17873
17874 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17875 match pull_task {
17876 Ok(()) => {
17877 if editor
17878 .update_in(cx, |editor, window, cx| {
17879 editor.update_diagnostics_state(window, cx);
17880 })
17881 .is_err()
17882 {
17883 return;
17884 }
17885 }
17886 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17887 }
17888 }
17889 });
17890
17891 Some(())
17892 }
17893
17894 pub fn set_selections_from_remote(
17895 &mut self,
17896 selections: Vec<Selection<Anchor>>,
17897 pending_selection: Option<Selection<Anchor>>,
17898 window: &mut Window,
17899 cx: &mut Context<Self>,
17900 ) {
17901 let old_cursor_position = self.selections.newest_anchor().head();
17902 self.selections.change_with(cx, |s| {
17903 s.select_anchors(selections);
17904 if let Some(pending_selection) = pending_selection {
17905 s.set_pending(pending_selection, SelectMode::Character);
17906 } else {
17907 s.clear_pending();
17908 }
17909 });
17910 self.selections_did_change(
17911 false,
17912 &old_cursor_position,
17913 SelectionEffects::default(),
17914 window,
17915 cx,
17916 );
17917 }
17918
17919 pub fn transact(
17920 &mut self,
17921 window: &mut Window,
17922 cx: &mut Context<Self>,
17923 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17924 ) -> Option<TransactionId> {
17925 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17926 this.start_transaction_at(Instant::now(), window, cx);
17927 update(this, window, cx);
17928 this.end_transaction_at(Instant::now(), cx)
17929 })
17930 }
17931
17932 pub fn start_transaction_at(
17933 &mut self,
17934 now: Instant,
17935 window: &mut Window,
17936 cx: &mut Context<Self>,
17937 ) -> Option<TransactionId> {
17938 self.end_selection(window, cx);
17939 if let Some(tx_id) = self
17940 .buffer
17941 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17942 {
17943 self.selection_history
17944 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
17945 cx.emit(EditorEvent::TransactionBegun {
17946 transaction_id: tx_id,
17947 });
17948 Some(tx_id)
17949 } else {
17950 None
17951 }
17952 }
17953
17954 pub fn end_transaction_at(
17955 &mut self,
17956 now: Instant,
17957 cx: &mut Context<Self>,
17958 ) -> Option<TransactionId> {
17959 if let Some(transaction_id) = self
17960 .buffer
17961 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17962 {
17963 if let Some((_, end_selections)) =
17964 self.selection_history.transaction_mut(transaction_id)
17965 {
17966 *end_selections = Some(self.selections.disjoint_anchors_arc());
17967 } else {
17968 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17969 }
17970
17971 cx.emit(EditorEvent::Edited { transaction_id });
17972 Some(transaction_id)
17973 } else {
17974 None
17975 }
17976 }
17977
17978 pub fn modify_transaction_selection_history(
17979 &mut self,
17980 transaction_id: TransactionId,
17981 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17982 ) -> bool {
17983 self.selection_history
17984 .transaction_mut(transaction_id)
17985 .map(modify)
17986 .is_some()
17987 }
17988
17989 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17990 if self.selection_mark_mode {
17991 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17992 s.move_with(|_, sel| {
17993 sel.collapse_to(sel.head(), SelectionGoal::None);
17994 });
17995 })
17996 }
17997 self.selection_mark_mode = true;
17998 cx.notify();
17999 }
18000
18001 pub fn swap_selection_ends(
18002 &mut self,
18003 _: &actions::SwapSelectionEnds,
18004 window: &mut Window,
18005 cx: &mut Context<Self>,
18006 ) {
18007 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18008 s.move_with(|_, sel| {
18009 if sel.start != sel.end {
18010 sel.reversed = !sel.reversed
18011 }
18012 });
18013 });
18014 self.request_autoscroll(Autoscroll::newest(), cx);
18015 cx.notify();
18016 }
18017
18018 pub fn toggle_focus(
18019 workspace: &mut Workspace,
18020 _: &actions::ToggleFocus,
18021 window: &mut Window,
18022 cx: &mut Context<Workspace>,
18023 ) {
18024 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18025 return;
18026 };
18027 workspace.activate_item(&item, true, true, window, cx);
18028 }
18029
18030 pub fn toggle_fold(
18031 &mut self,
18032 _: &actions::ToggleFold,
18033 window: &mut Window,
18034 cx: &mut Context<Self>,
18035 ) {
18036 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18037 let selection = self.selections.newest::<Point>(cx);
18038
18039 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18040 let range = if selection.is_empty() {
18041 let point = selection.head().to_display_point(&display_map);
18042 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18043 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18044 .to_point(&display_map);
18045 start..end
18046 } else {
18047 selection.range()
18048 };
18049 if display_map.folds_in_range(range).next().is_some() {
18050 self.unfold_lines(&Default::default(), window, cx)
18051 } else {
18052 self.fold(&Default::default(), window, cx)
18053 }
18054 } else {
18055 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18056 let buffer_ids: HashSet<_> = self
18057 .selections
18058 .disjoint_anchor_ranges()
18059 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18060 .collect();
18061
18062 let should_unfold = buffer_ids
18063 .iter()
18064 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18065
18066 for buffer_id in buffer_ids {
18067 if should_unfold {
18068 self.unfold_buffer(buffer_id, cx);
18069 } else {
18070 self.fold_buffer(buffer_id, cx);
18071 }
18072 }
18073 }
18074 }
18075
18076 pub fn toggle_fold_recursive(
18077 &mut self,
18078 _: &actions::ToggleFoldRecursive,
18079 window: &mut Window,
18080 cx: &mut Context<Self>,
18081 ) {
18082 let selection = self.selections.newest::<Point>(cx);
18083
18084 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18085 let range = if selection.is_empty() {
18086 let point = selection.head().to_display_point(&display_map);
18087 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18088 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18089 .to_point(&display_map);
18090 start..end
18091 } else {
18092 selection.range()
18093 };
18094 if display_map.folds_in_range(range).next().is_some() {
18095 self.unfold_recursive(&Default::default(), window, cx)
18096 } else {
18097 self.fold_recursive(&Default::default(), window, cx)
18098 }
18099 }
18100
18101 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18102 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18103 let mut to_fold = Vec::new();
18104 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18105 let selections = self.selections.all_adjusted(cx);
18106
18107 for selection in selections {
18108 let range = selection.range().sorted();
18109 let buffer_start_row = range.start.row;
18110
18111 if range.start.row != range.end.row {
18112 let mut found = false;
18113 let mut row = range.start.row;
18114 while row <= range.end.row {
18115 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18116 {
18117 found = true;
18118 row = crease.range().end.row + 1;
18119 to_fold.push(crease);
18120 } else {
18121 row += 1
18122 }
18123 }
18124 if found {
18125 continue;
18126 }
18127 }
18128
18129 for row in (0..=range.start.row).rev() {
18130 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18131 && crease.range().end.row >= buffer_start_row
18132 {
18133 to_fold.push(crease);
18134 if row <= range.start.row {
18135 break;
18136 }
18137 }
18138 }
18139 }
18140
18141 self.fold_creases(to_fold, true, window, cx);
18142 } else {
18143 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18144 let buffer_ids = self
18145 .selections
18146 .disjoint_anchor_ranges()
18147 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18148 .collect::<HashSet<_>>();
18149 for buffer_id in buffer_ids {
18150 self.fold_buffer(buffer_id, cx);
18151 }
18152 }
18153 }
18154
18155 pub fn toggle_fold_all(
18156 &mut self,
18157 _: &actions::ToggleFoldAll,
18158 window: &mut Window,
18159 cx: &mut Context<Self>,
18160 ) {
18161 if self.buffer.read(cx).is_singleton() {
18162 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18163 let has_folds = display_map
18164 .folds_in_range(0..display_map.buffer_snapshot().len())
18165 .next()
18166 .is_some();
18167
18168 if has_folds {
18169 self.unfold_all(&actions::UnfoldAll, window, cx);
18170 } else {
18171 self.fold_all(&actions::FoldAll, window, cx);
18172 }
18173 } else {
18174 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18175 let should_unfold = buffer_ids
18176 .iter()
18177 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18178
18179 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18180 editor
18181 .update_in(cx, |editor, _, cx| {
18182 for buffer_id in buffer_ids {
18183 if should_unfold {
18184 editor.unfold_buffer(buffer_id, cx);
18185 } else {
18186 editor.fold_buffer(buffer_id, cx);
18187 }
18188 }
18189 })
18190 .ok();
18191 });
18192 }
18193 }
18194
18195 fn fold_at_level(
18196 &mut self,
18197 fold_at: &FoldAtLevel,
18198 window: &mut Window,
18199 cx: &mut Context<Self>,
18200 ) {
18201 if !self.buffer.read(cx).is_singleton() {
18202 return;
18203 }
18204
18205 let fold_at_level = fold_at.0;
18206 let snapshot = self.buffer.read(cx).snapshot(cx);
18207 let mut to_fold = Vec::new();
18208 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18209
18210 let row_ranges_to_keep: Vec<Range<u32>> = self
18211 .selections
18212 .all::<Point>(cx)
18213 .into_iter()
18214 .map(|sel| sel.start.row..sel.end.row)
18215 .collect();
18216
18217 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18218 while start_row < end_row {
18219 match self
18220 .snapshot(window, cx)
18221 .crease_for_buffer_row(MultiBufferRow(start_row))
18222 {
18223 Some(crease) => {
18224 let nested_start_row = crease.range().start.row + 1;
18225 let nested_end_row = crease.range().end.row;
18226
18227 if current_level < fold_at_level {
18228 stack.push((nested_start_row, nested_end_row, current_level + 1));
18229 } else if current_level == fold_at_level {
18230 // Fold iff there is no selection completely contained within the fold region
18231 if !row_ranges_to_keep.iter().any(|selection| {
18232 selection.end >= nested_start_row
18233 && selection.start <= nested_end_row
18234 }) {
18235 to_fold.push(crease);
18236 }
18237 }
18238
18239 start_row = nested_end_row + 1;
18240 }
18241 None => start_row += 1,
18242 }
18243 }
18244 }
18245
18246 self.fold_creases(to_fold, true, window, cx);
18247 }
18248
18249 pub fn fold_at_level_1(
18250 &mut self,
18251 _: &actions::FoldAtLevel1,
18252 window: &mut Window,
18253 cx: &mut Context<Self>,
18254 ) {
18255 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18256 }
18257
18258 pub fn fold_at_level_2(
18259 &mut self,
18260 _: &actions::FoldAtLevel2,
18261 window: &mut Window,
18262 cx: &mut Context<Self>,
18263 ) {
18264 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18265 }
18266
18267 pub fn fold_at_level_3(
18268 &mut self,
18269 _: &actions::FoldAtLevel3,
18270 window: &mut Window,
18271 cx: &mut Context<Self>,
18272 ) {
18273 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18274 }
18275
18276 pub fn fold_at_level_4(
18277 &mut self,
18278 _: &actions::FoldAtLevel4,
18279 window: &mut Window,
18280 cx: &mut Context<Self>,
18281 ) {
18282 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18283 }
18284
18285 pub fn fold_at_level_5(
18286 &mut self,
18287 _: &actions::FoldAtLevel5,
18288 window: &mut Window,
18289 cx: &mut Context<Self>,
18290 ) {
18291 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18292 }
18293
18294 pub fn fold_at_level_6(
18295 &mut self,
18296 _: &actions::FoldAtLevel6,
18297 window: &mut Window,
18298 cx: &mut Context<Self>,
18299 ) {
18300 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18301 }
18302
18303 pub fn fold_at_level_7(
18304 &mut self,
18305 _: &actions::FoldAtLevel7,
18306 window: &mut Window,
18307 cx: &mut Context<Self>,
18308 ) {
18309 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18310 }
18311
18312 pub fn fold_at_level_8(
18313 &mut self,
18314 _: &actions::FoldAtLevel8,
18315 window: &mut Window,
18316 cx: &mut Context<Self>,
18317 ) {
18318 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18319 }
18320
18321 pub fn fold_at_level_9(
18322 &mut self,
18323 _: &actions::FoldAtLevel9,
18324 window: &mut Window,
18325 cx: &mut Context<Self>,
18326 ) {
18327 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18328 }
18329
18330 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18331 if self.buffer.read(cx).is_singleton() {
18332 let mut fold_ranges = Vec::new();
18333 let snapshot = self.buffer.read(cx).snapshot(cx);
18334
18335 for row in 0..snapshot.max_row().0 {
18336 if let Some(foldable_range) = self
18337 .snapshot(window, cx)
18338 .crease_for_buffer_row(MultiBufferRow(row))
18339 {
18340 fold_ranges.push(foldable_range);
18341 }
18342 }
18343
18344 self.fold_creases(fold_ranges, true, window, cx);
18345 } else {
18346 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18347 editor
18348 .update_in(cx, |editor, _, cx| {
18349 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18350 editor.fold_buffer(buffer_id, cx);
18351 }
18352 })
18353 .ok();
18354 });
18355 }
18356 }
18357
18358 pub fn fold_function_bodies(
18359 &mut self,
18360 _: &actions::FoldFunctionBodies,
18361 window: &mut Window,
18362 cx: &mut Context<Self>,
18363 ) {
18364 let snapshot = self.buffer.read(cx).snapshot(cx);
18365
18366 let ranges = snapshot
18367 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18368 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18369 .collect::<Vec<_>>();
18370
18371 let creases = ranges
18372 .into_iter()
18373 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18374 .collect();
18375
18376 self.fold_creases(creases, true, window, cx);
18377 }
18378
18379 pub fn fold_recursive(
18380 &mut self,
18381 _: &actions::FoldRecursive,
18382 window: &mut Window,
18383 cx: &mut Context<Self>,
18384 ) {
18385 let mut to_fold = Vec::new();
18386 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18387 let selections = self.selections.all_adjusted(cx);
18388
18389 for selection in selections {
18390 let range = selection.range().sorted();
18391 let buffer_start_row = range.start.row;
18392
18393 if range.start.row != range.end.row {
18394 let mut found = false;
18395 for row in range.start.row..=range.end.row {
18396 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18397 found = true;
18398 to_fold.push(crease);
18399 }
18400 }
18401 if found {
18402 continue;
18403 }
18404 }
18405
18406 for row in (0..=range.start.row).rev() {
18407 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18408 if crease.range().end.row >= buffer_start_row {
18409 to_fold.push(crease);
18410 } else {
18411 break;
18412 }
18413 }
18414 }
18415 }
18416
18417 self.fold_creases(to_fold, true, window, cx);
18418 }
18419
18420 pub fn fold_at(
18421 &mut self,
18422 buffer_row: MultiBufferRow,
18423 window: &mut Window,
18424 cx: &mut Context<Self>,
18425 ) {
18426 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18427
18428 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18429 let autoscroll = self
18430 .selections
18431 .all::<Point>(cx)
18432 .iter()
18433 .any(|selection| crease.range().overlaps(&selection.range()));
18434
18435 self.fold_creases(vec![crease], autoscroll, window, cx);
18436 }
18437 }
18438
18439 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18440 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18441 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18442 let buffer = display_map.buffer_snapshot();
18443 let selections = self.selections.all::<Point>(cx);
18444 let ranges = selections
18445 .iter()
18446 .map(|s| {
18447 let range = s.display_range(&display_map).sorted();
18448 let mut start = range.start.to_point(&display_map);
18449 let mut end = range.end.to_point(&display_map);
18450 start.column = 0;
18451 end.column = buffer.line_len(MultiBufferRow(end.row));
18452 start..end
18453 })
18454 .collect::<Vec<_>>();
18455
18456 self.unfold_ranges(&ranges, true, true, cx);
18457 } else {
18458 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18459 let buffer_ids = self
18460 .selections
18461 .disjoint_anchor_ranges()
18462 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18463 .collect::<HashSet<_>>();
18464 for buffer_id in buffer_ids {
18465 self.unfold_buffer(buffer_id, cx);
18466 }
18467 }
18468 }
18469
18470 pub fn unfold_recursive(
18471 &mut self,
18472 _: &UnfoldRecursive,
18473 _window: &mut Window,
18474 cx: &mut Context<Self>,
18475 ) {
18476 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18477 let selections = self.selections.all::<Point>(cx);
18478 let ranges = selections
18479 .iter()
18480 .map(|s| {
18481 let mut range = s.display_range(&display_map).sorted();
18482 *range.start.column_mut() = 0;
18483 *range.end.column_mut() = display_map.line_len(range.end.row());
18484 let start = range.start.to_point(&display_map);
18485 let end = range.end.to_point(&display_map);
18486 start..end
18487 })
18488 .collect::<Vec<_>>();
18489
18490 self.unfold_ranges(&ranges, true, true, cx);
18491 }
18492
18493 pub fn unfold_at(
18494 &mut self,
18495 buffer_row: MultiBufferRow,
18496 _window: &mut Window,
18497 cx: &mut Context<Self>,
18498 ) {
18499 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18500
18501 let intersection_range = Point::new(buffer_row.0, 0)
18502 ..Point::new(
18503 buffer_row.0,
18504 display_map.buffer_snapshot().line_len(buffer_row),
18505 );
18506
18507 let autoscroll = self
18508 .selections
18509 .all::<Point>(cx)
18510 .iter()
18511 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18512
18513 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18514 }
18515
18516 pub fn unfold_all(
18517 &mut self,
18518 _: &actions::UnfoldAll,
18519 _window: &mut Window,
18520 cx: &mut Context<Self>,
18521 ) {
18522 if self.buffer.read(cx).is_singleton() {
18523 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18524 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18525 } else {
18526 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18527 editor
18528 .update(cx, |editor, cx| {
18529 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18530 editor.unfold_buffer(buffer_id, cx);
18531 }
18532 })
18533 .ok();
18534 });
18535 }
18536 }
18537
18538 pub fn fold_selected_ranges(
18539 &mut self,
18540 _: &FoldSelectedRanges,
18541 window: &mut Window,
18542 cx: &mut Context<Self>,
18543 ) {
18544 let selections = self.selections.all_adjusted(cx);
18545 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18546 let ranges = selections
18547 .into_iter()
18548 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18549 .collect::<Vec<_>>();
18550 self.fold_creases(ranges, true, window, cx);
18551 }
18552
18553 pub fn fold_ranges<T: ToOffset + Clone>(
18554 &mut self,
18555 ranges: Vec<Range<T>>,
18556 auto_scroll: bool,
18557 window: &mut Window,
18558 cx: &mut Context<Self>,
18559 ) {
18560 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18561 let ranges = ranges
18562 .into_iter()
18563 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18564 .collect::<Vec<_>>();
18565 self.fold_creases(ranges, auto_scroll, window, cx);
18566 }
18567
18568 pub fn fold_creases<T: ToOffset + Clone>(
18569 &mut self,
18570 creases: Vec<Crease<T>>,
18571 auto_scroll: bool,
18572 _window: &mut Window,
18573 cx: &mut Context<Self>,
18574 ) {
18575 if creases.is_empty() {
18576 return;
18577 }
18578
18579 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18580
18581 if auto_scroll {
18582 self.request_autoscroll(Autoscroll::fit(), cx);
18583 }
18584
18585 cx.notify();
18586
18587 self.scrollbar_marker_state.dirty = true;
18588 self.folds_did_change(cx);
18589 }
18590
18591 /// Removes any folds whose ranges intersect any of the given ranges.
18592 pub fn unfold_ranges<T: ToOffset + Clone>(
18593 &mut self,
18594 ranges: &[Range<T>],
18595 inclusive: bool,
18596 auto_scroll: bool,
18597 cx: &mut Context<Self>,
18598 ) {
18599 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18600 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18601 });
18602 self.folds_did_change(cx);
18603 }
18604
18605 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18606 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18607 return;
18608 }
18609 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18610 self.display_map.update(cx, |display_map, cx| {
18611 display_map.fold_buffers([buffer_id], cx)
18612 });
18613 cx.emit(EditorEvent::BufferFoldToggled {
18614 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18615 folded: true,
18616 });
18617 cx.notify();
18618 }
18619
18620 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18621 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18622 return;
18623 }
18624 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18625 self.display_map.update(cx, |display_map, cx| {
18626 display_map.unfold_buffers([buffer_id], cx);
18627 });
18628 cx.emit(EditorEvent::BufferFoldToggled {
18629 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18630 folded: false,
18631 });
18632 cx.notify();
18633 }
18634
18635 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18636 self.display_map.read(cx).is_buffer_folded(buffer)
18637 }
18638
18639 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18640 self.display_map.read(cx).folded_buffers()
18641 }
18642
18643 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18644 self.display_map.update(cx, |display_map, cx| {
18645 display_map.disable_header_for_buffer(buffer_id, cx);
18646 });
18647 cx.notify();
18648 }
18649
18650 /// Removes any folds with the given ranges.
18651 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18652 &mut self,
18653 ranges: &[Range<T>],
18654 type_id: TypeId,
18655 auto_scroll: bool,
18656 cx: &mut Context<Self>,
18657 ) {
18658 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18659 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18660 });
18661 self.folds_did_change(cx);
18662 }
18663
18664 fn remove_folds_with<T: ToOffset + Clone>(
18665 &mut self,
18666 ranges: &[Range<T>],
18667 auto_scroll: bool,
18668 cx: &mut Context<Self>,
18669 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18670 ) {
18671 if ranges.is_empty() {
18672 return;
18673 }
18674
18675 let mut buffers_affected = HashSet::default();
18676 let multi_buffer = self.buffer().read(cx);
18677 for range in ranges {
18678 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18679 buffers_affected.insert(buffer.read(cx).remote_id());
18680 };
18681 }
18682
18683 self.display_map.update(cx, update);
18684
18685 if auto_scroll {
18686 self.request_autoscroll(Autoscroll::fit(), cx);
18687 }
18688
18689 cx.notify();
18690 self.scrollbar_marker_state.dirty = true;
18691 self.active_indent_guides_state.dirty = true;
18692 }
18693
18694 pub fn update_renderer_widths(
18695 &mut self,
18696 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18697 cx: &mut Context<Self>,
18698 ) -> bool {
18699 self.display_map
18700 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18701 }
18702
18703 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18704 self.display_map.read(cx).fold_placeholder.clone()
18705 }
18706
18707 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18708 self.buffer.update(cx, |buffer, cx| {
18709 buffer.set_all_diff_hunks_expanded(cx);
18710 });
18711 }
18712
18713 pub fn expand_all_diff_hunks(
18714 &mut self,
18715 _: &ExpandAllDiffHunks,
18716 _window: &mut Window,
18717 cx: &mut Context<Self>,
18718 ) {
18719 self.buffer.update(cx, |buffer, cx| {
18720 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18721 });
18722 }
18723
18724 pub fn toggle_selected_diff_hunks(
18725 &mut self,
18726 _: &ToggleSelectedDiffHunks,
18727 _window: &mut Window,
18728 cx: &mut Context<Self>,
18729 ) {
18730 let ranges: Vec<_> = self
18731 .selections
18732 .disjoint_anchors()
18733 .iter()
18734 .map(|s| s.range())
18735 .collect();
18736 self.toggle_diff_hunks_in_ranges(ranges, cx);
18737 }
18738
18739 pub fn diff_hunks_in_ranges<'a>(
18740 &'a self,
18741 ranges: &'a [Range<Anchor>],
18742 buffer: &'a MultiBufferSnapshot,
18743 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18744 ranges.iter().flat_map(move |range| {
18745 let end_excerpt_id = range.end.excerpt_id;
18746 let range = range.to_point(buffer);
18747 let mut peek_end = range.end;
18748 if range.end.row < buffer.max_row().0 {
18749 peek_end = Point::new(range.end.row + 1, 0);
18750 }
18751 buffer
18752 .diff_hunks_in_range(range.start..peek_end)
18753 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18754 })
18755 }
18756
18757 pub fn has_stageable_diff_hunks_in_ranges(
18758 &self,
18759 ranges: &[Range<Anchor>],
18760 snapshot: &MultiBufferSnapshot,
18761 ) -> bool {
18762 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18763 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18764 }
18765
18766 pub fn toggle_staged_selected_diff_hunks(
18767 &mut self,
18768 _: &::git::ToggleStaged,
18769 _: &mut Window,
18770 cx: &mut Context<Self>,
18771 ) {
18772 let snapshot = self.buffer.read(cx).snapshot(cx);
18773 let ranges: Vec<_> = self
18774 .selections
18775 .disjoint_anchors()
18776 .iter()
18777 .map(|s| s.range())
18778 .collect();
18779 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18780 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18781 }
18782
18783 pub fn set_render_diff_hunk_controls(
18784 &mut self,
18785 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18786 cx: &mut Context<Self>,
18787 ) {
18788 self.render_diff_hunk_controls = render_diff_hunk_controls;
18789 cx.notify();
18790 }
18791
18792 pub fn stage_and_next(
18793 &mut self,
18794 _: &::git::StageAndNext,
18795 window: &mut Window,
18796 cx: &mut Context<Self>,
18797 ) {
18798 self.do_stage_or_unstage_and_next(true, window, cx);
18799 }
18800
18801 pub fn unstage_and_next(
18802 &mut self,
18803 _: &::git::UnstageAndNext,
18804 window: &mut Window,
18805 cx: &mut Context<Self>,
18806 ) {
18807 self.do_stage_or_unstage_and_next(false, window, cx);
18808 }
18809
18810 pub fn stage_or_unstage_diff_hunks(
18811 &mut self,
18812 stage: bool,
18813 ranges: Vec<Range<Anchor>>,
18814 cx: &mut Context<Self>,
18815 ) {
18816 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18817 cx.spawn(async move |this, cx| {
18818 task.await?;
18819 this.update(cx, |this, cx| {
18820 let snapshot = this.buffer.read(cx).snapshot(cx);
18821 let chunk_by = this
18822 .diff_hunks_in_ranges(&ranges, &snapshot)
18823 .chunk_by(|hunk| hunk.buffer_id);
18824 for (buffer_id, hunks) in &chunk_by {
18825 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18826 }
18827 })
18828 })
18829 .detach_and_log_err(cx);
18830 }
18831
18832 fn save_buffers_for_ranges_if_needed(
18833 &mut self,
18834 ranges: &[Range<Anchor>],
18835 cx: &mut Context<Editor>,
18836 ) -> Task<Result<()>> {
18837 let multibuffer = self.buffer.read(cx);
18838 let snapshot = multibuffer.read(cx);
18839 let buffer_ids: HashSet<_> = ranges
18840 .iter()
18841 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18842 .collect();
18843 drop(snapshot);
18844
18845 let mut buffers = HashSet::default();
18846 for buffer_id in buffer_ids {
18847 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18848 let buffer = buffer_entity.read(cx);
18849 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18850 {
18851 buffers.insert(buffer_entity);
18852 }
18853 }
18854 }
18855
18856 if let Some(project) = &self.project {
18857 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18858 } else {
18859 Task::ready(Ok(()))
18860 }
18861 }
18862
18863 fn do_stage_or_unstage_and_next(
18864 &mut self,
18865 stage: bool,
18866 window: &mut Window,
18867 cx: &mut Context<Self>,
18868 ) {
18869 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18870
18871 if ranges.iter().any(|range| range.start != range.end) {
18872 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18873 return;
18874 }
18875
18876 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18877 let snapshot = self.snapshot(window, cx);
18878 let position = self.selections.newest::<Point>(cx).head();
18879 let mut row = snapshot
18880 .buffer_snapshot()
18881 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
18882 .find(|hunk| hunk.row_range.start.0 > position.row)
18883 .map(|hunk| hunk.row_range.start);
18884
18885 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18886 // Outside of the project diff editor, wrap around to the beginning.
18887 if !all_diff_hunks_expanded {
18888 row = row.or_else(|| {
18889 snapshot
18890 .buffer_snapshot()
18891 .diff_hunks_in_range(Point::zero()..position)
18892 .find(|hunk| hunk.row_range.end.0 < position.row)
18893 .map(|hunk| hunk.row_range.start)
18894 });
18895 }
18896
18897 if let Some(row) = row {
18898 let destination = Point::new(row.0, 0);
18899 let autoscroll = Autoscroll::center();
18900
18901 self.unfold_ranges(&[destination..destination], false, false, cx);
18902 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18903 s.select_ranges([destination..destination]);
18904 });
18905 }
18906 }
18907
18908 fn do_stage_or_unstage(
18909 &self,
18910 stage: bool,
18911 buffer_id: BufferId,
18912 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18913 cx: &mut App,
18914 ) -> Option<()> {
18915 let project = self.project()?;
18916 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18917 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18918 let buffer_snapshot = buffer.read(cx).snapshot();
18919 let file_exists = buffer_snapshot
18920 .file()
18921 .is_some_and(|file| file.disk_state().exists());
18922 diff.update(cx, |diff, cx| {
18923 diff.stage_or_unstage_hunks(
18924 stage,
18925 &hunks
18926 .map(|hunk| buffer_diff::DiffHunk {
18927 buffer_range: hunk.buffer_range,
18928 diff_base_byte_range: hunk.diff_base_byte_range,
18929 secondary_status: hunk.secondary_status,
18930 range: Point::zero()..Point::zero(), // unused
18931 })
18932 .collect::<Vec<_>>(),
18933 &buffer_snapshot,
18934 file_exists,
18935 cx,
18936 )
18937 });
18938 None
18939 }
18940
18941 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18942 let ranges: Vec<_> = self
18943 .selections
18944 .disjoint_anchors()
18945 .iter()
18946 .map(|s| s.range())
18947 .collect();
18948 self.buffer
18949 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18950 }
18951
18952 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18953 self.buffer.update(cx, |buffer, cx| {
18954 let ranges = vec![Anchor::min()..Anchor::max()];
18955 if !buffer.all_diff_hunks_expanded()
18956 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18957 {
18958 buffer.collapse_diff_hunks(ranges, cx);
18959 true
18960 } else {
18961 false
18962 }
18963 })
18964 }
18965
18966 fn toggle_diff_hunks_in_ranges(
18967 &mut self,
18968 ranges: Vec<Range<Anchor>>,
18969 cx: &mut Context<Editor>,
18970 ) {
18971 self.buffer.update(cx, |buffer, cx| {
18972 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18973 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18974 })
18975 }
18976
18977 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18978 self.buffer.update(cx, |buffer, cx| {
18979 let snapshot = buffer.snapshot(cx);
18980 let excerpt_id = range.end.excerpt_id;
18981 let point_range = range.to_point(&snapshot);
18982 let expand = !buffer.single_hunk_is_expanded(range, cx);
18983 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18984 })
18985 }
18986
18987 pub(crate) fn apply_all_diff_hunks(
18988 &mut self,
18989 _: &ApplyAllDiffHunks,
18990 window: &mut Window,
18991 cx: &mut Context<Self>,
18992 ) {
18993 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18994
18995 let buffers = self.buffer.read(cx).all_buffers();
18996 for branch_buffer in buffers {
18997 branch_buffer.update(cx, |branch_buffer, cx| {
18998 branch_buffer.merge_into_base(Vec::new(), cx);
18999 });
19000 }
19001
19002 if let Some(project) = self.project.clone() {
19003 self.save(
19004 SaveOptions {
19005 format: true,
19006 autosave: false,
19007 },
19008 project,
19009 window,
19010 cx,
19011 )
19012 .detach_and_log_err(cx);
19013 }
19014 }
19015
19016 pub(crate) fn apply_selected_diff_hunks(
19017 &mut self,
19018 _: &ApplyDiffHunk,
19019 window: &mut Window,
19020 cx: &mut Context<Self>,
19021 ) {
19022 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19023 let snapshot = self.snapshot(window, cx);
19024 let hunks = snapshot.hunks_for_ranges(
19025 self.selections
19026 .all(cx)
19027 .into_iter()
19028 .map(|selection| selection.range()),
19029 );
19030 let mut ranges_by_buffer = HashMap::default();
19031 self.transact(window, cx, |editor, _window, cx| {
19032 for hunk in hunks {
19033 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19034 ranges_by_buffer
19035 .entry(buffer.clone())
19036 .or_insert_with(Vec::new)
19037 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19038 }
19039 }
19040
19041 for (buffer, ranges) in ranges_by_buffer {
19042 buffer.update(cx, |buffer, cx| {
19043 buffer.merge_into_base(ranges, cx);
19044 });
19045 }
19046 });
19047
19048 if let Some(project) = self.project.clone() {
19049 self.save(
19050 SaveOptions {
19051 format: true,
19052 autosave: false,
19053 },
19054 project,
19055 window,
19056 cx,
19057 )
19058 .detach_and_log_err(cx);
19059 }
19060 }
19061
19062 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19063 if hovered != self.gutter_hovered {
19064 self.gutter_hovered = hovered;
19065 cx.notify();
19066 }
19067 }
19068
19069 pub fn insert_blocks(
19070 &mut self,
19071 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19072 autoscroll: Option<Autoscroll>,
19073 cx: &mut Context<Self>,
19074 ) -> Vec<CustomBlockId> {
19075 let blocks = self
19076 .display_map
19077 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19078 if let Some(autoscroll) = autoscroll {
19079 self.request_autoscroll(autoscroll, cx);
19080 }
19081 cx.notify();
19082 blocks
19083 }
19084
19085 pub fn resize_blocks(
19086 &mut self,
19087 heights: HashMap<CustomBlockId, u32>,
19088 autoscroll: Option<Autoscroll>,
19089 cx: &mut Context<Self>,
19090 ) {
19091 self.display_map
19092 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19093 if let Some(autoscroll) = autoscroll {
19094 self.request_autoscroll(autoscroll, cx);
19095 }
19096 cx.notify();
19097 }
19098
19099 pub fn replace_blocks(
19100 &mut self,
19101 renderers: HashMap<CustomBlockId, RenderBlock>,
19102 autoscroll: Option<Autoscroll>,
19103 cx: &mut Context<Self>,
19104 ) {
19105 self.display_map
19106 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19107 if let Some(autoscroll) = autoscroll {
19108 self.request_autoscroll(autoscroll, cx);
19109 }
19110 cx.notify();
19111 }
19112
19113 pub fn remove_blocks(
19114 &mut self,
19115 block_ids: HashSet<CustomBlockId>,
19116 autoscroll: Option<Autoscroll>,
19117 cx: &mut Context<Self>,
19118 ) {
19119 self.display_map.update(cx, |display_map, cx| {
19120 display_map.remove_blocks(block_ids, cx)
19121 });
19122 if let Some(autoscroll) = autoscroll {
19123 self.request_autoscroll(autoscroll, cx);
19124 }
19125 cx.notify();
19126 }
19127
19128 pub fn row_for_block(
19129 &self,
19130 block_id: CustomBlockId,
19131 cx: &mut Context<Self>,
19132 ) -> Option<DisplayRow> {
19133 self.display_map
19134 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19135 }
19136
19137 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19138 self.focused_block = Some(focused_block);
19139 }
19140
19141 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19142 self.focused_block.take()
19143 }
19144
19145 pub fn insert_creases(
19146 &mut self,
19147 creases: impl IntoIterator<Item = Crease<Anchor>>,
19148 cx: &mut Context<Self>,
19149 ) -> Vec<CreaseId> {
19150 self.display_map
19151 .update(cx, |map, cx| map.insert_creases(creases, cx))
19152 }
19153
19154 pub fn remove_creases(
19155 &mut self,
19156 ids: impl IntoIterator<Item = CreaseId>,
19157 cx: &mut Context<Self>,
19158 ) -> Vec<(CreaseId, Range<Anchor>)> {
19159 self.display_map
19160 .update(cx, |map, cx| map.remove_creases(ids, cx))
19161 }
19162
19163 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19164 self.display_map
19165 .update(cx, |map, cx| map.snapshot(cx))
19166 .longest_row()
19167 }
19168
19169 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19170 self.display_map
19171 .update(cx, |map, cx| map.snapshot(cx))
19172 .max_point()
19173 }
19174
19175 pub fn text(&self, cx: &App) -> String {
19176 self.buffer.read(cx).read(cx).text()
19177 }
19178
19179 pub fn is_empty(&self, cx: &App) -> bool {
19180 self.buffer.read(cx).read(cx).is_empty()
19181 }
19182
19183 pub fn text_option(&self, cx: &App) -> Option<String> {
19184 let text = self.text(cx);
19185 let text = text.trim();
19186
19187 if text.is_empty() {
19188 return None;
19189 }
19190
19191 Some(text.to_string())
19192 }
19193
19194 pub fn set_text(
19195 &mut self,
19196 text: impl Into<Arc<str>>,
19197 window: &mut Window,
19198 cx: &mut Context<Self>,
19199 ) {
19200 self.transact(window, cx, |this, _, cx| {
19201 this.buffer
19202 .read(cx)
19203 .as_singleton()
19204 .expect("you can only call set_text on editors for singleton buffers")
19205 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19206 });
19207 }
19208
19209 pub fn display_text(&self, cx: &mut App) -> String {
19210 self.display_map
19211 .update(cx, |map, cx| map.snapshot(cx))
19212 .text()
19213 }
19214
19215 fn create_minimap(
19216 &self,
19217 minimap_settings: MinimapSettings,
19218 window: &mut Window,
19219 cx: &mut Context<Self>,
19220 ) -> Option<Entity<Self>> {
19221 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19222 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19223 }
19224
19225 fn initialize_new_minimap(
19226 &self,
19227 minimap_settings: MinimapSettings,
19228 window: &mut Window,
19229 cx: &mut Context<Self>,
19230 ) -> Entity<Self> {
19231 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19232
19233 let mut minimap = Editor::new_internal(
19234 EditorMode::Minimap {
19235 parent: cx.weak_entity(),
19236 },
19237 self.buffer.clone(),
19238 None,
19239 Some(self.display_map.clone()),
19240 window,
19241 cx,
19242 );
19243 minimap.scroll_manager.clone_state(&self.scroll_manager);
19244 minimap.set_text_style_refinement(TextStyleRefinement {
19245 font_size: Some(MINIMAP_FONT_SIZE),
19246 font_weight: Some(MINIMAP_FONT_WEIGHT),
19247 ..Default::default()
19248 });
19249 minimap.update_minimap_configuration(minimap_settings, cx);
19250 cx.new(|_| minimap)
19251 }
19252
19253 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19254 let current_line_highlight = minimap_settings
19255 .current_line_highlight
19256 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19257 self.set_current_line_highlight(Some(current_line_highlight));
19258 }
19259
19260 pub fn minimap(&self) -> Option<&Entity<Self>> {
19261 self.minimap
19262 .as_ref()
19263 .filter(|_| self.minimap_visibility.visible())
19264 }
19265
19266 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19267 let mut wrap_guides = smallvec![];
19268
19269 if self.show_wrap_guides == Some(false) {
19270 return wrap_guides;
19271 }
19272
19273 let settings = self.buffer.read(cx).language_settings(cx);
19274 if settings.show_wrap_guides {
19275 match self.soft_wrap_mode(cx) {
19276 SoftWrap::Column(soft_wrap) => {
19277 wrap_guides.push((soft_wrap as usize, true));
19278 }
19279 SoftWrap::Bounded(soft_wrap) => {
19280 wrap_guides.push((soft_wrap as usize, true));
19281 }
19282 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19283 }
19284 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19285 }
19286
19287 wrap_guides
19288 }
19289
19290 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19291 let settings = self.buffer.read(cx).language_settings(cx);
19292 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19293 match mode {
19294 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19295 SoftWrap::None
19296 }
19297 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19298 language_settings::SoftWrap::PreferredLineLength => {
19299 SoftWrap::Column(settings.preferred_line_length)
19300 }
19301 language_settings::SoftWrap::Bounded => {
19302 SoftWrap::Bounded(settings.preferred_line_length)
19303 }
19304 }
19305 }
19306
19307 pub fn set_soft_wrap_mode(
19308 &mut self,
19309 mode: language_settings::SoftWrap,
19310
19311 cx: &mut Context<Self>,
19312 ) {
19313 self.soft_wrap_mode_override = Some(mode);
19314 cx.notify();
19315 }
19316
19317 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19318 self.hard_wrap = hard_wrap;
19319 cx.notify();
19320 }
19321
19322 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19323 self.text_style_refinement = Some(style);
19324 }
19325
19326 /// called by the Element so we know what style we were most recently rendered with.
19327 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19328 // We intentionally do not inform the display map about the minimap style
19329 // so that wrapping is not recalculated and stays consistent for the editor
19330 // and its linked minimap.
19331 if !self.mode.is_minimap() {
19332 let font = style.text.font();
19333 let font_size = style.text.font_size.to_pixels(window.rem_size());
19334 let display_map = self
19335 .placeholder_display_map
19336 .as_ref()
19337 .filter(|_| self.is_empty(cx))
19338 .unwrap_or(&self.display_map);
19339
19340 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19341 }
19342 self.style = Some(style);
19343 }
19344
19345 pub fn style(&self) -> Option<&EditorStyle> {
19346 self.style.as_ref()
19347 }
19348
19349 // Called by the element. This method is not designed to be called outside of the editor
19350 // element's layout code because it does not notify when rewrapping is computed synchronously.
19351 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19352 if self.is_empty(cx) {
19353 self.placeholder_display_map
19354 .as_ref()
19355 .map_or(false, |display_map| {
19356 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19357 })
19358 } else {
19359 self.display_map
19360 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19361 }
19362 }
19363
19364 pub fn set_soft_wrap(&mut self) {
19365 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19366 }
19367
19368 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19369 if self.soft_wrap_mode_override.is_some() {
19370 self.soft_wrap_mode_override.take();
19371 } else {
19372 let soft_wrap = match self.soft_wrap_mode(cx) {
19373 SoftWrap::GitDiff => return,
19374 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19375 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19376 language_settings::SoftWrap::None
19377 }
19378 };
19379 self.soft_wrap_mode_override = Some(soft_wrap);
19380 }
19381 cx.notify();
19382 }
19383
19384 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19385 let Some(workspace) = self.workspace() else {
19386 return;
19387 };
19388 let fs = workspace.read(cx).app_state().fs.clone();
19389 let current_show = TabBarSettings::get_global(cx).show;
19390 update_settings_file(fs, cx, move |setting, _| {
19391 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19392 });
19393 }
19394
19395 pub fn toggle_indent_guides(
19396 &mut self,
19397 _: &ToggleIndentGuides,
19398 _: &mut Window,
19399 cx: &mut Context<Self>,
19400 ) {
19401 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19402 self.buffer
19403 .read(cx)
19404 .language_settings(cx)
19405 .indent_guides
19406 .enabled
19407 });
19408 self.show_indent_guides = Some(!currently_enabled);
19409 cx.notify();
19410 }
19411
19412 fn should_show_indent_guides(&self) -> Option<bool> {
19413 self.show_indent_guides
19414 }
19415
19416 pub fn toggle_line_numbers(
19417 &mut self,
19418 _: &ToggleLineNumbers,
19419 _: &mut Window,
19420 cx: &mut Context<Self>,
19421 ) {
19422 let mut editor_settings = EditorSettings::get_global(cx).clone();
19423 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19424 EditorSettings::override_global(editor_settings, cx);
19425 }
19426
19427 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19428 if let Some(show_line_numbers) = self.show_line_numbers {
19429 return show_line_numbers;
19430 }
19431 EditorSettings::get_global(cx).gutter.line_numbers
19432 }
19433
19434 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19435 self.use_relative_line_numbers
19436 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19437 }
19438
19439 pub fn toggle_relative_line_numbers(
19440 &mut self,
19441 _: &ToggleRelativeLineNumbers,
19442 _: &mut Window,
19443 cx: &mut Context<Self>,
19444 ) {
19445 let is_relative = self.should_use_relative_line_numbers(cx);
19446 self.set_relative_line_number(Some(!is_relative), cx)
19447 }
19448
19449 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19450 self.use_relative_line_numbers = is_relative;
19451 cx.notify();
19452 }
19453
19454 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19455 self.show_gutter = show_gutter;
19456 cx.notify();
19457 }
19458
19459 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19460 self.show_scrollbars = ScrollbarAxes {
19461 horizontal: show,
19462 vertical: show,
19463 };
19464 cx.notify();
19465 }
19466
19467 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19468 self.show_scrollbars.vertical = show;
19469 cx.notify();
19470 }
19471
19472 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19473 self.show_scrollbars.horizontal = show;
19474 cx.notify();
19475 }
19476
19477 pub fn set_minimap_visibility(
19478 &mut self,
19479 minimap_visibility: MinimapVisibility,
19480 window: &mut Window,
19481 cx: &mut Context<Self>,
19482 ) {
19483 if self.minimap_visibility != minimap_visibility {
19484 if minimap_visibility.visible() && self.minimap.is_none() {
19485 let minimap_settings = EditorSettings::get_global(cx).minimap;
19486 self.minimap =
19487 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19488 }
19489 self.minimap_visibility = minimap_visibility;
19490 cx.notify();
19491 }
19492 }
19493
19494 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19495 self.set_show_scrollbars(false, cx);
19496 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19497 }
19498
19499 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19500 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19501 }
19502
19503 /// Normally the text in full mode and auto height editors is padded on the
19504 /// left side by roughly half a character width for improved hit testing.
19505 ///
19506 /// Use this method to disable this for cases where this is not wanted (e.g.
19507 /// if you want to align the editor text with some other text above or below)
19508 /// or if you want to add this padding to single-line editors.
19509 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19510 self.offset_content = offset_content;
19511 cx.notify();
19512 }
19513
19514 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19515 self.show_line_numbers = Some(show_line_numbers);
19516 cx.notify();
19517 }
19518
19519 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19520 self.disable_expand_excerpt_buttons = true;
19521 cx.notify();
19522 }
19523
19524 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19525 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19526 cx.notify();
19527 }
19528
19529 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19530 self.show_code_actions = Some(show_code_actions);
19531 cx.notify();
19532 }
19533
19534 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19535 self.show_runnables = Some(show_runnables);
19536 cx.notify();
19537 }
19538
19539 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19540 self.show_breakpoints = Some(show_breakpoints);
19541 cx.notify();
19542 }
19543
19544 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19545 if self.display_map.read(cx).masked != masked {
19546 self.display_map.update(cx, |map, _| map.masked = masked);
19547 }
19548 cx.notify()
19549 }
19550
19551 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19552 self.show_wrap_guides = Some(show_wrap_guides);
19553 cx.notify();
19554 }
19555
19556 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19557 self.show_indent_guides = Some(show_indent_guides);
19558 cx.notify();
19559 }
19560
19561 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19562 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19563 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19564 && let Some(dir) = file.abs_path(cx).parent()
19565 {
19566 return Some(dir.to_owned());
19567 }
19568 }
19569
19570 None
19571 }
19572
19573 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19574 self.active_excerpt(cx)?
19575 .1
19576 .read(cx)
19577 .file()
19578 .and_then(|f| f.as_local())
19579 }
19580
19581 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19582 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19583 let buffer = buffer.read(cx);
19584 if let Some(project_path) = buffer.project_path(cx) {
19585 let project = self.project()?.read(cx);
19586 project.absolute_path(&project_path, cx)
19587 } else {
19588 buffer
19589 .file()
19590 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19591 }
19592 })
19593 }
19594
19595 pub fn reveal_in_finder(
19596 &mut self,
19597 _: &RevealInFileManager,
19598 _window: &mut Window,
19599 cx: &mut Context<Self>,
19600 ) {
19601 if let Some(target) = self.target_file(cx) {
19602 cx.reveal_path(&target.abs_path(cx));
19603 }
19604 }
19605
19606 pub fn copy_path(
19607 &mut self,
19608 _: &zed_actions::workspace::CopyPath,
19609 _window: &mut Window,
19610 cx: &mut Context<Self>,
19611 ) {
19612 if let Some(path) = self.target_file_abs_path(cx)
19613 && let Some(path) = path.to_str()
19614 {
19615 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19616 } else {
19617 cx.propagate();
19618 }
19619 }
19620
19621 pub fn copy_relative_path(
19622 &mut self,
19623 _: &zed_actions::workspace::CopyRelativePath,
19624 _window: &mut Window,
19625 cx: &mut Context<Self>,
19626 ) {
19627 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19628 let project = self.project()?.read(cx);
19629 let path = buffer.read(cx).file()?.path();
19630 let path = path.display(project.path_style(cx));
19631 Some(path)
19632 }) {
19633 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19634 } else {
19635 cx.propagate();
19636 }
19637 }
19638
19639 /// Returns the project path for the editor's buffer, if any buffer is
19640 /// opened in the editor.
19641 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19642 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19643 buffer.read(cx).project_path(cx)
19644 } else {
19645 None
19646 }
19647 }
19648
19649 // Returns true if the editor handled a go-to-line request
19650 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19651 maybe!({
19652 let breakpoint_store = self.breakpoint_store.as_ref()?;
19653
19654 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19655 else {
19656 self.clear_row_highlights::<ActiveDebugLine>();
19657 return None;
19658 };
19659
19660 let position = active_stack_frame.position;
19661 let buffer_id = position.buffer_id?;
19662 let snapshot = self
19663 .project
19664 .as_ref()?
19665 .read(cx)
19666 .buffer_for_id(buffer_id, cx)?
19667 .read(cx)
19668 .snapshot();
19669
19670 let mut handled = false;
19671 for (id, ExcerptRange { context, .. }) in
19672 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19673 {
19674 if context.start.cmp(&position, &snapshot).is_ge()
19675 || context.end.cmp(&position, &snapshot).is_lt()
19676 {
19677 continue;
19678 }
19679 let snapshot = self.buffer.read(cx).snapshot(cx);
19680 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19681
19682 handled = true;
19683 self.clear_row_highlights::<ActiveDebugLine>();
19684
19685 self.go_to_line::<ActiveDebugLine>(
19686 multibuffer_anchor,
19687 Some(cx.theme().colors().editor_debugger_active_line_background),
19688 window,
19689 cx,
19690 );
19691
19692 cx.notify();
19693 }
19694
19695 handled.then_some(())
19696 })
19697 .is_some()
19698 }
19699
19700 pub fn copy_file_name_without_extension(
19701 &mut self,
19702 _: &CopyFileNameWithoutExtension,
19703 _: &mut Window,
19704 cx: &mut Context<Self>,
19705 ) {
19706 if let Some(file) = self.target_file(cx)
19707 && let Some(file_stem) = file.path().file_stem()
19708 {
19709 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19710 }
19711 }
19712
19713 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19714 if let Some(file) = self.target_file(cx)
19715 && let Some(name) = file.path().file_name()
19716 {
19717 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19718 }
19719 }
19720
19721 pub fn toggle_git_blame(
19722 &mut self,
19723 _: &::git::Blame,
19724 window: &mut Window,
19725 cx: &mut Context<Self>,
19726 ) {
19727 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19728
19729 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19730 self.start_git_blame(true, window, cx);
19731 }
19732
19733 cx.notify();
19734 }
19735
19736 pub fn toggle_git_blame_inline(
19737 &mut self,
19738 _: &ToggleGitBlameInline,
19739 window: &mut Window,
19740 cx: &mut Context<Self>,
19741 ) {
19742 self.toggle_git_blame_inline_internal(true, window, cx);
19743 cx.notify();
19744 }
19745
19746 pub fn open_git_blame_commit(
19747 &mut self,
19748 _: &OpenGitBlameCommit,
19749 window: &mut Window,
19750 cx: &mut Context<Self>,
19751 ) {
19752 self.open_git_blame_commit_internal(window, cx);
19753 }
19754
19755 fn open_git_blame_commit_internal(
19756 &mut self,
19757 window: &mut Window,
19758 cx: &mut Context<Self>,
19759 ) -> Option<()> {
19760 let blame = self.blame.as_ref()?;
19761 let snapshot = self.snapshot(window, cx);
19762 let cursor = self.selections.newest::<Point>(cx).head();
19763 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
19764 let (_, blame_entry) = blame
19765 .update(cx, |blame, cx| {
19766 blame
19767 .blame_for_rows(
19768 &[RowInfo {
19769 buffer_id: Some(buffer.remote_id()),
19770 buffer_row: Some(point.row),
19771 ..Default::default()
19772 }],
19773 cx,
19774 )
19775 .next()
19776 })
19777 .flatten()?;
19778 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19779 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19780 let workspace = self.workspace()?.downgrade();
19781 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19782 None
19783 }
19784
19785 pub fn git_blame_inline_enabled(&self) -> bool {
19786 self.git_blame_inline_enabled
19787 }
19788
19789 pub fn toggle_selection_menu(
19790 &mut self,
19791 _: &ToggleSelectionMenu,
19792 _: &mut Window,
19793 cx: &mut Context<Self>,
19794 ) {
19795 self.show_selection_menu = self
19796 .show_selection_menu
19797 .map(|show_selections_menu| !show_selections_menu)
19798 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19799
19800 cx.notify();
19801 }
19802
19803 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19804 self.show_selection_menu
19805 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19806 }
19807
19808 fn start_git_blame(
19809 &mut self,
19810 user_triggered: bool,
19811 window: &mut Window,
19812 cx: &mut Context<Self>,
19813 ) {
19814 if let Some(project) = self.project() {
19815 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19816 && buffer.read(cx).file().is_none()
19817 {
19818 return;
19819 }
19820
19821 let focused = self.focus_handle(cx).contains_focused(window, cx);
19822
19823 let project = project.clone();
19824 let blame = cx
19825 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19826 self.blame_subscription =
19827 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19828 self.blame = Some(blame);
19829 }
19830 }
19831
19832 fn toggle_git_blame_inline_internal(
19833 &mut self,
19834 user_triggered: bool,
19835 window: &mut Window,
19836 cx: &mut Context<Self>,
19837 ) {
19838 if self.git_blame_inline_enabled {
19839 self.git_blame_inline_enabled = false;
19840 self.show_git_blame_inline = false;
19841 self.show_git_blame_inline_delay_task.take();
19842 } else {
19843 self.git_blame_inline_enabled = true;
19844 self.start_git_blame_inline(user_triggered, window, cx);
19845 }
19846
19847 cx.notify();
19848 }
19849
19850 fn start_git_blame_inline(
19851 &mut self,
19852 user_triggered: bool,
19853 window: &mut Window,
19854 cx: &mut Context<Self>,
19855 ) {
19856 self.start_git_blame(user_triggered, window, cx);
19857
19858 if ProjectSettings::get_global(cx)
19859 .git
19860 .inline_blame_delay()
19861 .is_some()
19862 {
19863 self.start_inline_blame_timer(window, cx);
19864 } else {
19865 self.show_git_blame_inline = true
19866 }
19867 }
19868
19869 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19870 self.blame.as_ref()
19871 }
19872
19873 pub fn show_git_blame_gutter(&self) -> bool {
19874 self.show_git_blame_gutter
19875 }
19876
19877 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19878 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19879 }
19880
19881 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19882 self.show_git_blame_inline
19883 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19884 && !self.newest_selection_head_on_empty_line(cx)
19885 && self.has_blame_entries(cx)
19886 }
19887
19888 fn has_blame_entries(&self, cx: &App) -> bool {
19889 self.blame()
19890 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19891 }
19892
19893 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19894 let cursor_anchor = self.selections.newest_anchor().head();
19895
19896 let snapshot = self.buffer.read(cx).snapshot(cx);
19897 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19898
19899 snapshot.line_len(buffer_row) == 0
19900 }
19901
19902 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19903 let buffer_and_selection = maybe!({
19904 let selection = self.selections.newest::<Point>(cx);
19905 let selection_range = selection.range();
19906
19907 let multi_buffer = self.buffer().read(cx);
19908 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19909 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19910
19911 let (buffer, range, _) = if selection.reversed {
19912 buffer_ranges.first()
19913 } else {
19914 buffer_ranges.last()
19915 }?;
19916
19917 let selection = text::ToPoint::to_point(&range.start, buffer).row
19918 ..text::ToPoint::to_point(&range.end, buffer).row;
19919 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19920 });
19921
19922 let Some((buffer, selection)) = buffer_and_selection else {
19923 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19924 };
19925
19926 let Some(project) = self.project() else {
19927 return Task::ready(Err(anyhow!("editor does not have project")));
19928 };
19929
19930 project.update(cx, |project, cx| {
19931 project.get_permalink_to_line(&buffer, selection, cx)
19932 })
19933 }
19934
19935 pub fn copy_permalink_to_line(
19936 &mut self,
19937 _: &CopyPermalinkToLine,
19938 window: &mut Window,
19939 cx: &mut Context<Self>,
19940 ) {
19941 let permalink_task = self.get_permalink_to_line(cx);
19942 let workspace = self.workspace();
19943
19944 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19945 Ok(permalink) => {
19946 cx.update(|_, cx| {
19947 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19948 })
19949 .ok();
19950 }
19951 Err(err) => {
19952 let message = format!("Failed to copy permalink: {err}");
19953
19954 anyhow::Result::<()>::Err(err).log_err();
19955
19956 if let Some(workspace) = workspace {
19957 workspace
19958 .update_in(cx, |workspace, _, cx| {
19959 struct CopyPermalinkToLine;
19960
19961 workspace.show_toast(
19962 Toast::new(
19963 NotificationId::unique::<CopyPermalinkToLine>(),
19964 message,
19965 ),
19966 cx,
19967 )
19968 })
19969 .ok();
19970 }
19971 }
19972 })
19973 .detach();
19974 }
19975
19976 pub fn copy_file_location(
19977 &mut self,
19978 _: &CopyFileLocation,
19979 _: &mut Window,
19980 cx: &mut Context<Self>,
19981 ) {
19982 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19983 if let Some(file) = self.target_file(cx) {
19984 let path = file.path().display(file.path_style(cx));
19985 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19986 }
19987 }
19988
19989 pub fn open_permalink_to_line(
19990 &mut self,
19991 _: &OpenPermalinkToLine,
19992 window: &mut Window,
19993 cx: &mut Context<Self>,
19994 ) {
19995 let permalink_task = self.get_permalink_to_line(cx);
19996 let workspace = self.workspace();
19997
19998 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19999 Ok(permalink) => {
20000 cx.update(|_, cx| {
20001 cx.open_url(permalink.as_ref());
20002 })
20003 .ok();
20004 }
20005 Err(err) => {
20006 let message = format!("Failed to open permalink: {err}");
20007
20008 anyhow::Result::<()>::Err(err).log_err();
20009
20010 if let Some(workspace) = workspace {
20011 workspace
20012 .update(cx, |workspace, cx| {
20013 struct OpenPermalinkToLine;
20014
20015 workspace.show_toast(
20016 Toast::new(
20017 NotificationId::unique::<OpenPermalinkToLine>(),
20018 message,
20019 ),
20020 cx,
20021 )
20022 })
20023 .ok();
20024 }
20025 }
20026 })
20027 .detach();
20028 }
20029
20030 pub fn insert_uuid_v4(
20031 &mut self,
20032 _: &InsertUuidV4,
20033 window: &mut Window,
20034 cx: &mut Context<Self>,
20035 ) {
20036 self.insert_uuid(UuidVersion::V4, window, cx);
20037 }
20038
20039 pub fn insert_uuid_v7(
20040 &mut self,
20041 _: &InsertUuidV7,
20042 window: &mut Window,
20043 cx: &mut Context<Self>,
20044 ) {
20045 self.insert_uuid(UuidVersion::V7, window, cx);
20046 }
20047
20048 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20049 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20050 self.transact(window, cx, |this, window, cx| {
20051 let edits = this
20052 .selections
20053 .all::<Point>(cx)
20054 .into_iter()
20055 .map(|selection| {
20056 let uuid = match version {
20057 UuidVersion::V4 => uuid::Uuid::new_v4(),
20058 UuidVersion::V7 => uuid::Uuid::now_v7(),
20059 };
20060
20061 (selection.range(), uuid.to_string())
20062 });
20063 this.edit(edits, cx);
20064 this.refresh_edit_prediction(true, false, window, cx);
20065 });
20066 }
20067
20068 pub fn open_selections_in_multibuffer(
20069 &mut self,
20070 _: &OpenSelectionsInMultibuffer,
20071 window: &mut Window,
20072 cx: &mut Context<Self>,
20073 ) {
20074 let multibuffer = self.buffer.read(cx);
20075
20076 let Some(buffer) = multibuffer.as_singleton() else {
20077 return;
20078 };
20079
20080 let Some(workspace) = self.workspace() else {
20081 return;
20082 };
20083
20084 let title = multibuffer.title(cx).to_string();
20085
20086 let locations = self
20087 .selections
20088 .all_anchors(cx)
20089 .iter()
20090 .map(|selection| {
20091 (
20092 buffer.clone(),
20093 (selection.start.text_anchor..selection.end.text_anchor)
20094 .to_point(buffer.read(cx)),
20095 )
20096 })
20097 .into_group_map();
20098
20099 cx.spawn_in(window, async move |_, cx| {
20100 workspace.update_in(cx, |workspace, window, cx| {
20101 Self::open_locations_in_multibuffer(
20102 workspace,
20103 locations,
20104 format!("Selections for '{title}'"),
20105 false,
20106 MultibufferSelectionMode::All,
20107 window,
20108 cx,
20109 );
20110 })
20111 })
20112 .detach();
20113 }
20114
20115 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20116 /// last highlight added will be used.
20117 ///
20118 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20119 pub fn highlight_rows<T: 'static>(
20120 &mut self,
20121 range: Range<Anchor>,
20122 color: Hsla,
20123 options: RowHighlightOptions,
20124 cx: &mut Context<Self>,
20125 ) {
20126 let snapshot = self.buffer().read(cx).snapshot(cx);
20127 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20128 let ix = row_highlights.binary_search_by(|highlight| {
20129 Ordering::Equal
20130 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20131 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20132 });
20133
20134 if let Err(mut ix) = ix {
20135 let index = post_inc(&mut self.highlight_order);
20136
20137 // If this range intersects with the preceding highlight, then merge it with
20138 // the preceding highlight. Otherwise insert a new highlight.
20139 let mut merged = false;
20140 if ix > 0 {
20141 let prev_highlight = &mut row_highlights[ix - 1];
20142 if prev_highlight
20143 .range
20144 .end
20145 .cmp(&range.start, &snapshot)
20146 .is_ge()
20147 {
20148 ix -= 1;
20149 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20150 prev_highlight.range.end = range.end;
20151 }
20152 merged = true;
20153 prev_highlight.index = index;
20154 prev_highlight.color = color;
20155 prev_highlight.options = options;
20156 }
20157 }
20158
20159 if !merged {
20160 row_highlights.insert(
20161 ix,
20162 RowHighlight {
20163 range,
20164 index,
20165 color,
20166 options,
20167 type_id: TypeId::of::<T>(),
20168 },
20169 );
20170 }
20171
20172 // If any of the following highlights intersect with this one, merge them.
20173 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20174 let highlight = &row_highlights[ix];
20175 if next_highlight
20176 .range
20177 .start
20178 .cmp(&highlight.range.end, &snapshot)
20179 .is_le()
20180 {
20181 if next_highlight
20182 .range
20183 .end
20184 .cmp(&highlight.range.end, &snapshot)
20185 .is_gt()
20186 {
20187 row_highlights[ix].range.end = next_highlight.range.end;
20188 }
20189 row_highlights.remove(ix + 1);
20190 } else {
20191 break;
20192 }
20193 }
20194 }
20195 }
20196
20197 /// Remove any highlighted row ranges of the given type that intersect the
20198 /// given ranges.
20199 pub fn remove_highlighted_rows<T: 'static>(
20200 &mut self,
20201 ranges_to_remove: Vec<Range<Anchor>>,
20202 cx: &mut Context<Self>,
20203 ) {
20204 let snapshot = self.buffer().read(cx).snapshot(cx);
20205 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20206 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20207 row_highlights.retain(|highlight| {
20208 while let Some(range_to_remove) = ranges_to_remove.peek() {
20209 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20210 Ordering::Less | Ordering::Equal => {
20211 ranges_to_remove.next();
20212 }
20213 Ordering::Greater => {
20214 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20215 Ordering::Less | Ordering::Equal => {
20216 return false;
20217 }
20218 Ordering::Greater => break,
20219 }
20220 }
20221 }
20222 }
20223
20224 true
20225 })
20226 }
20227
20228 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20229 pub fn clear_row_highlights<T: 'static>(&mut self) {
20230 self.highlighted_rows.remove(&TypeId::of::<T>());
20231 }
20232
20233 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20234 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20235 self.highlighted_rows
20236 .get(&TypeId::of::<T>())
20237 .map_or(&[] as &[_], |vec| vec.as_slice())
20238 .iter()
20239 .map(|highlight| (highlight.range.clone(), highlight.color))
20240 }
20241
20242 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20243 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20244 /// Allows to ignore certain kinds of highlights.
20245 pub fn highlighted_display_rows(
20246 &self,
20247 window: &mut Window,
20248 cx: &mut App,
20249 ) -> BTreeMap<DisplayRow, LineHighlight> {
20250 let snapshot = self.snapshot(window, cx);
20251 let mut used_highlight_orders = HashMap::default();
20252 self.highlighted_rows
20253 .iter()
20254 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20255 .fold(
20256 BTreeMap::<DisplayRow, LineHighlight>::new(),
20257 |mut unique_rows, highlight| {
20258 let start = highlight.range.start.to_display_point(&snapshot);
20259 let end = highlight.range.end.to_display_point(&snapshot);
20260 let start_row = start.row().0;
20261 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20262 && end.column() == 0
20263 {
20264 end.row().0.saturating_sub(1)
20265 } else {
20266 end.row().0
20267 };
20268 for row in start_row..=end_row {
20269 let used_index =
20270 used_highlight_orders.entry(row).or_insert(highlight.index);
20271 if highlight.index >= *used_index {
20272 *used_index = highlight.index;
20273 unique_rows.insert(
20274 DisplayRow(row),
20275 LineHighlight {
20276 include_gutter: highlight.options.include_gutter,
20277 border: None,
20278 background: highlight.color.into(),
20279 type_id: Some(highlight.type_id),
20280 },
20281 );
20282 }
20283 }
20284 unique_rows
20285 },
20286 )
20287 }
20288
20289 pub fn highlighted_display_row_for_autoscroll(
20290 &self,
20291 snapshot: &DisplaySnapshot,
20292 ) -> Option<DisplayRow> {
20293 self.highlighted_rows
20294 .values()
20295 .flat_map(|highlighted_rows| highlighted_rows.iter())
20296 .filter_map(|highlight| {
20297 if highlight.options.autoscroll {
20298 Some(highlight.range.start.to_display_point(snapshot).row())
20299 } else {
20300 None
20301 }
20302 })
20303 .min()
20304 }
20305
20306 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20307 self.highlight_background::<SearchWithinRange>(
20308 ranges,
20309 |colors| colors.colors().editor_document_highlight_read_background,
20310 cx,
20311 )
20312 }
20313
20314 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20315 self.breadcrumb_header = Some(new_header);
20316 }
20317
20318 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20319 self.clear_background_highlights::<SearchWithinRange>(cx);
20320 }
20321
20322 pub fn highlight_background<T: 'static>(
20323 &mut self,
20324 ranges: &[Range<Anchor>],
20325 color_fetcher: fn(&Theme) -> Hsla,
20326 cx: &mut Context<Self>,
20327 ) {
20328 self.background_highlights.insert(
20329 HighlightKey::Type(TypeId::of::<T>()),
20330 (color_fetcher, Arc::from(ranges)),
20331 );
20332 self.scrollbar_marker_state.dirty = true;
20333 cx.notify();
20334 }
20335
20336 pub fn highlight_background_key<T: 'static>(
20337 &mut self,
20338 key: usize,
20339 ranges: &[Range<Anchor>],
20340 color_fetcher: fn(&Theme) -> Hsla,
20341 cx: &mut Context<Self>,
20342 ) {
20343 self.background_highlights.insert(
20344 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20345 (color_fetcher, Arc::from(ranges)),
20346 );
20347 self.scrollbar_marker_state.dirty = true;
20348 cx.notify();
20349 }
20350
20351 pub fn clear_background_highlights<T: 'static>(
20352 &mut self,
20353 cx: &mut Context<Self>,
20354 ) -> Option<BackgroundHighlight> {
20355 let text_highlights = self
20356 .background_highlights
20357 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20358 if !text_highlights.1.is_empty() {
20359 self.scrollbar_marker_state.dirty = true;
20360 cx.notify();
20361 }
20362 Some(text_highlights)
20363 }
20364
20365 pub fn highlight_gutter<T: 'static>(
20366 &mut self,
20367 ranges: impl Into<Vec<Range<Anchor>>>,
20368 color_fetcher: fn(&App) -> Hsla,
20369 cx: &mut Context<Self>,
20370 ) {
20371 self.gutter_highlights
20372 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20373 cx.notify();
20374 }
20375
20376 pub fn clear_gutter_highlights<T: 'static>(
20377 &mut self,
20378 cx: &mut Context<Self>,
20379 ) -> Option<GutterHighlight> {
20380 cx.notify();
20381 self.gutter_highlights.remove(&TypeId::of::<T>())
20382 }
20383
20384 pub fn insert_gutter_highlight<T: 'static>(
20385 &mut self,
20386 range: Range<Anchor>,
20387 color_fetcher: fn(&App) -> Hsla,
20388 cx: &mut Context<Self>,
20389 ) {
20390 let snapshot = self.buffer().read(cx).snapshot(cx);
20391 let mut highlights = self
20392 .gutter_highlights
20393 .remove(&TypeId::of::<T>())
20394 .map(|(_, highlights)| highlights)
20395 .unwrap_or_default();
20396 let ix = highlights.binary_search_by(|highlight| {
20397 Ordering::Equal
20398 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20399 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20400 });
20401 if let Err(ix) = ix {
20402 highlights.insert(ix, range);
20403 }
20404 self.gutter_highlights
20405 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20406 }
20407
20408 pub fn remove_gutter_highlights<T: 'static>(
20409 &mut self,
20410 ranges_to_remove: Vec<Range<Anchor>>,
20411 cx: &mut Context<Self>,
20412 ) {
20413 let snapshot = self.buffer().read(cx).snapshot(cx);
20414 let Some((color_fetcher, mut gutter_highlights)) =
20415 self.gutter_highlights.remove(&TypeId::of::<T>())
20416 else {
20417 return;
20418 };
20419 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20420 gutter_highlights.retain(|highlight| {
20421 while let Some(range_to_remove) = ranges_to_remove.peek() {
20422 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20423 Ordering::Less | Ordering::Equal => {
20424 ranges_to_remove.next();
20425 }
20426 Ordering::Greater => {
20427 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20428 Ordering::Less | Ordering::Equal => {
20429 return false;
20430 }
20431 Ordering::Greater => break,
20432 }
20433 }
20434 }
20435 }
20436
20437 true
20438 });
20439 self.gutter_highlights
20440 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20441 }
20442
20443 #[cfg(feature = "test-support")]
20444 pub fn all_text_highlights(
20445 &self,
20446 window: &mut Window,
20447 cx: &mut Context<Self>,
20448 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20449 let snapshot = self.snapshot(window, cx);
20450 self.display_map.update(cx, |display_map, _| {
20451 display_map
20452 .all_text_highlights()
20453 .map(|highlight| {
20454 let (style, ranges) = highlight.as_ref();
20455 (
20456 *style,
20457 ranges
20458 .iter()
20459 .map(|range| range.clone().to_display_points(&snapshot))
20460 .collect(),
20461 )
20462 })
20463 .collect()
20464 })
20465 }
20466
20467 #[cfg(feature = "test-support")]
20468 pub fn all_text_background_highlights(
20469 &self,
20470 window: &mut Window,
20471 cx: &mut Context<Self>,
20472 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20473 let snapshot = self.snapshot(window, cx);
20474 let buffer = &snapshot.buffer_snapshot();
20475 let start = buffer.anchor_before(0);
20476 let end = buffer.anchor_after(buffer.len());
20477 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20478 }
20479
20480 #[cfg(any(test, feature = "test-support"))]
20481 pub fn sorted_background_highlights_in_range(
20482 &self,
20483 search_range: Range<Anchor>,
20484 display_snapshot: &DisplaySnapshot,
20485 theme: &Theme,
20486 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20487 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20488 res.sort_by(|a, b| {
20489 a.0.start
20490 .cmp(&b.0.start)
20491 .then_with(|| a.0.end.cmp(&b.0.end))
20492 .then_with(|| a.1.cmp(&b.1))
20493 });
20494 res
20495 }
20496
20497 #[cfg(feature = "test-support")]
20498 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20499 let snapshot = self.buffer().read(cx).snapshot(cx);
20500
20501 let highlights = self
20502 .background_highlights
20503 .get(&HighlightKey::Type(TypeId::of::<
20504 items::BufferSearchHighlights,
20505 >()));
20506
20507 if let Some((_color, ranges)) = highlights {
20508 ranges
20509 .iter()
20510 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20511 .collect_vec()
20512 } else {
20513 vec![]
20514 }
20515 }
20516
20517 fn document_highlights_for_position<'a>(
20518 &'a self,
20519 position: Anchor,
20520 buffer: &'a MultiBufferSnapshot,
20521 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20522 let read_highlights = self
20523 .background_highlights
20524 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20525 .map(|h| &h.1);
20526 let write_highlights = self
20527 .background_highlights
20528 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20529 .map(|h| &h.1);
20530 let left_position = position.bias_left(buffer);
20531 let right_position = position.bias_right(buffer);
20532 read_highlights
20533 .into_iter()
20534 .chain(write_highlights)
20535 .flat_map(move |ranges| {
20536 let start_ix = match ranges.binary_search_by(|probe| {
20537 let cmp = probe.end.cmp(&left_position, buffer);
20538 if cmp.is_ge() {
20539 Ordering::Greater
20540 } else {
20541 Ordering::Less
20542 }
20543 }) {
20544 Ok(i) | Err(i) => i,
20545 };
20546
20547 ranges[start_ix..]
20548 .iter()
20549 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20550 })
20551 }
20552
20553 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20554 self.background_highlights
20555 .get(&HighlightKey::Type(TypeId::of::<T>()))
20556 .is_some_and(|(_, highlights)| !highlights.is_empty())
20557 }
20558
20559 /// Returns all background highlights for a given range.
20560 ///
20561 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20562 pub fn background_highlights_in_range(
20563 &self,
20564 search_range: Range<Anchor>,
20565 display_snapshot: &DisplaySnapshot,
20566 theme: &Theme,
20567 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20568 let mut results = Vec::new();
20569 for (color_fetcher, ranges) in self.background_highlights.values() {
20570 let color = color_fetcher(theme);
20571 let start_ix = match ranges.binary_search_by(|probe| {
20572 let cmp = probe
20573 .end
20574 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20575 if cmp.is_gt() {
20576 Ordering::Greater
20577 } else {
20578 Ordering::Less
20579 }
20580 }) {
20581 Ok(i) | Err(i) => i,
20582 };
20583 for range in &ranges[start_ix..] {
20584 if range
20585 .start
20586 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20587 .is_ge()
20588 {
20589 break;
20590 }
20591
20592 let start = range.start.to_display_point(display_snapshot);
20593 let end = range.end.to_display_point(display_snapshot);
20594 results.push((start..end, color))
20595 }
20596 }
20597 results
20598 }
20599
20600 pub fn gutter_highlights_in_range(
20601 &self,
20602 search_range: Range<Anchor>,
20603 display_snapshot: &DisplaySnapshot,
20604 cx: &App,
20605 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20606 let mut results = Vec::new();
20607 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20608 let color = color_fetcher(cx);
20609 let start_ix = match ranges.binary_search_by(|probe| {
20610 let cmp = probe
20611 .end
20612 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20613 if cmp.is_gt() {
20614 Ordering::Greater
20615 } else {
20616 Ordering::Less
20617 }
20618 }) {
20619 Ok(i) | Err(i) => i,
20620 };
20621 for range in &ranges[start_ix..] {
20622 if range
20623 .start
20624 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20625 .is_ge()
20626 {
20627 break;
20628 }
20629
20630 let start = range.start.to_display_point(display_snapshot);
20631 let end = range.end.to_display_point(display_snapshot);
20632 results.push((start..end, color))
20633 }
20634 }
20635 results
20636 }
20637
20638 /// Get the text ranges corresponding to the redaction query
20639 pub fn redacted_ranges(
20640 &self,
20641 search_range: Range<Anchor>,
20642 display_snapshot: &DisplaySnapshot,
20643 cx: &App,
20644 ) -> Vec<Range<DisplayPoint>> {
20645 display_snapshot
20646 .buffer_snapshot()
20647 .redacted_ranges(search_range, |file| {
20648 if let Some(file) = file {
20649 file.is_private()
20650 && EditorSettings::get(
20651 Some(SettingsLocation {
20652 worktree_id: file.worktree_id(cx),
20653 path: file.path().as_ref(),
20654 }),
20655 cx,
20656 )
20657 .redact_private_values
20658 } else {
20659 false
20660 }
20661 })
20662 .map(|range| {
20663 range.start.to_display_point(display_snapshot)
20664 ..range.end.to_display_point(display_snapshot)
20665 })
20666 .collect()
20667 }
20668
20669 pub fn highlight_text_key<T: 'static>(
20670 &mut self,
20671 key: usize,
20672 ranges: Vec<Range<Anchor>>,
20673 style: HighlightStyle,
20674 cx: &mut Context<Self>,
20675 ) {
20676 self.display_map.update(cx, |map, _| {
20677 map.highlight_text(
20678 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20679 ranges,
20680 style,
20681 );
20682 });
20683 cx.notify();
20684 }
20685
20686 pub fn highlight_text<T: 'static>(
20687 &mut self,
20688 ranges: Vec<Range<Anchor>>,
20689 style: HighlightStyle,
20690 cx: &mut Context<Self>,
20691 ) {
20692 self.display_map.update(cx, |map, _| {
20693 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20694 });
20695 cx.notify();
20696 }
20697
20698 pub(crate) fn highlight_inlays<T: 'static>(
20699 &mut self,
20700 highlights: Vec<InlayHighlight>,
20701 style: HighlightStyle,
20702 cx: &mut Context<Self>,
20703 ) {
20704 self.display_map.update(cx, |map, _| {
20705 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20706 });
20707 cx.notify();
20708 }
20709
20710 pub fn text_highlights<'a, T: 'static>(
20711 &'a self,
20712 cx: &'a App,
20713 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20714 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20715 }
20716
20717 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20718 let cleared = self
20719 .display_map
20720 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20721 if cleared {
20722 cx.notify();
20723 }
20724 }
20725
20726 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20727 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20728 && self.focus_handle.is_focused(window)
20729 }
20730
20731 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20732 self.show_cursor_when_unfocused = is_enabled;
20733 cx.notify();
20734 }
20735
20736 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20737 cx.notify();
20738 }
20739
20740 fn on_debug_session_event(
20741 &mut self,
20742 _session: Entity<Session>,
20743 event: &SessionEvent,
20744 cx: &mut Context<Self>,
20745 ) {
20746 if let SessionEvent::InvalidateInlineValue = event {
20747 self.refresh_inline_values(cx);
20748 }
20749 }
20750
20751 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20752 let Some(project) = self.project.clone() else {
20753 return;
20754 };
20755
20756 if !self.inline_value_cache.enabled {
20757 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20758 self.splice_inlays(&inlays, Vec::new(), cx);
20759 return;
20760 }
20761
20762 let current_execution_position = self
20763 .highlighted_rows
20764 .get(&TypeId::of::<ActiveDebugLine>())
20765 .and_then(|lines| lines.last().map(|line| line.range.end));
20766
20767 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20768 let inline_values = editor
20769 .update(cx, |editor, cx| {
20770 let Some(current_execution_position) = current_execution_position else {
20771 return Some(Task::ready(Ok(Vec::new())));
20772 };
20773
20774 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20775 let snapshot = buffer.snapshot(cx);
20776
20777 let excerpt = snapshot.excerpt_containing(
20778 current_execution_position..current_execution_position,
20779 )?;
20780
20781 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20782 })?;
20783
20784 let range =
20785 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20786
20787 project.inline_values(buffer, range, cx)
20788 })
20789 .ok()
20790 .flatten()?
20791 .await
20792 .context("refreshing debugger inlays")
20793 .log_err()?;
20794
20795 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20796
20797 for (buffer_id, inline_value) in inline_values
20798 .into_iter()
20799 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20800 {
20801 buffer_inline_values
20802 .entry(buffer_id)
20803 .or_default()
20804 .push(inline_value);
20805 }
20806
20807 editor
20808 .update(cx, |editor, cx| {
20809 let snapshot = editor.buffer.read(cx).snapshot(cx);
20810 let mut new_inlays = Vec::default();
20811
20812 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20813 let buffer_id = buffer_snapshot.remote_id();
20814 buffer_inline_values
20815 .get(&buffer_id)
20816 .into_iter()
20817 .flatten()
20818 .for_each(|hint| {
20819 let inlay = Inlay::debugger(
20820 post_inc(&mut editor.next_inlay_id),
20821 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20822 hint.text(),
20823 );
20824 if !inlay.text().chars().contains(&'\n') {
20825 new_inlays.push(inlay);
20826 }
20827 });
20828 }
20829
20830 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20831 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20832
20833 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20834 })
20835 .ok()?;
20836 Some(())
20837 });
20838 }
20839
20840 fn on_buffer_event(
20841 &mut self,
20842 multibuffer: &Entity<MultiBuffer>,
20843 event: &multi_buffer::Event,
20844 window: &mut Window,
20845 cx: &mut Context<Self>,
20846 ) {
20847 match event {
20848 multi_buffer::Event::Edited { edited_buffer } => {
20849 self.scrollbar_marker_state.dirty = true;
20850 self.active_indent_guides_state.dirty = true;
20851 self.refresh_active_diagnostics(cx);
20852 self.refresh_code_actions(window, cx);
20853 self.refresh_selected_text_highlights(true, window, cx);
20854 self.refresh_single_line_folds(window, cx);
20855 refresh_matching_bracket_highlights(self, cx);
20856 if self.has_active_edit_prediction() {
20857 self.update_visible_edit_prediction(window, cx);
20858 }
20859
20860 if let Some(edited_buffer) = edited_buffer {
20861 if edited_buffer.read(cx).file().is_none() {
20862 cx.emit(EditorEvent::TitleChanged);
20863 }
20864
20865 let buffer_id = edited_buffer.read(cx).remote_id();
20866 if let Some(project) = self.project.clone() {
20867 self.register_buffer(buffer_id, cx);
20868 self.update_lsp_data(Some(buffer_id), window, cx);
20869 #[allow(clippy::mutable_key_type)]
20870 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20871 multibuffer
20872 .all_buffers()
20873 .into_iter()
20874 .filter_map(|buffer| {
20875 buffer.update(cx, |buffer, cx| {
20876 let language = buffer.language()?;
20877 let should_discard = project.update(cx, |project, cx| {
20878 project.is_local()
20879 && !project.has_language_servers_for(buffer, cx)
20880 });
20881 should_discard.not().then_some(language.clone())
20882 })
20883 })
20884 .collect::<HashSet<_>>()
20885 });
20886 if !languages_affected.is_empty() {
20887 self.refresh_inlay_hints(
20888 InlayHintRefreshReason::BufferEdited(languages_affected),
20889 cx,
20890 );
20891 }
20892 }
20893 }
20894
20895 cx.emit(EditorEvent::BufferEdited);
20896 cx.emit(SearchEvent::MatchesInvalidated);
20897
20898 let Some(project) = &self.project else { return };
20899 let (telemetry, is_via_ssh) = {
20900 let project = project.read(cx);
20901 let telemetry = project.client().telemetry().clone();
20902 let is_via_ssh = project.is_via_remote_server();
20903 (telemetry, is_via_ssh)
20904 };
20905 telemetry.log_edit_event("editor", is_via_ssh);
20906 }
20907 multi_buffer::Event::ExcerptsAdded {
20908 buffer,
20909 predecessor,
20910 excerpts,
20911 } => {
20912 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20913 let buffer_id = buffer.read(cx).remote_id();
20914 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20915 && let Some(project) = &self.project
20916 {
20917 update_uncommitted_diff_for_buffer(
20918 cx.entity(),
20919 project,
20920 [buffer.clone()],
20921 self.buffer.clone(),
20922 cx,
20923 )
20924 .detach();
20925 }
20926 self.update_lsp_data(Some(buffer_id), window, cx);
20927 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20928 cx.emit(EditorEvent::ExcerptsAdded {
20929 buffer: buffer.clone(),
20930 predecessor: *predecessor,
20931 excerpts: excerpts.clone(),
20932 });
20933 }
20934 multi_buffer::Event::ExcerptsRemoved {
20935 ids,
20936 removed_buffer_ids,
20937 } => {
20938 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20939 for buffer_id in removed_buffer_ids {
20940 self.registered_buffers.remove(buffer_id);
20941 }
20942 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20943 cx.emit(EditorEvent::ExcerptsRemoved {
20944 ids: ids.clone(),
20945 removed_buffer_ids: removed_buffer_ids.clone(),
20946 });
20947 }
20948 multi_buffer::Event::ExcerptsEdited {
20949 excerpt_ids,
20950 buffer_ids,
20951 } => {
20952 self.display_map.update(cx, |map, cx| {
20953 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20954 });
20955 cx.emit(EditorEvent::ExcerptsEdited {
20956 ids: excerpt_ids.clone(),
20957 });
20958 }
20959 multi_buffer::Event::ExcerptsExpanded { ids } => {
20960 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20961 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20962 }
20963 multi_buffer::Event::Reparsed(buffer_id) => {
20964 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20965 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20966
20967 cx.emit(EditorEvent::Reparsed(*buffer_id));
20968 }
20969 multi_buffer::Event::DiffHunksToggled => {
20970 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20971 }
20972 multi_buffer::Event::LanguageChanged(buffer_id) => {
20973 self.registered_buffers.remove(&buffer_id);
20974 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20975 cx.emit(EditorEvent::Reparsed(*buffer_id));
20976 cx.notify();
20977 }
20978 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20979 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20980 multi_buffer::Event::FileHandleChanged
20981 | multi_buffer::Event::Reloaded
20982 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20983 multi_buffer::Event::DiagnosticsUpdated => {
20984 self.update_diagnostics_state(window, cx);
20985 }
20986 _ => {}
20987 };
20988 }
20989
20990 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20991 if !self.diagnostics_enabled() {
20992 return;
20993 }
20994 self.refresh_active_diagnostics(cx);
20995 self.refresh_inline_diagnostics(true, window, cx);
20996 self.scrollbar_marker_state.dirty = true;
20997 cx.notify();
20998 }
20999
21000 pub fn start_temporary_diff_override(&mut self) {
21001 self.load_diff_task.take();
21002 self.temporary_diff_override = true;
21003 }
21004
21005 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21006 self.temporary_diff_override = false;
21007 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21008 self.buffer.update(cx, |buffer, cx| {
21009 buffer.set_all_diff_hunks_collapsed(cx);
21010 });
21011
21012 if let Some(project) = self.project.clone() {
21013 self.load_diff_task = Some(
21014 update_uncommitted_diff_for_buffer(
21015 cx.entity(),
21016 &project,
21017 self.buffer.read(cx).all_buffers(),
21018 self.buffer.clone(),
21019 cx,
21020 )
21021 .shared(),
21022 );
21023 }
21024 }
21025
21026 fn on_display_map_changed(
21027 &mut self,
21028 _: Entity<DisplayMap>,
21029 _: &mut Window,
21030 cx: &mut Context<Self>,
21031 ) {
21032 cx.notify();
21033 }
21034
21035 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21036 if self.diagnostics_enabled() {
21037 let new_severity = EditorSettings::get_global(cx)
21038 .diagnostics_max_severity
21039 .unwrap_or(DiagnosticSeverity::Hint);
21040 self.set_max_diagnostics_severity(new_severity, cx);
21041 }
21042 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21043 self.update_edit_prediction_settings(cx);
21044 self.refresh_edit_prediction(true, false, window, cx);
21045 self.refresh_inline_values(cx);
21046 self.refresh_inlay_hints(
21047 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21048 self.selections.newest_anchor().head(),
21049 &self.buffer.read(cx).snapshot(cx),
21050 cx,
21051 )),
21052 cx,
21053 );
21054
21055 let old_cursor_shape = self.cursor_shape;
21056 let old_show_breadcrumbs = self.show_breadcrumbs;
21057
21058 {
21059 let editor_settings = EditorSettings::get_global(cx);
21060 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21061 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21062 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21063 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21064 }
21065
21066 if old_cursor_shape != self.cursor_shape {
21067 cx.emit(EditorEvent::CursorShapeChanged);
21068 }
21069
21070 if old_show_breadcrumbs != self.show_breadcrumbs {
21071 cx.emit(EditorEvent::BreadcrumbsChanged);
21072 }
21073
21074 let project_settings = ProjectSettings::get_global(cx);
21075 self.serialize_dirty_buffers =
21076 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
21077
21078 if self.mode.is_full() {
21079 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21080 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21081 if self.show_inline_diagnostics != show_inline_diagnostics {
21082 self.show_inline_diagnostics = show_inline_diagnostics;
21083 self.refresh_inline_diagnostics(false, window, cx);
21084 }
21085
21086 if self.git_blame_inline_enabled != inline_blame_enabled {
21087 self.toggle_git_blame_inline_internal(false, window, cx);
21088 }
21089
21090 let minimap_settings = EditorSettings::get_global(cx).minimap;
21091 if self.minimap_visibility != MinimapVisibility::Disabled {
21092 if self.minimap_visibility.settings_visibility()
21093 != minimap_settings.minimap_enabled()
21094 {
21095 self.set_minimap_visibility(
21096 MinimapVisibility::for_mode(self.mode(), cx),
21097 window,
21098 cx,
21099 );
21100 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21101 minimap_entity.update(cx, |minimap_editor, cx| {
21102 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21103 })
21104 }
21105 }
21106 }
21107
21108 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21109 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21110 }) {
21111 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
21112 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21113 }
21114 self.refresh_colors_for_visible_range(None, window, cx);
21115 }
21116
21117 cx.notify();
21118 }
21119
21120 pub fn set_searchable(&mut self, searchable: bool) {
21121 self.searchable = searchable;
21122 }
21123
21124 pub fn searchable(&self) -> bool {
21125 self.searchable
21126 }
21127
21128 fn open_proposed_changes_editor(
21129 &mut self,
21130 _: &OpenProposedChangesEditor,
21131 window: &mut Window,
21132 cx: &mut Context<Self>,
21133 ) {
21134 let Some(workspace) = self.workspace() else {
21135 cx.propagate();
21136 return;
21137 };
21138
21139 let selections = self.selections.all::<usize>(cx);
21140 let multi_buffer = self.buffer.read(cx);
21141 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
21142 let mut new_selections_by_buffer = HashMap::default();
21143 for selection in selections {
21144 for (buffer, range, _) in
21145 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
21146 {
21147 let mut range = range.to_point(buffer);
21148 range.start.column = 0;
21149 range.end.column = buffer.line_len(range.end.row);
21150 new_selections_by_buffer
21151 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
21152 .or_insert(Vec::new())
21153 .push(range)
21154 }
21155 }
21156
21157 let proposed_changes_buffers = new_selections_by_buffer
21158 .into_iter()
21159 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
21160 .collect::<Vec<_>>();
21161 let proposed_changes_editor = cx.new(|cx| {
21162 ProposedChangesEditor::new(
21163 "Proposed changes",
21164 proposed_changes_buffers,
21165 self.project.clone(),
21166 window,
21167 cx,
21168 )
21169 });
21170
21171 window.defer(cx, move |window, cx| {
21172 workspace.update(cx, |workspace, cx| {
21173 workspace.active_pane().update(cx, |pane, cx| {
21174 pane.add_item(
21175 Box::new(proposed_changes_editor),
21176 true,
21177 true,
21178 None,
21179 window,
21180 cx,
21181 );
21182 });
21183 });
21184 });
21185 }
21186
21187 pub fn open_excerpts_in_split(
21188 &mut self,
21189 _: &OpenExcerptsSplit,
21190 window: &mut Window,
21191 cx: &mut Context<Self>,
21192 ) {
21193 self.open_excerpts_common(None, true, window, cx)
21194 }
21195
21196 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21197 self.open_excerpts_common(None, false, window, cx)
21198 }
21199
21200 fn open_excerpts_common(
21201 &mut self,
21202 jump_data: Option<JumpData>,
21203 split: bool,
21204 window: &mut Window,
21205 cx: &mut Context<Self>,
21206 ) {
21207 let Some(workspace) = self.workspace() else {
21208 cx.propagate();
21209 return;
21210 };
21211
21212 if self.buffer.read(cx).is_singleton() {
21213 cx.propagate();
21214 return;
21215 }
21216
21217 let mut new_selections_by_buffer = HashMap::default();
21218 match &jump_data {
21219 Some(JumpData::MultiBufferPoint {
21220 excerpt_id,
21221 position,
21222 anchor,
21223 line_offset_from_top,
21224 }) => {
21225 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21226 if let Some(buffer) = multi_buffer_snapshot
21227 .buffer_id_for_excerpt(*excerpt_id)
21228 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21229 {
21230 let buffer_snapshot = buffer.read(cx).snapshot();
21231 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21232 language::ToPoint::to_point(anchor, &buffer_snapshot)
21233 } else {
21234 buffer_snapshot.clip_point(*position, Bias::Left)
21235 };
21236 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21237 new_selections_by_buffer.insert(
21238 buffer,
21239 (
21240 vec![jump_to_offset..jump_to_offset],
21241 Some(*line_offset_from_top),
21242 ),
21243 );
21244 }
21245 }
21246 Some(JumpData::MultiBufferRow {
21247 row,
21248 line_offset_from_top,
21249 }) => {
21250 let point = MultiBufferPoint::new(row.0, 0);
21251 if let Some((buffer, buffer_point, _)) =
21252 self.buffer.read(cx).point_to_buffer_point(point, cx)
21253 {
21254 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21255 new_selections_by_buffer
21256 .entry(buffer)
21257 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21258 .0
21259 .push(buffer_offset..buffer_offset)
21260 }
21261 }
21262 None => {
21263 let selections = self.selections.all::<usize>(cx);
21264 let multi_buffer = self.buffer.read(cx);
21265 for selection in selections {
21266 for (snapshot, range, _, anchor) in multi_buffer
21267 .snapshot(cx)
21268 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21269 {
21270 if let Some(anchor) = anchor {
21271 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21272 else {
21273 continue;
21274 };
21275 let offset = text::ToOffset::to_offset(
21276 &anchor.text_anchor,
21277 &buffer_handle.read(cx).snapshot(),
21278 );
21279 let range = offset..offset;
21280 new_selections_by_buffer
21281 .entry(buffer_handle)
21282 .or_insert((Vec::new(), None))
21283 .0
21284 .push(range)
21285 } else {
21286 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21287 else {
21288 continue;
21289 };
21290 new_selections_by_buffer
21291 .entry(buffer_handle)
21292 .or_insert((Vec::new(), None))
21293 .0
21294 .push(range)
21295 }
21296 }
21297 }
21298 }
21299 }
21300
21301 new_selections_by_buffer
21302 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21303
21304 if new_selections_by_buffer.is_empty() {
21305 return;
21306 }
21307
21308 // We defer the pane interaction because we ourselves are a workspace item
21309 // and activating a new item causes the pane to call a method on us reentrantly,
21310 // which panics if we're on the stack.
21311 window.defer(cx, move |window, cx| {
21312 workspace.update(cx, |workspace, cx| {
21313 let pane = if split {
21314 workspace.adjacent_pane(window, cx)
21315 } else {
21316 workspace.active_pane().clone()
21317 };
21318
21319 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21320 let editor = buffer
21321 .read(cx)
21322 .file()
21323 .is_none()
21324 .then(|| {
21325 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21326 // so `workspace.open_project_item` will never find them, always opening a new editor.
21327 // Instead, we try to activate the existing editor in the pane first.
21328 let (editor, pane_item_index) =
21329 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21330 let editor = item.downcast::<Editor>()?;
21331 let singleton_buffer =
21332 editor.read(cx).buffer().read(cx).as_singleton()?;
21333 if singleton_buffer == buffer {
21334 Some((editor, i))
21335 } else {
21336 None
21337 }
21338 })?;
21339 pane.update(cx, |pane, cx| {
21340 pane.activate_item(pane_item_index, true, true, window, cx)
21341 });
21342 Some(editor)
21343 })
21344 .flatten()
21345 .unwrap_or_else(|| {
21346 workspace.open_project_item::<Self>(
21347 pane.clone(),
21348 buffer,
21349 true,
21350 true,
21351 window,
21352 cx,
21353 )
21354 });
21355
21356 editor.update(cx, |editor, cx| {
21357 let autoscroll = match scroll_offset {
21358 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21359 None => Autoscroll::newest(),
21360 };
21361 let nav_history = editor.nav_history.take();
21362 editor.change_selections(
21363 SelectionEffects::scroll(autoscroll),
21364 window,
21365 cx,
21366 |s| {
21367 s.select_ranges(ranges);
21368 },
21369 );
21370 editor.nav_history = nav_history;
21371 });
21372 }
21373 })
21374 });
21375 }
21376
21377 // For now, don't allow opening excerpts in buffers that aren't backed by
21378 // regular project files.
21379 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21380 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21381 }
21382
21383 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21384 let snapshot = self.buffer.read(cx).read(cx);
21385 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21386 Some(
21387 ranges
21388 .iter()
21389 .map(move |range| {
21390 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21391 })
21392 .collect(),
21393 )
21394 }
21395
21396 fn selection_replacement_ranges(
21397 &self,
21398 range: Range<OffsetUtf16>,
21399 cx: &mut App,
21400 ) -> Vec<Range<OffsetUtf16>> {
21401 let selections = self.selections.all::<OffsetUtf16>(cx);
21402 let newest_selection = selections
21403 .iter()
21404 .max_by_key(|selection| selection.id)
21405 .unwrap();
21406 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21407 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21408 let snapshot = self.buffer.read(cx).read(cx);
21409 selections
21410 .into_iter()
21411 .map(|mut selection| {
21412 selection.start.0 =
21413 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21414 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21415 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21416 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21417 })
21418 .collect()
21419 }
21420
21421 fn report_editor_event(
21422 &self,
21423 reported_event: ReportEditorEvent,
21424 file_extension: Option<String>,
21425 cx: &App,
21426 ) {
21427 if cfg!(any(test, feature = "test-support")) {
21428 return;
21429 }
21430
21431 let Some(project) = &self.project else { return };
21432
21433 // If None, we are in a file without an extension
21434 let file = self
21435 .buffer
21436 .read(cx)
21437 .as_singleton()
21438 .and_then(|b| b.read(cx).file());
21439 let file_extension = file_extension.or(file
21440 .as_ref()
21441 .and_then(|file| Path::new(file.file_name(cx)).extension())
21442 .and_then(|e| e.to_str())
21443 .map(|a| a.to_string()));
21444
21445 let vim_mode = vim_enabled(cx);
21446
21447 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21448 let copilot_enabled = edit_predictions_provider
21449 == language::language_settings::EditPredictionProvider::Copilot;
21450 let copilot_enabled_for_language = self
21451 .buffer
21452 .read(cx)
21453 .language_settings(cx)
21454 .show_edit_predictions;
21455
21456 let project = project.read(cx);
21457 let event_type = reported_event.event_type();
21458
21459 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21460 telemetry::event!(
21461 event_type,
21462 type = if auto_saved {"autosave"} else {"manual"},
21463 file_extension,
21464 vim_mode,
21465 copilot_enabled,
21466 copilot_enabled_for_language,
21467 edit_predictions_provider,
21468 is_via_ssh = project.is_via_remote_server(),
21469 );
21470 } else {
21471 telemetry::event!(
21472 event_type,
21473 file_extension,
21474 vim_mode,
21475 copilot_enabled,
21476 copilot_enabled_for_language,
21477 edit_predictions_provider,
21478 is_via_ssh = project.is_via_remote_server(),
21479 );
21480 };
21481 }
21482
21483 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21484 /// with each line being an array of {text, highlight} objects.
21485 fn copy_highlight_json(
21486 &mut self,
21487 _: &CopyHighlightJson,
21488 window: &mut Window,
21489 cx: &mut Context<Self>,
21490 ) {
21491 #[derive(Serialize)]
21492 struct Chunk<'a> {
21493 text: String,
21494 highlight: Option<&'a str>,
21495 }
21496
21497 let snapshot = self.buffer.read(cx).snapshot(cx);
21498 let range = self
21499 .selected_text_range(false, window, cx)
21500 .and_then(|selection| {
21501 if selection.range.is_empty() {
21502 None
21503 } else {
21504 Some(selection.range)
21505 }
21506 })
21507 .unwrap_or_else(|| 0..snapshot.len());
21508
21509 let chunks = snapshot.chunks(range, true);
21510 let mut lines = Vec::new();
21511 let mut line: VecDeque<Chunk> = VecDeque::new();
21512
21513 let Some(style) = self.style.as_ref() else {
21514 return;
21515 };
21516
21517 for chunk in chunks {
21518 let highlight = chunk
21519 .syntax_highlight_id
21520 .and_then(|id| id.name(&style.syntax));
21521 let mut chunk_lines = chunk.text.split('\n').peekable();
21522 while let Some(text) = chunk_lines.next() {
21523 let mut merged_with_last_token = false;
21524 if let Some(last_token) = line.back_mut()
21525 && last_token.highlight == highlight
21526 {
21527 last_token.text.push_str(text);
21528 merged_with_last_token = true;
21529 }
21530
21531 if !merged_with_last_token {
21532 line.push_back(Chunk {
21533 text: text.into(),
21534 highlight,
21535 });
21536 }
21537
21538 if chunk_lines.peek().is_some() {
21539 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21540 line.pop_front();
21541 }
21542 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21543 line.pop_back();
21544 }
21545
21546 lines.push(mem::take(&mut line));
21547 }
21548 }
21549 }
21550
21551 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21552 return;
21553 };
21554 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21555 }
21556
21557 pub fn open_context_menu(
21558 &mut self,
21559 _: &OpenContextMenu,
21560 window: &mut Window,
21561 cx: &mut Context<Self>,
21562 ) {
21563 self.request_autoscroll(Autoscroll::newest(), cx);
21564 let position = self.selections.newest_display(cx).start;
21565 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21566 }
21567
21568 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
21569 &self.inlay_hint_cache
21570 }
21571
21572 pub fn replay_insert_event(
21573 &mut self,
21574 text: &str,
21575 relative_utf16_range: Option<Range<isize>>,
21576 window: &mut Window,
21577 cx: &mut Context<Self>,
21578 ) {
21579 if !self.input_enabled {
21580 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21581 return;
21582 }
21583 if let Some(relative_utf16_range) = relative_utf16_range {
21584 let selections = self.selections.all::<OffsetUtf16>(cx);
21585 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21586 let new_ranges = selections.into_iter().map(|range| {
21587 let start = OffsetUtf16(
21588 range
21589 .head()
21590 .0
21591 .saturating_add_signed(relative_utf16_range.start),
21592 );
21593 let end = OffsetUtf16(
21594 range
21595 .head()
21596 .0
21597 .saturating_add_signed(relative_utf16_range.end),
21598 );
21599 start..end
21600 });
21601 s.select_ranges(new_ranges);
21602 });
21603 }
21604
21605 self.handle_input(text, window, cx);
21606 }
21607
21608 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
21609 let Some(provider) = self.semantics_provider.as_ref() else {
21610 return false;
21611 };
21612
21613 let mut supports = false;
21614 self.buffer().update(cx, |this, cx| {
21615 this.for_each_buffer(|buffer| {
21616 supports |= provider.supports_inlay_hints(buffer, cx);
21617 });
21618 });
21619
21620 supports
21621 }
21622
21623 pub fn is_focused(&self, window: &Window) -> bool {
21624 self.focus_handle.is_focused(window)
21625 }
21626
21627 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21628 cx.emit(EditorEvent::Focused);
21629
21630 if let Some(descendant) = self
21631 .last_focused_descendant
21632 .take()
21633 .and_then(|descendant| descendant.upgrade())
21634 {
21635 window.focus(&descendant);
21636 } else {
21637 if let Some(blame) = self.blame.as_ref() {
21638 blame.update(cx, GitBlame::focus)
21639 }
21640
21641 self.blink_manager.update(cx, BlinkManager::enable);
21642 self.show_cursor_names(window, cx);
21643 self.buffer.update(cx, |buffer, cx| {
21644 buffer.finalize_last_transaction(cx);
21645 if self.leader_id.is_none() {
21646 buffer.set_active_selections(
21647 &self.selections.disjoint_anchors_arc(),
21648 self.selections.line_mode(),
21649 self.cursor_shape,
21650 cx,
21651 );
21652 }
21653 });
21654 }
21655 }
21656
21657 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21658 cx.emit(EditorEvent::FocusedIn)
21659 }
21660
21661 fn handle_focus_out(
21662 &mut self,
21663 event: FocusOutEvent,
21664 _window: &mut Window,
21665 cx: &mut Context<Self>,
21666 ) {
21667 if event.blurred != self.focus_handle {
21668 self.last_focused_descendant = Some(event.blurred);
21669 }
21670 self.selection_drag_state = SelectionDragState::None;
21671 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21672 }
21673
21674 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21675 self.blink_manager.update(cx, BlinkManager::disable);
21676 self.buffer
21677 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21678
21679 if let Some(blame) = self.blame.as_ref() {
21680 blame.update(cx, GitBlame::blur)
21681 }
21682 if !self.hover_state.focused(window, cx) {
21683 hide_hover(self, cx);
21684 }
21685 if !self
21686 .context_menu
21687 .borrow()
21688 .as_ref()
21689 .is_some_and(|context_menu| context_menu.focused(window, cx))
21690 {
21691 self.hide_context_menu(window, cx);
21692 }
21693 self.take_active_edit_prediction(cx);
21694 cx.emit(EditorEvent::Blurred);
21695 cx.notify();
21696 }
21697
21698 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21699 let mut pending: String = window
21700 .pending_input_keystrokes()
21701 .into_iter()
21702 .flatten()
21703 .filter_map(|keystroke| {
21704 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21705 keystroke.key_char.clone()
21706 } else {
21707 None
21708 }
21709 })
21710 .collect();
21711
21712 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21713 pending = "".to_string();
21714 }
21715
21716 let existing_pending = self
21717 .text_highlights::<PendingInput>(cx)
21718 .map(|(_, ranges)| ranges.to_vec());
21719 if existing_pending.is_none() && pending.is_empty() {
21720 return;
21721 }
21722 let transaction =
21723 self.transact(window, cx, |this, window, cx| {
21724 let selections = this.selections.all::<usize>(cx);
21725 let edits = selections
21726 .iter()
21727 .map(|selection| (selection.end..selection.end, pending.clone()));
21728 this.edit(edits, cx);
21729 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21730 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21731 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21732 }));
21733 });
21734 if let Some(existing_ranges) = existing_pending {
21735 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21736 this.edit(edits, cx);
21737 }
21738 });
21739
21740 let snapshot = self.snapshot(window, cx);
21741 let ranges = self
21742 .selections
21743 .all::<usize>(cx)
21744 .into_iter()
21745 .map(|selection| {
21746 snapshot.buffer_snapshot().anchor_after(selection.end)
21747 ..snapshot
21748 .buffer_snapshot()
21749 .anchor_before(selection.end + pending.len())
21750 })
21751 .collect();
21752
21753 if pending.is_empty() {
21754 self.clear_highlights::<PendingInput>(cx);
21755 } else {
21756 self.highlight_text::<PendingInput>(
21757 ranges,
21758 HighlightStyle {
21759 underline: Some(UnderlineStyle {
21760 thickness: px(1.),
21761 color: None,
21762 wavy: false,
21763 }),
21764 ..Default::default()
21765 },
21766 cx,
21767 );
21768 }
21769
21770 self.ime_transaction = self.ime_transaction.or(transaction);
21771 if let Some(transaction) = self.ime_transaction {
21772 self.buffer.update(cx, |buffer, cx| {
21773 buffer.group_until_transaction(transaction, cx);
21774 });
21775 }
21776
21777 if self.text_highlights::<PendingInput>(cx).is_none() {
21778 self.ime_transaction.take();
21779 }
21780 }
21781
21782 pub fn register_action_renderer(
21783 &mut self,
21784 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21785 ) -> Subscription {
21786 let id = self.next_editor_action_id.post_inc();
21787 self.editor_actions
21788 .borrow_mut()
21789 .insert(id, Box::new(listener));
21790
21791 let editor_actions = self.editor_actions.clone();
21792 Subscription::new(move || {
21793 editor_actions.borrow_mut().remove(&id);
21794 })
21795 }
21796
21797 pub fn register_action<A: Action>(
21798 &mut self,
21799 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21800 ) -> Subscription {
21801 let id = self.next_editor_action_id.post_inc();
21802 let listener = Arc::new(listener);
21803 self.editor_actions.borrow_mut().insert(
21804 id,
21805 Box::new(move |_, window, _| {
21806 let listener = listener.clone();
21807 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21808 let action = action.downcast_ref().unwrap();
21809 if phase == DispatchPhase::Bubble {
21810 listener(action, window, cx)
21811 }
21812 })
21813 }),
21814 );
21815
21816 let editor_actions = self.editor_actions.clone();
21817 Subscription::new(move || {
21818 editor_actions.borrow_mut().remove(&id);
21819 })
21820 }
21821
21822 pub fn file_header_size(&self) -> u32 {
21823 FILE_HEADER_HEIGHT
21824 }
21825
21826 pub fn restore(
21827 &mut self,
21828 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21829 window: &mut Window,
21830 cx: &mut Context<Self>,
21831 ) {
21832 let workspace = self.workspace();
21833 let project = self.project();
21834 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21835 let mut tasks = Vec::new();
21836 for (buffer_id, changes) in revert_changes {
21837 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21838 buffer.update(cx, |buffer, cx| {
21839 buffer.edit(
21840 changes
21841 .into_iter()
21842 .map(|(range, text)| (range, text.to_string())),
21843 None,
21844 cx,
21845 );
21846 });
21847
21848 if let Some(project) =
21849 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21850 {
21851 project.update(cx, |project, cx| {
21852 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21853 })
21854 }
21855 }
21856 }
21857 tasks
21858 });
21859 cx.spawn_in(window, async move |_, cx| {
21860 for (buffer, task) in save_tasks {
21861 let result = task.await;
21862 if result.is_err() {
21863 let Some(path) = buffer
21864 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21865 .ok()
21866 else {
21867 continue;
21868 };
21869 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21870 let Some(task) = cx
21871 .update_window_entity(workspace, |workspace, window, cx| {
21872 workspace
21873 .open_path_preview(path, None, false, false, false, window, cx)
21874 })
21875 .ok()
21876 else {
21877 continue;
21878 };
21879 task.await.log_err();
21880 }
21881 }
21882 }
21883 })
21884 .detach();
21885 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21886 selections.refresh()
21887 });
21888 }
21889
21890 pub fn to_pixel_point(
21891 &self,
21892 source: multi_buffer::Anchor,
21893 editor_snapshot: &EditorSnapshot,
21894 window: &mut Window,
21895 ) -> Option<gpui::Point<Pixels>> {
21896 let source_point = source.to_display_point(editor_snapshot);
21897 self.display_to_pixel_point(source_point, editor_snapshot, window)
21898 }
21899
21900 pub fn display_to_pixel_point(
21901 &self,
21902 source: DisplayPoint,
21903 editor_snapshot: &EditorSnapshot,
21904 window: &mut Window,
21905 ) -> Option<gpui::Point<Pixels>> {
21906 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21907 let text_layout_details = self.text_layout_details(window);
21908 let scroll_top = text_layout_details
21909 .scroll_anchor
21910 .scroll_position(editor_snapshot)
21911 .y;
21912
21913 if source.row().as_f64() < scroll_top.floor() {
21914 return None;
21915 }
21916 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21917 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
21918 Some(gpui::Point::new(source_x, source_y))
21919 }
21920
21921 pub fn has_visible_completions_menu(&self) -> bool {
21922 !self.edit_prediction_preview_is_active()
21923 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21924 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21925 })
21926 }
21927
21928 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21929 if self.mode.is_minimap() {
21930 return;
21931 }
21932 self.addons
21933 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21934 }
21935
21936 pub fn unregister_addon<T: Addon>(&mut self) {
21937 self.addons.remove(&std::any::TypeId::of::<T>());
21938 }
21939
21940 pub fn addon<T: Addon>(&self) -> Option<&T> {
21941 let type_id = std::any::TypeId::of::<T>();
21942 self.addons
21943 .get(&type_id)
21944 .and_then(|item| item.to_any().downcast_ref::<T>())
21945 }
21946
21947 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21948 let type_id = std::any::TypeId::of::<T>();
21949 self.addons
21950 .get_mut(&type_id)
21951 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21952 }
21953
21954 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21955 let text_layout_details = self.text_layout_details(window);
21956 let style = &text_layout_details.editor_style;
21957 let font_id = window.text_system().resolve_font(&style.text.font());
21958 let font_size = style.text.font_size.to_pixels(window.rem_size());
21959 let line_height = style.text.line_height_in_pixels(window.rem_size());
21960 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21961 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21962
21963 CharacterDimensions {
21964 em_width,
21965 em_advance,
21966 line_height,
21967 }
21968 }
21969
21970 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21971 self.load_diff_task.clone()
21972 }
21973
21974 fn read_metadata_from_db(
21975 &mut self,
21976 item_id: u64,
21977 workspace_id: WorkspaceId,
21978 window: &mut Window,
21979 cx: &mut Context<Editor>,
21980 ) {
21981 if self.buffer_kind(cx) == ItemBufferKind::Singleton
21982 && !self.mode.is_minimap()
21983 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21984 {
21985 let buffer_snapshot = OnceCell::new();
21986
21987 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21988 && !folds.is_empty()
21989 {
21990 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21991 self.fold_ranges(
21992 folds
21993 .into_iter()
21994 .map(|(start, end)| {
21995 snapshot.clip_offset(start, Bias::Left)
21996 ..snapshot.clip_offset(end, Bias::Right)
21997 })
21998 .collect(),
21999 false,
22000 window,
22001 cx,
22002 );
22003 }
22004
22005 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22006 && !selections.is_empty()
22007 {
22008 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22009 // skip adding the initial selection to selection history
22010 self.selection_history.mode = SelectionHistoryMode::Skipping;
22011 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22012 s.select_ranges(selections.into_iter().map(|(start, end)| {
22013 snapshot.clip_offset(start, Bias::Left)
22014 ..snapshot.clip_offset(end, Bias::Right)
22015 }));
22016 });
22017 self.selection_history.mode = SelectionHistoryMode::Normal;
22018 };
22019 }
22020
22021 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22022 }
22023
22024 fn update_lsp_data(
22025 &mut self,
22026 for_buffer: Option<BufferId>,
22027 window: &mut Window,
22028 cx: &mut Context<'_, Self>,
22029 ) {
22030 self.pull_diagnostics(for_buffer, window, cx);
22031 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22032 }
22033
22034 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22035 // Singletons are registered on editor creation.
22036 if self.ignore_lsp_data() || self.buffer().read(cx).is_singleton() {
22037 return;
22038 }
22039 for (_, (visible_buffer, _, _)) in self.visible_excerpts(None, cx) {
22040 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22041 }
22042 }
22043
22044 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) -> bool {
22045 if !self.registered_buffers.contains_key(&buffer_id)
22046 && let Some(project) = self.project.as_ref()
22047 {
22048 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22049 project.update(cx, |project, cx| {
22050 self.registered_buffers.insert(
22051 buffer_id,
22052 project.register_buffer_with_language_servers(&buffer, cx),
22053 );
22054 });
22055 return true;
22056 } else {
22057 self.registered_buffers.remove(&buffer_id);
22058 }
22059 }
22060
22061 false
22062 }
22063
22064 fn ignore_lsp_data(&self) -> bool {
22065 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22066 // skip any LSP updates for it.
22067 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22068 }
22069}
22070
22071fn edit_for_markdown_paste<'a>(
22072 buffer: &MultiBufferSnapshot,
22073 range: Range<usize>,
22074 to_insert: &'a str,
22075 url: Option<url::Url>,
22076) -> (Range<usize>, Cow<'a, str>) {
22077 if url.is_none() {
22078 return (range, Cow::Borrowed(to_insert));
22079 };
22080
22081 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22082
22083 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22084 Cow::Borrowed(to_insert)
22085 } else {
22086 Cow::Owned(format!("[{old_text}]({to_insert})"))
22087 };
22088 (range, new_text)
22089}
22090
22091fn vim_enabled(cx: &App) -> bool {
22092 vim_mode_setting::VimModeSetting::try_get(cx)
22093 .map(|vim_mode| vim_mode.0)
22094 .unwrap_or(false)
22095}
22096
22097fn process_completion_for_edit(
22098 completion: &Completion,
22099 intent: CompletionIntent,
22100 buffer: &Entity<Buffer>,
22101 cursor_position: &text::Anchor,
22102 cx: &mut Context<Editor>,
22103) -> CompletionEdit {
22104 let buffer = buffer.read(cx);
22105 let buffer_snapshot = buffer.snapshot();
22106 let (snippet, new_text) = if completion.is_snippet() {
22107 let mut snippet_source = completion.new_text.clone();
22108 // Workaround for typescript language server issues so that methods don't expand within
22109 // strings and functions with type expressions. The previous point is used because the query
22110 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22111 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22112 let previous_point = if previous_point.column > 0 {
22113 cursor_position.to_previous_offset(&buffer_snapshot)
22114 } else {
22115 cursor_position.to_offset(&buffer_snapshot)
22116 };
22117 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22118 && scope.prefers_label_for_snippet_in_completion()
22119 && let Some(label) = completion.label()
22120 && matches!(
22121 completion.kind(),
22122 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22123 )
22124 {
22125 snippet_source = label;
22126 }
22127 match Snippet::parse(&snippet_source).log_err() {
22128 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22129 None => (None, completion.new_text.clone()),
22130 }
22131 } else {
22132 (None, completion.new_text.clone())
22133 };
22134
22135 let mut range_to_replace = {
22136 let replace_range = &completion.replace_range;
22137 if let CompletionSource::Lsp {
22138 insert_range: Some(insert_range),
22139 ..
22140 } = &completion.source
22141 {
22142 debug_assert_eq!(
22143 insert_range.start, replace_range.start,
22144 "insert_range and replace_range should start at the same position"
22145 );
22146 debug_assert!(
22147 insert_range
22148 .start
22149 .cmp(cursor_position, &buffer_snapshot)
22150 .is_le(),
22151 "insert_range should start before or at cursor position"
22152 );
22153 debug_assert!(
22154 replace_range
22155 .start
22156 .cmp(cursor_position, &buffer_snapshot)
22157 .is_le(),
22158 "replace_range should start before or at cursor position"
22159 );
22160
22161 let should_replace = match intent {
22162 CompletionIntent::CompleteWithInsert => false,
22163 CompletionIntent::CompleteWithReplace => true,
22164 CompletionIntent::Complete | CompletionIntent::Compose => {
22165 let insert_mode =
22166 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22167 .completions
22168 .lsp_insert_mode;
22169 match insert_mode {
22170 LspInsertMode::Insert => false,
22171 LspInsertMode::Replace => true,
22172 LspInsertMode::ReplaceSubsequence => {
22173 let mut text_to_replace = buffer.chars_for_range(
22174 buffer.anchor_before(replace_range.start)
22175 ..buffer.anchor_after(replace_range.end),
22176 );
22177 let mut current_needle = text_to_replace.next();
22178 for haystack_ch in completion.label.text.chars() {
22179 if let Some(needle_ch) = current_needle
22180 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22181 {
22182 current_needle = text_to_replace.next();
22183 }
22184 }
22185 current_needle.is_none()
22186 }
22187 LspInsertMode::ReplaceSuffix => {
22188 if replace_range
22189 .end
22190 .cmp(cursor_position, &buffer_snapshot)
22191 .is_gt()
22192 {
22193 let range_after_cursor = *cursor_position..replace_range.end;
22194 let text_after_cursor = buffer
22195 .text_for_range(
22196 buffer.anchor_before(range_after_cursor.start)
22197 ..buffer.anchor_after(range_after_cursor.end),
22198 )
22199 .collect::<String>()
22200 .to_ascii_lowercase();
22201 completion
22202 .label
22203 .text
22204 .to_ascii_lowercase()
22205 .ends_with(&text_after_cursor)
22206 } else {
22207 true
22208 }
22209 }
22210 }
22211 }
22212 };
22213
22214 if should_replace {
22215 replace_range.clone()
22216 } else {
22217 insert_range.clone()
22218 }
22219 } else {
22220 replace_range.clone()
22221 }
22222 };
22223
22224 if range_to_replace
22225 .end
22226 .cmp(cursor_position, &buffer_snapshot)
22227 .is_lt()
22228 {
22229 range_to_replace.end = *cursor_position;
22230 }
22231
22232 CompletionEdit {
22233 new_text,
22234 replace_range: range_to_replace.to_offset(buffer),
22235 snippet,
22236 }
22237}
22238
22239struct CompletionEdit {
22240 new_text: String,
22241 replace_range: Range<usize>,
22242 snippet: Option<Snippet>,
22243}
22244
22245fn insert_extra_newline_brackets(
22246 buffer: &MultiBufferSnapshot,
22247 range: Range<usize>,
22248 language: &language::LanguageScope,
22249) -> bool {
22250 let leading_whitespace_len = buffer
22251 .reversed_chars_at(range.start)
22252 .take_while(|c| c.is_whitespace() && *c != '\n')
22253 .map(|c| c.len_utf8())
22254 .sum::<usize>();
22255 let trailing_whitespace_len = buffer
22256 .chars_at(range.end)
22257 .take_while(|c| c.is_whitespace() && *c != '\n')
22258 .map(|c| c.len_utf8())
22259 .sum::<usize>();
22260 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22261
22262 language.brackets().any(|(pair, enabled)| {
22263 let pair_start = pair.start.trim_end();
22264 let pair_end = pair.end.trim_start();
22265
22266 enabled
22267 && pair.newline
22268 && buffer.contains_str_at(range.end, pair_end)
22269 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22270 })
22271}
22272
22273fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22274 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22275 [(buffer, range, _)] => (*buffer, range.clone()),
22276 _ => return false,
22277 };
22278 let pair = {
22279 let mut result: Option<BracketMatch> = None;
22280
22281 for pair in buffer
22282 .all_bracket_ranges(range.clone())
22283 .filter(move |pair| {
22284 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22285 })
22286 {
22287 let len = pair.close_range.end - pair.open_range.start;
22288
22289 if let Some(existing) = &result {
22290 let existing_len = existing.close_range.end - existing.open_range.start;
22291 if len > existing_len {
22292 continue;
22293 }
22294 }
22295
22296 result = Some(pair);
22297 }
22298
22299 result
22300 };
22301 let Some(pair) = pair else {
22302 return false;
22303 };
22304 pair.newline_only
22305 && buffer
22306 .chars_for_range(pair.open_range.end..range.start)
22307 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22308 .all(|c| c.is_whitespace() && c != '\n')
22309}
22310
22311fn update_uncommitted_diff_for_buffer(
22312 editor: Entity<Editor>,
22313 project: &Entity<Project>,
22314 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22315 buffer: Entity<MultiBuffer>,
22316 cx: &mut App,
22317) -> Task<()> {
22318 let mut tasks = Vec::new();
22319 project.update(cx, |project, cx| {
22320 for buffer in buffers {
22321 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22322 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22323 }
22324 }
22325 });
22326 cx.spawn(async move |cx| {
22327 let diffs = future::join_all(tasks).await;
22328 if editor
22329 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22330 .unwrap_or(false)
22331 {
22332 return;
22333 }
22334
22335 buffer
22336 .update(cx, |buffer, cx| {
22337 for diff in diffs.into_iter().flatten() {
22338 buffer.add_diff(diff, cx);
22339 }
22340 })
22341 .ok();
22342 })
22343}
22344
22345fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22346 let tab_size = tab_size.get() as usize;
22347 let mut width = offset;
22348
22349 for ch in text.chars() {
22350 width += if ch == '\t' {
22351 tab_size - (width % tab_size)
22352 } else {
22353 1
22354 };
22355 }
22356
22357 width - offset
22358}
22359
22360#[cfg(test)]
22361mod tests {
22362 use super::*;
22363
22364 #[test]
22365 fn test_string_size_with_expanded_tabs() {
22366 let nz = |val| NonZeroU32::new(val).unwrap();
22367 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22368 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22369 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22370 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22371 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22372 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22373 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22374 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22375 }
22376}
22377
22378/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22379struct WordBreakingTokenizer<'a> {
22380 input: &'a str,
22381}
22382
22383impl<'a> WordBreakingTokenizer<'a> {
22384 fn new(input: &'a str) -> Self {
22385 Self { input }
22386 }
22387}
22388
22389fn is_char_ideographic(ch: char) -> bool {
22390 use unicode_script::Script::*;
22391 use unicode_script::UnicodeScript;
22392 matches!(ch.script(), Han | Tangut | Yi)
22393}
22394
22395fn is_grapheme_ideographic(text: &str) -> bool {
22396 text.chars().any(is_char_ideographic)
22397}
22398
22399fn is_grapheme_whitespace(text: &str) -> bool {
22400 text.chars().any(|x| x.is_whitespace())
22401}
22402
22403fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22404 text.chars()
22405 .next()
22406 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22407}
22408
22409#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22410enum WordBreakToken<'a> {
22411 Word { token: &'a str, grapheme_len: usize },
22412 InlineWhitespace { token: &'a str, grapheme_len: usize },
22413 Newline,
22414}
22415
22416impl<'a> Iterator for WordBreakingTokenizer<'a> {
22417 /// Yields a span, the count of graphemes in the token, and whether it was
22418 /// whitespace. Note that it also breaks at word boundaries.
22419 type Item = WordBreakToken<'a>;
22420
22421 fn next(&mut self) -> Option<Self::Item> {
22422 use unicode_segmentation::UnicodeSegmentation;
22423 if self.input.is_empty() {
22424 return None;
22425 }
22426
22427 let mut iter = self.input.graphemes(true).peekable();
22428 let mut offset = 0;
22429 let mut grapheme_len = 0;
22430 if let Some(first_grapheme) = iter.next() {
22431 let is_newline = first_grapheme == "\n";
22432 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22433 offset += first_grapheme.len();
22434 grapheme_len += 1;
22435 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22436 if let Some(grapheme) = iter.peek().copied()
22437 && should_stay_with_preceding_ideograph(grapheme)
22438 {
22439 offset += grapheme.len();
22440 grapheme_len += 1;
22441 }
22442 } else {
22443 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22444 let mut next_word_bound = words.peek().copied();
22445 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22446 next_word_bound = words.next();
22447 }
22448 while let Some(grapheme) = iter.peek().copied() {
22449 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22450 break;
22451 };
22452 if is_grapheme_whitespace(grapheme) != is_whitespace
22453 || (grapheme == "\n") != is_newline
22454 {
22455 break;
22456 };
22457 offset += grapheme.len();
22458 grapheme_len += 1;
22459 iter.next();
22460 }
22461 }
22462 let token = &self.input[..offset];
22463 self.input = &self.input[offset..];
22464 if token == "\n" {
22465 Some(WordBreakToken::Newline)
22466 } else if is_whitespace {
22467 Some(WordBreakToken::InlineWhitespace {
22468 token,
22469 grapheme_len,
22470 })
22471 } else {
22472 Some(WordBreakToken::Word {
22473 token,
22474 grapheme_len,
22475 })
22476 }
22477 } else {
22478 None
22479 }
22480 }
22481}
22482
22483#[test]
22484fn test_word_breaking_tokenizer() {
22485 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22486 ("", &[]),
22487 (" ", &[whitespace(" ", 2)]),
22488 ("Ʒ", &[word("Ʒ", 1)]),
22489 ("Ǽ", &[word("Ǽ", 1)]),
22490 ("⋑", &[word("⋑", 1)]),
22491 ("⋑⋑", &[word("⋑⋑", 2)]),
22492 (
22493 "原理,进而",
22494 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22495 ),
22496 (
22497 "hello world",
22498 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22499 ),
22500 (
22501 "hello, world",
22502 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22503 ),
22504 (
22505 " hello world",
22506 &[
22507 whitespace(" ", 2),
22508 word("hello", 5),
22509 whitespace(" ", 1),
22510 word("world", 5),
22511 ],
22512 ),
22513 (
22514 "这是什么 \n 钢笔",
22515 &[
22516 word("这", 1),
22517 word("是", 1),
22518 word("什", 1),
22519 word("么", 1),
22520 whitespace(" ", 1),
22521 newline(),
22522 whitespace(" ", 1),
22523 word("钢", 1),
22524 word("笔", 1),
22525 ],
22526 ),
22527 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22528 ];
22529
22530 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22531 WordBreakToken::Word {
22532 token,
22533 grapheme_len,
22534 }
22535 }
22536
22537 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22538 WordBreakToken::InlineWhitespace {
22539 token,
22540 grapheme_len,
22541 }
22542 }
22543
22544 fn newline() -> WordBreakToken<'static> {
22545 WordBreakToken::Newline
22546 }
22547
22548 for (input, result) in tests {
22549 assert_eq!(
22550 WordBreakingTokenizer::new(input)
22551 .collect::<Vec<_>>()
22552 .as_slice(),
22553 *result,
22554 );
22555 }
22556}
22557
22558fn wrap_with_prefix(
22559 first_line_prefix: String,
22560 subsequent_lines_prefix: String,
22561 unwrapped_text: String,
22562 wrap_column: usize,
22563 tab_size: NonZeroU32,
22564 preserve_existing_whitespace: bool,
22565) -> String {
22566 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22567 let subsequent_lines_prefix_len =
22568 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22569 let mut wrapped_text = String::new();
22570 let mut current_line = first_line_prefix;
22571 let mut is_first_line = true;
22572
22573 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22574 let mut current_line_len = first_line_prefix_len;
22575 let mut in_whitespace = false;
22576 for token in tokenizer {
22577 let have_preceding_whitespace = in_whitespace;
22578 match token {
22579 WordBreakToken::Word {
22580 token,
22581 grapheme_len,
22582 } => {
22583 in_whitespace = false;
22584 let current_prefix_len = if is_first_line {
22585 first_line_prefix_len
22586 } else {
22587 subsequent_lines_prefix_len
22588 };
22589 if current_line_len + grapheme_len > wrap_column
22590 && current_line_len != current_prefix_len
22591 {
22592 wrapped_text.push_str(current_line.trim_end());
22593 wrapped_text.push('\n');
22594 is_first_line = false;
22595 current_line = subsequent_lines_prefix.clone();
22596 current_line_len = subsequent_lines_prefix_len;
22597 }
22598 current_line.push_str(token);
22599 current_line_len += grapheme_len;
22600 }
22601 WordBreakToken::InlineWhitespace {
22602 mut token,
22603 mut grapheme_len,
22604 } => {
22605 in_whitespace = true;
22606 if have_preceding_whitespace && !preserve_existing_whitespace {
22607 continue;
22608 }
22609 if !preserve_existing_whitespace {
22610 // Keep a single whitespace grapheme as-is
22611 if let Some(first) =
22612 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22613 {
22614 token = first;
22615 } else {
22616 token = " ";
22617 }
22618 grapheme_len = 1;
22619 }
22620 let current_prefix_len = if is_first_line {
22621 first_line_prefix_len
22622 } else {
22623 subsequent_lines_prefix_len
22624 };
22625 if current_line_len + grapheme_len > wrap_column {
22626 wrapped_text.push_str(current_line.trim_end());
22627 wrapped_text.push('\n');
22628 is_first_line = false;
22629 current_line = subsequent_lines_prefix.clone();
22630 current_line_len = subsequent_lines_prefix_len;
22631 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22632 current_line.push_str(token);
22633 current_line_len += grapheme_len;
22634 }
22635 }
22636 WordBreakToken::Newline => {
22637 in_whitespace = true;
22638 let current_prefix_len = if is_first_line {
22639 first_line_prefix_len
22640 } else {
22641 subsequent_lines_prefix_len
22642 };
22643 if preserve_existing_whitespace {
22644 wrapped_text.push_str(current_line.trim_end());
22645 wrapped_text.push('\n');
22646 is_first_line = false;
22647 current_line = subsequent_lines_prefix.clone();
22648 current_line_len = subsequent_lines_prefix_len;
22649 } else if have_preceding_whitespace {
22650 continue;
22651 } else if current_line_len + 1 > wrap_column
22652 && current_line_len != current_prefix_len
22653 {
22654 wrapped_text.push_str(current_line.trim_end());
22655 wrapped_text.push('\n');
22656 is_first_line = false;
22657 current_line = subsequent_lines_prefix.clone();
22658 current_line_len = subsequent_lines_prefix_len;
22659 } else if current_line_len != current_prefix_len {
22660 current_line.push(' ');
22661 current_line_len += 1;
22662 }
22663 }
22664 }
22665 }
22666
22667 if !current_line.is_empty() {
22668 wrapped_text.push_str(¤t_line);
22669 }
22670 wrapped_text
22671}
22672
22673#[test]
22674fn test_wrap_with_prefix() {
22675 assert_eq!(
22676 wrap_with_prefix(
22677 "# ".to_string(),
22678 "# ".to_string(),
22679 "abcdefg".to_string(),
22680 4,
22681 NonZeroU32::new(4).unwrap(),
22682 false,
22683 ),
22684 "# abcdefg"
22685 );
22686 assert_eq!(
22687 wrap_with_prefix(
22688 "".to_string(),
22689 "".to_string(),
22690 "\thello world".to_string(),
22691 8,
22692 NonZeroU32::new(4).unwrap(),
22693 false,
22694 ),
22695 "hello\nworld"
22696 );
22697 assert_eq!(
22698 wrap_with_prefix(
22699 "// ".to_string(),
22700 "// ".to_string(),
22701 "xx \nyy zz aa bb cc".to_string(),
22702 12,
22703 NonZeroU32::new(4).unwrap(),
22704 false,
22705 ),
22706 "// xx yy zz\n// aa bb cc"
22707 );
22708 assert_eq!(
22709 wrap_with_prefix(
22710 String::new(),
22711 String::new(),
22712 "这是什么 \n 钢笔".to_string(),
22713 3,
22714 NonZeroU32::new(4).unwrap(),
22715 false,
22716 ),
22717 "这是什\n么 钢\n笔"
22718 );
22719 assert_eq!(
22720 wrap_with_prefix(
22721 String::new(),
22722 String::new(),
22723 format!("foo{}bar", '\u{2009}'), // thin space
22724 80,
22725 NonZeroU32::new(4).unwrap(),
22726 false,
22727 ),
22728 format!("foo{}bar", '\u{2009}')
22729 );
22730}
22731
22732pub trait CollaborationHub {
22733 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22734 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22735 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22736}
22737
22738impl CollaborationHub for Entity<Project> {
22739 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22740 self.read(cx).collaborators()
22741 }
22742
22743 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22744 self.read(cx).user_store().read(cx).participant_indices()
22745 }
22746
22747 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22748 let this = self.read(cx);
22749 let user_ids = this.collaborators().values().map(|c| c.user_id);
22750 this.user_store().read(cx).participant_names(user_ids, cx)
22751 }
22752}
22753
22754pub trait SemanticsProvider {
22755 fn hover(
22756 &self,
22757 buffer: &Entity<Buffer>,
22758 position: text::Anchor,
22759 cx: &mut App,
22760 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22761
22762 fn inline_values(
22763 &self,
22764 buffer_handle: Entity<Buffer>,
22765 range: Range<text::Anchor>,
22766 cx: &mut App,
22767 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22768
22769 fn inlay_hints(
22770 &self,
22771 buffer_handle: Entity<Buffer>,
22772 range: Range<text::Anchor>,
22773 cx: &mut App,
22774 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22775
22776 fn resolve_inlay_hint(
22777 &self,
22778 hint: InlayHint,
22779 buffer_handle: Entity<Buffer>,
22780 server_id: LanguageServerId,
22781 cx: &mut App,
22782 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22783
22784 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22785
22786 fn document_highlights(
22787 &self,
22788 buffer: &Entity<Buffer>,
22789 position: text::Anchor,
22790 cx: &mut App,
22791 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22792
22793 fn definitions(
22794 &self,
22795 buffer: &Entity<Buffer>,
22796 position: text::Anchor,
22797 kind: GotoDefinitionKind,
22798 cx: &mut App,
22799 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22800
22801 fn range_for_rename(
22802 &self,
22803 buffer: &Entity<Buffer>,
22804 position: text::Anchor,
22805 cx: &mut App,
22806 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22807
22808 fn perform_rename(
22809 &self,
22810 buffer: &Entity<Buffer>,
22811 position: text::Anchor,
22812 new_name: String,
22813 cx: &mut App,
22814 ) -> Option<Task<Result<ProjectTransaction>>>;
22815}
22816
22817pub trait CompletionProvider {
22818 fn completions(
22819 &self,
22820 excerpt_id: ExcerptId,
22821 buffer: &Entity<Buffer>,
22822 buffer_position: text::Anchor,
22823 trigger: CompletionContext,
22824 window: &mut Window,
22825 cx: &mut Context<Editor>,
22826 ) -> Task<Result<Vec<CompletionResponse>>>;
22827
22828 fn resolve_completions(
22829 &self,
22830 _buffer: Entity<Buffer>,
22831 _completion_indices: Vec<usize>,
22832 _completions: Rc<RefCell<Box<[Completion]>>>,
22833 _cx: &mut Context<Editor>,
22834 ) -> Task<Result<bool>> {
22835 Task::ready(Ok(false))
22836 }
22837
22838 fn apply_additional_edits_for_completion(
22839 &self,
22840 _buffer: Entity<Buffer>,
22841 _completions: Rc<RefCell<Box<[Completion]>>>,
22842 _completion_index: usize,
22843 _push_to_history: bool,
22844 _cx: &mut Context<Editor>,
22845 ) -> Task<Result<Option<language::Transaction>>> {
22846 Task::ready(Ok(None))
22847 }
22848
22849 fn is_completion_trigger(
22850 &self,
22851 buffer: &Entity<Buffer>,
22852 position: language::Anchor,
22853 text: &str,
22854 trigger_in_words: bool,
22855 menu_is_open: bool,
22856 cx: &mut Context<Editor>,
22857 ) -> bool;
22858
22859 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22860
22861 fn sort_completions(&self) -> bool {
22862 true
22863 }
22864
22865 fn filter_completions(&self) -> bool {
22866 true
22867 }
22868}
22869
22870pub trait CodeActionProvider {
22871 fn id(&self) -> Arc<str>;
22872
22873 fn code_actions(
22874 &self,
22875 buffer: &Entity<Buffer>,
22876 range: Range<text::Anchor>,
22877 window: &mut Window,
22878 cx: &mut App,
22879 ) -> Task<Result<Vec<CodeAction>>>;
22880
22881 fn apply_code_action(
22882 &self,
22883 buffer_handle: Entity<Buffer>,
22884 action: CodeAction,
22885 excerpt_id: ExcerptId,
22886 push_to_history: bool,
22887 window: &mut Window,
22888 cx: &mut App,
22889 ) -> Task<Result<ProjectTransaction>>;
22890}
22891
22892impl CodeActionProvider for Entity<Project> {
22893 fn id(&self) -> Arc<str> {
22894 "project".into()
22895 }
22896
22897 fn code_actions(
22898 &self,
22899 buffer: &Entity<Buffer>,
22900 range: Range<text::Anchor>,
22901 _window: &mut Window,
22902 cx: &mut App,
22903 ) -> Task<Result<Vec<CodeAction>>> {
22904 self.update(cx, |project, cx| {
22905 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22906 let code_actions = project.code_actions(buffer, range, None, cx);
22907 cx.background_spawn(async move {
22908 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22909 Ok(code_lens_actions
22910 .context("code lens fetch")?
22911 .into_iter()
22912 .flatten()
22913 .chain(
22914 code_actions
22915 .context("code action fetch")?
22916 .into_iter()
22917 .flatten(),
22918 )
22919 .collect())
22920 })
22921 })
22922 }
22923
22924 fn apply_code_action(
22925 &self,
22926 buffer_handle: Entity<Buffer>,
22927 action: CodeAction,
22928 _excerpt_id: ExcerptId,
22929 push_to_history: bool,
22930 _window: &mut Window,
22931 cx: &mut App,
22932 ) -> Task<Result<ProjectTransaction>> {
22933 self.update(cx, |project, cx| {
22934 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22935 })
22936 }
22937}
22938
22939fn snippet_completions(
22940 project: &Project,
22941 buffer: &Entity<Buffer>,
22942 buffer_position: text::Anchor,
22943 cx: &mut App,
22944) -> Task<Result<CompletionResponse>> {
22945 let languages = buffer.read(cx).languages_at(buffer_position);
22946 let snippet_store = project.snippets().read(cx);
22947
22948 let scopes: Vec<_> = languages
22949 .iter()
22950 .filter_map(|language| {
22951 let language_name = language.lsp_id();
22952 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22953
22954 if snippets.is_empty() {
22955 None
22956 } else {
22957 Some((language.default_scope(), snippets))
22958 }
22959 })
22960 .collect();
22961
22962 if scopes.is_empty() {
22963 return Task::ready(Ok(CompletionResponse {
22964 completions: vec![],
22965 display_options: CompletionDisplayOptions::default(),
22966 is_incomplete: false,
22967 }));
22968 }
22969
22970 let snapshot = buffer.read(cx).text_snapshot();
22971 let executor = cx.background_executor().clone();
22972
22973 cx.background_spawn(async move {
22974 let mut is_incomplete = false;
22975 let mut completions: Vec<Completion> = Vec::new();
22976 for (scope, snippets) in scopes.into_iter() {
22977 let classifier =
22978 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
22979
22980 const MAX_WORD_PREFIX_LEN: usize = 128;
22981 let last_word: String = snapshot
22982 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22983 .take(MAX_WORD_PREFIX_LEN)
22984 .take_while(|c| classifier.is_word(*c))
22985 .collect::<String>()
22986 .chars()
22987 .rev()
22988 .collect();
22989
22990 if last_word.is_empty() {
22991 return Ok(CompletionResponse {
22992 completions: vec![],
22993 display_options: CompletionDisplayOptions::default(),
22994 is_incomplete: true,
22995 });
22996 }
22997
22998 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22999 let to_lsp = |point: &text::Anchor| {
23000 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
23001 point_to_lsp(end)
23002 };
23003 let lsp_end = to_lsp(&buffer_position);
23004
23005 let candidates = snippets
23006 .iter()
23007 .enumerate()
23008 .flat_map(|(ix, snippet)| {
23009 snippet
23010 .prefix
23011 .iter()
23012 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
23013 })
23014 .collect::<Vec<StringMatchCandidate>>();
23015
23016 const MAX_RESULTS: usize = 100;
23017 let mut matches = fuzzy::match_strings(
23018 &candidates,
23019 &last_word,
23020 last_word.chars().any(|c| c.is_uppercase()),
23021 true,
23022 MAX_RESULTS,
23023 &Default::default(),
23024 executor.clone(),
23025 )
23026 .await;
23027
23028 if matches.len() >= MAX_RESULTS {
23029 is_incomplete = true;
23030 }
23031
23032 // Remove all candidates where the query's start does not match the start of any word in the candidate
23033 if let Some(query_start) = last_word.chars().next() {
23034 matches.retain(|string_match| {
23035 split_words(&string_match.string).any(|word| {
23036 // Check that the first codepoint of the word as lowercase matches the first
23037 // codepoint of the query as lowercase
23038 word.chars()
23039 .flat_map(|codepoint| codepoint.to_lowercase())
23040 .zip(query_start.to_lowercase())
23041 .all(|(word_cp, query_cp)| word_cp == query_cp)
23042 })
23043 });
23044 }
23045
23046 let matched_strings = matches
23047 .into_iter()
23048 .map(|m| m.string)
23049 .collect::<HashSet<_>>();
23050
23051 completions.extend(snippets.iter().filter_map(|snippet| {
23052 let matching_prefix = snippet
23053 .prefix
23054 .iter()
23055 .find(|prefix| matched_strings.contains(*prefix))?;
23056 let start = as_offset - last_word.len();
23057 let start = snapshot.anchor_before(start);
23058 let range = start..buffer_position;
23059 let lsp_start = to_lsp(&start);
23060 let lsp_range = lsp::Range {
23061 start: lsp_start,
23062 end: lsp_end,
23063 };
23064 Some(Completion {
23065 replace_range: range,
23066 new_text: snippet.body.clone(),
23067 source: CompletionSource::Lsp {
23068 insert_range: None,
23069 server_id: LanguageServerId(usize::MAX),
23070 resolved: true,
23071 lsp_completion: Box::new(lsp::CompletionItem {
23072 label: snippet.prefix.first().unwrap().clone(),
23073 kind: Some(CompletionItemKind::SNIPPET),
23074 label_details: snippet.description.as_ref().map(|description| {
23075 lsp::CompletionItemLabelDetails {
23076 detail: Some(description.clone()),
23077 description: None,
23078 }
23079 }),
23080 insert_text_format: Some(InsertTextFormat::SNIPPET),
23081 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23082 lsp::InsertReplaceEdit {
23083 new_text: snippet.body.clone(),
23084 insert: lsp_range,
23085 replace: lsp_range,
23086 },
23087 )),
23088 filter_text: Some(snippet.body.clone()),
23089 sort_text: Some(char::MAX.to_string()),
23090 ..lsp::CompletionItem::default()
23091 }),
23092 lsp_defaults: None,
23093 },
23094 label: CodeLabel::plain(matching_prefix.clone(), None),
23095 icon_path: None,
23096 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23097 single_line: snippet.name.clone().into(),
23098 plain_text: snippet
23099 .description
23100 .clone()
23101 .map(|description| description.into()),
23102 }),
23103 insert_text_mode: None,
23104 confirm: None,
23105 })
23106 }))
23107 }
23108
23109 Ok(CompletionResponse {
23110 completions,
23111 display_options: CompletionDisplayOptions::default(),
23112 is_incomplete,
23113 })
23114 })
23115}
23116
23117impl CompletionProvider for Entity<Project> {
23118 fn completions(
23119 &self,
23120 _excerpt_id: ExcerptId,
23121 buffer: &Entity<Buffer>,
23122 buffer_position: text::Anchor,
23123 options: CompletionContext,
23124 _window: &mut Window,
23125 cx: &mut Context<Editor>,
23126 ) -> Task<Result<Vec<CompletionResponse>>> {
23127 self.update(cx, |project, cx| {
23128 let snippets = snippet_completions(project, buffer, buffer_position, cx);
23129 let project_completions = project.completions(buffer, buffer_position, options, cx);
23130 cx.background_spawn(async move {
23131 let mut responses = project_completions.await?;
23132 let snippets = snippets.await?;
23133 if !snippets.completions.is_empty() {
23134 responses.push(snippets);
23135 }
23136 Ok(responses)
23137 })
23138 })
23139 }
23140
23141 fn resolve_completions(
23142 &self,
23143 buffer: Entity<Buffer>,
23144 completion_indices: Vec<usize>,
23145 completions: Rc<RefCell<Box<[Completion]>>>,
23146 cx: &mut Context<Editor>,
23147 ) -> Task<Result<bool>> {
23148 self.update(cx, |project, cx| {
23149 project.lsp_store().update(cx, |lsp_store, cx| {
23150 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23151 })
23152 })
23153 }
23154
23155 fn apply_additional_edits_for_completion(
23156 &self,
23157 buffer: Entity<Buffer>,
23158 completions: Rc<RefCell<Box<[Completion]>>>,
23159 completion_index: usize,
23160 push_to_history: bool,
23161 cx: &mut Context<Editor>,
23162 ) -> Task<Result<Option<language::Transaction>>> {
23163 self.update(cx, |project, cx| {
23164 project.lsp_store().update(cx, |lsp_store, cx| {
23165 lsp_store.apply_additional_edits_for_completion(
23166 buffer,
23167 completions,
23168 completion_index,
23169 push_to_history,
23170 cx,
23171 )
23172 })
23173 })
23174 }
23175
23176 fn is_completion_trigger(
23177 &self,
23178 buffer: &Entity<Buffer>,
23179 position: language::Anchor,
23180 text: &str,
23181 trigger_in_words: bool,
23182 menu_is_open: bool,
23183 cx: &mut Context<Editor>,
23184 ) -> bool {
23185 let mut chars = text.chars();
23186 let char = if let Some(char) = chars.next() {
23187 char
23188 } else {
23189 return false;
23190 };
23191 if chars.next().is_some() {
23192 return false;
23193 }
23194
23195 let buffer = buffer.read(cx);
23196 let snapshot = buffer.snapshot();
23197 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23198 return false;
23199 }
23200 let classifier = snapshot
23201 .char_classifier_at(position)
23202 .scope_context(Some(CharScopeContext::Completion));
23203 if trigger_in_words && classifier.is_word(char) {
23204 return true;
23205 }
23206
23207 buffer.completion_triggers().contains(text)
23208 }
23209}
23210
23211impl SemanticsProvider for Entity<Project> {
23212 fn hover(
23213 &self,
23214 buffer: &Entity<Buffer>,
23215 position: text::Anchor,
23216 cx: &mut App,
23217 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23218 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23219 }
23220
23221 fn document_highlights(
23222 &self,
23223 buffer: &Entity<Buffer>,
23224 position: text::Anchor,
23225 cx: &mut App,
23226 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23227 Some(self.update(cx, |project, cx| {
23228 project.document_highlights(buffer, position, cx)
23229 }))
23230 }
23231
23232 fn definitions(
23233 &self,
23234 buffer: &Entity<Buffer>,
23235 position: text::Anchor,
23236 kind: GotoDefinitionKind,
23237 cx: &mut App,
23238 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23239 Some(self.update(cx, |project, cx| match kind {
23240 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23241 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23242 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23243 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23244 }))
23245 }
23246
23247 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23248 self.update(cx, |project, cx| {
23249 if project
23250 .active_debug_session(cx)
23251 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23252 {
23253 return true;
23254 }
23255
23256 buffer.update(cx, |buffer, cx| {
23257 project.any_language_server_supports_inlay_hints(buffer, cx)
23258 })
23259 })
23260 }
23261
23262 fn inline_values(
23263 &self,
23264 buffer_handle: Entity<Buffer>,
23265 range: Range<text::Anchor>,
23266 cx: &mut App,
23267 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23268 self.update(cx, |project, cx| {
23269 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23270
23271 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23272 })
23273 }
23274
23275 fn inlay_hints(
23276 &self,
23277 buffer_handle: Entity<Buffer>,
23278 range: Range<text::Anchor>,
23279 cx: &mut App,
23280 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23281 Some(self.update(cx, |project, cx| {
23282 project.inlay_hints(buffer_handle, range, cx)
23283 }))
23284 }
23285
23286 fn resolve_inlay_hint(
23287 &self,
23288 hint: InlayHint,
23289 buffer_handle: Entity<Buffer>,
23290 server_id: LanguageServerId,
23291 cx: &mut App,
23292 ) -> Option<Task<anyhow::Result<InlayHint>>> {
23293 Some(self.update(cx, |project, cx| {
23294 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
23295 }))
23296 }
23297
23298 fn range_for_rename(
23299 &self,
23300 buffer: &Entity<Buffer>,
23301 position: text::Anchor,
23302 cx: &mut App,
23303 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23304 Some(self.update(cx, |project, cx| {
23305 let buffer = buffer.clone();
23306 let task = project.prepare_rename(buffer.clone(), position, cx);
23307 cx.spawn(async move |_, cx| {
23308 Ok(match task.await? {
23309 PrepareRenameResponse::Success(range) => Some(range),
23310 PrepareRenameResponse::InvalidPosition => None,
23311 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23312 // Fallback on using TreeSitter info to determine identifier range
23313 buffer.read_with(cx, |buffer, _| {
23314 let snapshot = buffer.snapshot();
23315 let (range, kind) = snapshot.surrounding_word(position, None);
23316 if kind != Some(CharKind::Word) {
23317 return None;
23318 }
23319 Some(
23320 snapshot.anchor_before(range.start)
23321 ..snapshot.anchor_after(range.end),
23322 )
23323 })?
23324 }
23325 })
23326 })
23327 }))
23328 }
23329
23330 fn perform_rename(
23331 &self,
23332 buffer: &Entity<Buffer>,
23333 position: text::Anchor,
23334 new_name: String,
23335 cx: &mut App,
23336 ) -> Option<Task<Result<ProjectTransaction>>> {
23337 Some(self.update(cx, |project, cx| {
23338 project.perform_rename(buffer.clone(), position, new_name, cx)
23339 }))
23340 }
23341}
23342
23343fn inlay_hint_settings(
23344 location: Anchor,
23345 snapshot: &MultiBufferSnapshot,
23346 cx: &mut Context<Editor>,
23347) -> InlayHintSettings {
23348 let file = snapshot.file_at(location);
23349 let language = snapshot.language_at(location).map(|l| l.name());
23350 language_settings(language, file, cx).inlay_hints
23351}
23352
23353fn consume_contiguous_rows(
23354 contiguous_row_selections: &mut Vec<Selection<Point>>,
23355 selection: &Selection<Point>,
23356 display_map: &DisplaySnapshot,
23357 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23358) -> (MultiBufferRow, MultiBufferRow) {
23359 contiguous_row_selections.push(selection.clone());
23360 let start_row = starting_row(selection, display_map);
23361 let mut end_row = ending_row(selection, display_map);
23362
23363 while let Some(next_selection) = selections.peek() {
23364 if next_selection.start.row <= end_row.0 {
23365 end_row = ending_row(next_selection, display_map);
23366 contiguous_row_selections.push(selections.next().unwrap().clone());
23367 } else {
23368 break;
23369 }
23370 }
23371 (start_row, end_row)
23372}
23373
23374fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23375 if selection.start.column > 0 {
23376 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23377 } else {
23378 MultiBufferRow(selection.start.row)
23379 }
23380}
23381
23382fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23383 if next_selection.end.column > 0 || next_selection.is_empty() {
23384 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23385 } else {
23386 MultiBufferRow(next_selection.end.row)
23387 }
23388}
23389
23390impl EditorSnapshot {
23391 pub fn remote_selections_in_range<'a>(
23392 &'a self,
23393 range: &'a Range<Anchor>,
23394 collaboration_hub: &dyn CollaborationHub,
23395 cx: &'a App,
23396 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23397 let participant_names = collaboration_hub.user_names(cx);
23398 let participant_indices = collaboration_hub.user_participant_indices(cx);
23399 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23400 let collaborators_by_replica_id = collaborators_by_peer_id
23401 .values()
23402 .map(|collaborator| (collaborator.replica_id, collaborator))
23403 .collect::<HashMap<_, _>>();
23404 self.buffer_snapshot()
23405 .selections_in_range(range, false)
23406 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23407 if replica_id == AGENT_REPLICA_ID {
23408 Some(RemoteSelection {
23409 replica_id,
23410 selection,
23411 cursor_shape,
23412 line_mode,
23413 collaborator_id: CollaboratorId::Agent,
23414 user_name: Some("Agent".into()),
23415 color: cx.theme().players().agent(),
23416 })
23417 } else {
23418 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23419 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23420 let user_name = participant_names.get(&collaborator.user_id).cloned();
23421 Some(RemoteSelection {
23422 replica_id,
23423 selection,
23424 cursor_shape,
23425 line_mode,
23426 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23427 user_name,
23428 color: if let Some(index) = participant_index {
23429 cx.theme().players().color_for_participant(index.0)
23430 } else {
23431 cx.theme().players().absent()
23432 },
23433 })
23434 }
23435 })
23436 }
23437
23438 pub fn hunks_for_ranges(
23439 &self,
23440 ranges: impl IntoIterator<Item = Range<Point>>,
23441 ) -> Vec<MultiBufferDiffHunk> {
23442 let mut hunks = Vec::new();
23443 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23444 HashMap::default();
23445 for query_range in ranges {
23446 let query_rows =
23447 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23448 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23449 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23450 ) {
23451 // Include deleted hunks that are adjacent to the query range, because
23452 // otherwise they would be missed.
23453 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23454 if hunk.status().is_deleted() {
23455 intersects_range |= hunk.row_range.start == query_rows.end;
23456 intersects_range |= hunk.row_range.end == query_rows.start;
23457 }
23458 if intersects_range {
23459 if !processed_buffer_rows
23460 .entry(hunk.buffer_id)
23461 .or_default()
23462 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23463 {
23464 continue;
23465 }
23466 hunks.push(hunk);
23467 }
23468 }
23469 }
23470
23471 hunks
23472 }
23473
23474 fn display_diff_hunks_for_rows<'a>(
23475 &'a self,
23476 display_rows: Range<DisplayRow>,
23477 folded_buffers: &'a HashSet<BufferId>,
23478 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23479 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23480 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23481
23482 self.buffer_snapshot()
23483 .diff_hunks_in_range(buffer_start..buffer_end)
23484 .filter_map(|hunk| {
23485 if folded_buffers.contains(&hunk.buffer_id) {
23486 return None;
23487 }
23488
23489 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23490 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23491
23492 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23493 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23494
23495 let display_hunk = if hunk_display_start.column() != 0 {
23496 DisplayDiffHunk::Folded {
23497 display_row: hunk_display_start.row(),
23498 }
23499 } else {
23500 let mut end_row = hunk_display_end.row();
23501 if hunk_display_end.column() > 0 {
23502 end_row.0 += 1;
23503 }
23504 let is_created_file = hunk.is_created_file();
23505 DisplayDiffHunk::Unfolded {
23506 status: hunk.status(),
23507 diff_base_byte_range: hunk.diff_base_byte_range,
23508 display_row_range: hunk_display_start.row()..end_row,
23509 multi_buffer_range: Anchor::range_in_buffer(
23510 hunk.excerpt_id,
23511 hunk.buffer_id,
23512 hunk.buffer_range,
23513 ),
23514 is_created_file,
23515 }
23516 };
23517
23518 Some(display_hunk)
23519 })
23520 }
23521
23522 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23523 self.display_snapshot
23524 .buffer_snapshot()
23525 .language_at(position)
23526 }
23527
23528 pub fn is_focused(&self) -> bool {
23529 self.is_focused
23530 }
23531
23532 pub fn placeholder_text(&self) -> Option<String> {
23533 self.placeholder_display_snapshot
23534 .as_ref()
23535 .map(|display_map| display_map.text())
23536 }
23537
23538 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23539 self.scroll_anchor.scroll_position(&self.display_snapshot)
23540 }
23541
23542 fn gutter_dimensions(
23543 &self,
23544 font_id: FontId,
23545 font_size: Pixels,
23546 max_line_number_width: Pixels,
23547 cx: &App,
23548 ) -> Option<GutterDimensions> {
23549 if !self.show_gutter {
23550 return None;
23551 }
23552
23553 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23554 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23555
23556 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23557 matches!(
23558 ProjectSettings::get_global(cx).git.git_gutter,
23559 GitGutterSetting::TrackedFiles
23560 )
23561 });
23562 let gutter_settings = EditorSettings::get_global(cx).gutter;
23563 let show_line_numbers = self
23564 .show_line_numbers
23565 .unwrap_or(gutter_settings.line_numbers);
23566 let line_gutter_width = if show_line_numbers {
23567 // Avoid flicker-like gutter resizes when the line number gains another digit by
23568 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23569 let min_width_for_number_on_gutter =
23570 ch_advance * gutter_settings.min_line_number_digits as f32;
23571 max_line_number_width.max(min_width_for_number_on_gutter)
23572 } else {
23573 0.0.into()
23574 };
23575
23576 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23577 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23578
23579 let git_blame_entries_width =
23580 self.git_blame_gutter_max_author_length
23581 .map(|max_author_length| {
23582 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23583 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23584
23585 /// The number of characters to dedicate to gaps and margins.
23586 const SPACING_WIDTH: usize = 4;
23587
23588 let max_char_count = max_author_length.min(renderer.max_author_length())
23589 + ::git::SHORT_SHA_LENGTH
23590 + MAX_RELATIVE_TIMESTAMP.len()
23591 + SPACING_WIDTH;
23592
23593 ch_advance * max_char_count
23594 });
23595
23596 let is_singleton = self.buffer_snapshot().is_singleton();
23597
23598 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23599 left_padding += if !is_singleton {
23600 ch_width * 4.0
23601 } else if show_runnables || show_breakpoints {
23602 ch_width * 3.0
23603 } else if show_git_gutter && show_line_numbers {
23604 ch_width * 2.0
23605 } else if show_git_gutter || show_line_numbers {
23606 ch_width
23607 } else {
23608 px(0.)
23609 };
23610
23611 let shows_folds = is_singleton && gutter_settings.folds;
23612
23613 let right_padding = if shows_folds && show_line_numbers {
23614 ch_width * 4.0
23615 } else if shows_folds || (!is_singleton && show_line_numbers) {
23616 ch_width * 3.0
23617 } else if show_line_numbers {
23618 ch_width
23619 } else {
23620 px(0.)
23621 };
23622
23623 Some(GutterDimensions {
23624 left_padding,
23625 right_padding,
23626 width: line_gutter_width + left_padding + right_padding,
23627 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23628 git_blame_entries_width,
23629 })
23630 }
23631
23632 pub fn render_crease_toggle(
23633 &self,
23634 buffer_row: MultiBufferRow,
23635 row_contains_cursor: bool,
23636 editor: Entity<Editor>,
23637 window: &mut Window,
23638 cx: &mut App,
23639 ) -> Option<AnyElement> {
23640 let folded = self.is_line_folded(buffer_row);
23641 let mut is_foldable = false;
23642
23643 if let Some(crease) = self
23644 .crease_snapshot
23645 .query_row(buffer_row, self.buffer_snapshot())
23646 {
23647 is_foldable = true;
23648 match crease {
23649 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23650 if let Some(render_toggle) = render_toggle {
23651 let toggle_callback =
23652 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23653 if folded {
23654 editor.update(cx, |editor, cx| {
23655 editor.fold_at(buffer_row, window, cx)
23656 });
23657 } else {
23658 editor.update(cx, |editor, cx| {
23659 editor.unfold_at(buffer_row, window, cx)
23660 });
23661 }
23662 });
23663 return Some((render_toggle)(
23664 buffer_row,
23665 folded,
23666 toggle_callback,
23667 window,
23668 cx,
23669 ));
23670 }
23671 }
23672 }
23673 }
23674
23675 is_foldable |= self.starts_indent(buffer_row);
23676
23677 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23678 Some(
23679 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23680 .toggle_state(folded)
23681 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23682 if folded {
23683 this.unfold_at(buffer_row, window, cx);
23684 } else {
23685 this.fold_at(buffer_row, window, cx);
23686 }
23687 }))
23688 .into_any_element(),
23689 )
23690 } else {
23691 None
23692 }
23693 }
23694
23695 pub fn render_crease_trailer(
23696 &self,
23697 buffer_row: MultiBufferRow,
23698 window: &mut Window,
23699 cx: &mut App,
23700 ) -> Option<AnyElement> {
23701 let folded = self.is_line_folded(buffer_row);
23702 if let Crease::Inline { render_trailer, .. } = self
23703 .crease_snapshot
23704 .query_row(buffer_row, self.buffer_snapshot())?
23705 {
23706 let render_trailer = render_trailer.as_ref()?;
23707 Some(render_trailer(buffer_row, folded, window, cx))
23708 } else {
23709 None
23710 }
23711 }
23712}
23713
23714impl Deref for EditorSnapshot {
23715 type Target = DisplaySnapshot;
23716
23717 fn deref(&self) -> &Self::Target {
23718 &self.display_snapshot
23719 }
23720}
23721
23722#[derive(Clone, Debug, PartialEq, Eq)]
23723pub enum EditorEvent {
23724 InputIgnored {
23725 text: Arc<str>,
23726 },
23727 InputHandled {
23728 utf16_range_to_replace: Option<Range<isize>>,
23729 text: Arc<str>,
23730 },
23731 ExcerptsAdded {
23732 buffer: Entity<Buffer>,
23733 predecessor: ExcerptId,
23734 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23735 },
23736 ExcerptsRemoved {
23737 ids: Vec<ExcerptId>,
23738 removed_buffer_ids: Vec<BufferId>,
23739 },
23740 BufferFoldToggled {
23741 ids: Vec<ExcerptId>,
23742 folded: bool,
23743 },
23744 ExcerptsEdited {
23745 ids: Vec<ExcerptId>,
23746 },
23747 ExcerptsExpanded {
23748 ids: Vec<ExcerptId>,
23749 },
23750 BufferEdited,
23751 Edited {
23752 transaction_id: clock::Lamport,
23753 },
23754 Reparsed(BufferId),
23755 Focused,
23756 FocusedIn,
23757 Blurred,
23758 DirtyChanged,
23759 Saved,
23760 TitleChanged,
23761 SelectionsChanged {
23762 local: bool,
23763 },
23764 ScrollPositionChanged {
23765 local: bool,
23766 autoscroll: bool,
23767 },
23768 TransactionUndone {
23769 transaction_id: clock::Lamport,
23770 },
23771 TransactionBegun {
23772 transaction_id: clock::Lamport,
23773 },
23774 CursorShapeChanged,
23775 BreadcrumbsChanged,
23776 PushedToNavHistory {
23777 anchor: Anchor,
23778 is_deactivate: bool,
23779 },
23780}
23781
23782impl EventEmitter<EditorEvent> for Editor {}
23783
23784impl Focusable for Editor {
23785 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23786 self.focus_handle.clone()
23787 }
23788}
23789
23790impl Render for Editor {
23791 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23792 let settings = ThemeSettings::get_global(cx);
23793
23794 let mut text_style = match self.mode {
23795 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23796 color: cx.theme().colors().editor_foreground,
23797 font_family: settings.ui_font.family.clone(),
23798 font_features: settings.ui_font.features.clone(),
23799 font_fallbacks: settings.ui_font.fallbacks.clone(),
23800 font_size: rems(0.875).into(),
23801 font_weight: settings.ui_font.weight,
23802 line_height: relative(settings.buffer_line_height.value()),
23803 ..Default::default()
23804 },
23805 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23806 color: cx.theme().colors().editor_foreground,
23807 font_family: settings.buffer_font.family.clone(),
23808 font_features: settings.buffer_font.features.clone(),
23809 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23810 font_size: settings.buffer_font_size(cx).into(),
23811 font_weight: settings.buffer_font.weight,
23812 line_height: relative(settings.buffer_line_height.value()),
23813 ..Default::default()
23814 },
23815 };
23816 if let Some(text_style_refinement) = &self.text_style_refinement {
23817 text_style.refine(text_style_refinement)
23818 }
23819
23820 let background = match self.mode {
23821 EditorMode::SingleLine => cx.theme().system().transparent,
23822 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23823 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23824 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23825 };
23826
23827 EditorElement::new(
23828 &cx.entity(),
23829 EditorStyle {
23830 background,
23831 border: cx.theme().colors().border,
23832 local_player: cx.theme().players().local(),
23833 text: text_style,
23834 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23835 syntax: cx.theme().syntax().clone(),
23836 status: cx.theme().status().clone(),
23837 inlay_hints_style: make_inlay_hints_style(cx),
23838 edit_prediction_styles: make_suggestion_styles(cx),
23839 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23840 show_underlines: self.diagnostics_enabled(),
23841 },
23842 )
23843 }
23844}
23845
23846impl EntityInputHandler for Editor {
23847 fn text_for_range(
23848 &mut self,
23849 range_utf16: Range<usize>,
23850 adjusted_range: &mut Option<Range<usize>>,
23851 _: &mut Window,
23852 cx: &mut Context<Self>,
23853 ) -> Option<String> {
23854 let snapshot = self.buffer.read(cx).read(cx);
23855 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23856 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23857 if (start.0..end.0) != range_utf16 {
23858 adjusted_range.replace(start.0..end.0);
23859 }
23860 Some(snapshot.text_for_range(start..end).collect())
23861 }
23862
23863 fn selected_text_range(
23864 &mut self,
23865 ignore_disabled_input: bool,
23866 _: &mut Window,
23867 cx: &mut Context<Self>,
23868 ) -> Option<UTF16Selection> {
23869 // Prevent the IME menu from appearing when holding down an alphabetic key
23870 // while input is disabled.
23871 if !ignore_disabled_input && !self.input_enabled {
23872 return None;
23873 }
23874
23875 let selection = self.selections.newest::<OffsetUtf16>(cx);
23876 let range = selection.range();
23877
23878 Some(UTF16Selection {
23879 range: range.start.0..range.end.0,
23880 reversed: selection.reversed,
23881 })
23882 }
23883
23884 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23885 let snapshot = self.buffer.read(cx).read(cx);
23886 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23887 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23888 }
23889
23890 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23891 self.clear_highlights::<InputComposition>(cx);
23892 self.ime_transaction.take();
23893 }
23894
23895 fn replace_text_in_range(
23896 &mut self,
23897 range_utf16: Option<Range<usize>>,
23898 text: &str,
23899 window: &mut Window,
23900 cx: &mut Context<Self>,
23901 ) {
23902 if !self.input_enabled {
23903 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23904 return;
23905 }
23906
23907 self.transact(window, cx, |this, window, cx| {
23908 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23909 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23910 Some(this.selection_replacement_ranges(range_utf16, cx))
23911 } else {
23912 this.marked_text_ranges(cx)
23913 };
23914
23915 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23916 let newest_selection_id = this.selections.newest_anchor().id;
23917 this.selections
23918 .all::<OffsetUtf16>(cx)
23919 .iter()
23920 .zip(ranges_to_replace.iter())
23921 .find_map(|(selection, range)| {
23922 if selection.id == newest_selection_id {
23923 Some(
23924 (range.start.0 as isize - selection.head().0 as isize)
23925 ..(range.end.0 as isize - selection.head().0 as isize),
23926 )
23927 } else {
23928 None
23929 }
23930 })
23931 });
23932
23933 cx.emit(EditorEvent::InputHandled {
23934 utf16_range_to_replace: range_to_replace,
23935 text: text.into(),
23936 });
23937
23938 if let Some(new_selected_ranges) = new_selected_ranges {
23939 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23940 selections.select_ranges(new_selected_ranges)
23941 });
23942 this.backspace(&Default::default(), window, cx);
23943 }
23944
23945 this.handle_input(text, window, cx);
23946 });
23947
23948 if let Some(transaction) = self.ime_transaction {
23949 self.buffer.update(cx, |buffer, cx| {
23950 buffer.group_until_transaction(transaction, cx);
23951 });
23952 }
23953
23954 self.unmark_text(window, cx);
23955 }
23956
23957 fn replace_and_mark_text_in_range(
23958 &mut self,
23959 range_utf16: Option<Range<usize>>,
23960 text: &str,
23961 new_selected_range_utf16: Option<Range<usize>>,
23962 window: &mut Window,
23963 cx: &mut Context<Self>,
23964 ) {
23965 if !self.input_enabled {
23966 return;
23967 }
23968
23969 let transaction = self.transact(window, cx, |this, window, cx| {
23970 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23971 let snapshot = this.buffer.read(cx).read(cx);
23972 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23973 for marked_range in &mut marked_ranges {
23974 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23975 marked_range.start.0 += relative_range_utf16.start;
23976 marked_range.start =
23977 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23978 marked_range.end =
23979 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23980 }
23981 }
23982 Some(marked_ranges)
23983 } else if let Some(range_utf16) = range_utf16 {
23984 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23985 Some(this.selection_replacement_ranges(range_utf16, cx))
23986 } else {
23987 None
23988 };
23989
23990 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23991 let newest_selection_id = this.selections.newest_anchor().id;
23992 this.selections
23993 .all::<OffsetUtf16>(cx)
23994 .iter()
23995 .zip(ranges_to_replace.iter())
23996 .find_map(|(selection, range)| {
23997 if selection.id == newest_selection_id {
23998 Some(
23999 (range.start.0 as isize - selection.head().0 as isize)
24000 ..(range.end.0 as isize - selection.head().0 as isize),
24001 )
24002 } else {
24003 None
24004 }
24005 })
24006 });
24007
24008 cx.emit(EditorEvent::InputHandled {
24009 utf16_range_to_replace: range_to_replace,
24010 text: text.into(),
24011 });
24012
24013 if let Some(ranges) = ranges_to_replace {
24014 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24015 s.select_ranges(ranges)
24016 });
24017 }
24018
24019 let marked_ranges = {
24020 let snapshot = this.buffer.read(cx).read(cx);
24021 this.selections
24022 .disjoint_anchors_arc()
24023 .iter()
24024 .map(|selection| {
24025 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24026 })
24027 .collect::<Vec<_>>()
24028 };
24029
24030 if text.is_empty() {
24031 this.unmark_text(window, cx);
24032 } else {
24033 this.highlight_text::<InputComposition>(
24034 marked_ranges.clone(),
24035 HighlightStyle {
24036 underline: Some(UnderlineStyle {
24037 thickness: px(1.),
24038 color: None,
24039 wavy: false,
24040 }),
24041 ..Default::default()
24042 },
24043 cx,
24044 );
24045 }
24046
24047 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24048 let use_autoclose = this.use_autoclose;
24049 let use_auto_surround = this.use_auto_surround;
24050 this.set_use_autoclose(false);
24051 this.set_use_auto_surround(false);
24052 this.handle_input(text, window, cx);
24053 this.set_use_autoclose(use_autoclose);
24054 this.set_use_auto_surround(use_auto_surround);
24055
24056 if let Some(new_selected_range) = new_selected_range_utf16 {
24057 let snapshot = this.buffer.read(cx).read(cx);
24058 let new_selected_ranges = marked_ranges
24059 .into_iter()
24060 .map(|marked_range| {
24061 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24062 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
24063 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
24064 snapshot.clip_offset_utf16(new_start, Bias::Left)
24065 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24066 })
24067 .collect::<Vec<_>>();
24068
24069 drop(snapshot);
24070 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24071 selections.select_ranges(new_selected_ranges)
24072 });
24073 }
24074 });
24075
24076 self.ime_transaction = self.ime_transaction.or(transaction);
24077 if let Some(transaction) = self.ime_transaction {
24078 self.buffer.update(cx, |buffer, cx| {
24079 buffer.group_until_transaction(transaction, cx);
24080 });
24081 }
24082
24083 if self.text_highlights::<InputComposition>(cx).is_none() {
24084 self.ime_transaction.take();
24085 }
24086 }
24087
24088 fn bounds_for_range(
24089 &mut self,
24090 range_utf16: Range<usize>,
24091 element_bounds: gpui::Bounds<Pixels>,
24092 window: &mut Window,
24093 cx: &mut Context<Self>,
24094 ) -> Option<gpui::Bounds<Pixels>> {
24095 let text_layout_details = self.text_layout_details(window);
24096 let CharacterDimensions {
24097 em_width,
24098 em_advance,
24099 line_height,
24100 } = self.character_dimensions(window);
24101
24102 let snapshot = self.snapshot(window, cx);
24103 let scroll_position = snapshot.scroll_position();
24104 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24105
24106 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
24107 let x = Pixels::from(
24108 ScrollOffset::from(
24109 snapshot.x_for_display_point(start, &text_layout_details)
24110 + self.gutter_dimensions.full_width(),
24111 ) - scroll_left,
24112 );
24113 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24114
24115 Some(Bounds {
24116 origin: element_bounds.origin + point(x, y),
24117 size: size(em_width, line_height),
24118 })
24119 }
24120
24121 fn character_index_for_point(
24122 &mut self,
24123 point: gpui::Point<Pixels>,
24124 _window: &mut Window,
24125 _cx: &mut Context<Self>,
24126 ) -> Option<usize> {
24127 let position_map = self.last_position_map.as_ref()?;
24128 if !position_map.text_hitbox.contains(&point) {
24129 return None;
24130 }
24131 let display_point = position_map.point_for_position(point).previous_valid;
24132 let anchor = position_map
24133 .snapshot
24134 .display_point_to_anchor(display_point, Bias::Left);
24135 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24136 Some(utf16_offset.0)
24137 }
24138}
24139
24140trait SelectionExt {
24141 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24142 fn spanned_rows(
24143 &self,
24144 include_end_if_at_line_start: bool,
24145 map: &DisplaySnapshot,
24146 ) -> Range<MultiBufferRow>;
24147}
24148
24149impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24150 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24151 let start = self
24152 .start
24153 .to_point(map.buffer_snapshot())
24154 .to_display_point(map);
24155 let end = self
24156 .end
24157 .to_point(map.buffer_snapshot())
24158 .to_display_point(map);
24159 if self.reversed {
24160 end..start
24161 } else {
24162 start..end
24163 }
24164 }
24165
24166 fn spanned_rows(
24167 &self,
24168 include_end_if_at_line_start: bool,
24169 map: &DisplaySnapshot,
24170 ) -> Range<MultiBufferRow> {
24171 let start = self.start.to_point(map.buffer_snapshot());
24172 let mut end = self.end.to_point(map.buffer_snapshot());
24173 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24174 end.row -= 1;
24175 }
24176
24177 let buffer_start = map.prev_line_boundary(start).0;
24178 let buffer_end = map.next_line_boundary(end).0;
24179 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24180 }
24181}
24182
24183impl<T: InvalidationRegion> InvalidationStack<T> {
24184 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24185 where
24186 S: Clone + ToOffset,
24187 {
24188 while let Some(region) = self.last() {
24189 let all_selections_inside_invalidation_ranges =
24190 if selections.len() == region.ranges().len() {
24191 selections
24192 .iter()
24193 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24194 .all(|(selection, invalidation_range)| {
24195 let head = selection.head().to_offset(buffer);
24196 invalidation_range.start <= head && invalidation_range.end >= head
24197 })
24198 } else {
24199 false
24200 };
24201
24202 if all_selections_inside_invalidation_ranges {
24203 break;
24204 } else {
24205 self.pop();
24206 }
24207 }
24208 }
24209}
24210
24211impl<T> Default for InvalidationStack<T> {
24212 fn default() -> Self {
24213 Self(Default::default())
24214 }
24215}
24216
24217impl<T> Deref for InvalidationStack<T> {
24218 type Target = Vec<T>;
24219
24220 fn deref(&self) -> &Self::Target {
24221 &self.0
24222 }
24223}
24224
24225impl<T> DerefMut for InvalidationStack<T> {
24226 fn deref_mut(&mut self) -> &mut Self::Target {
24227 &mut self.0
24228 }
24229}
24230
24231impl InvalidationRegion for SnippetState {
24232 fn ranges(&self) -> &[Range<Anchor>] {
24233 &self.ranges[self.active_index]
24234 }
24235}
24236
24237fn edit_prediction_edit_text(
24238 current_snapshot: &BufferSnapshot,
24239 edits: &[(Range<Anchor>, String)],
24240 edit_preview: &EditPreview,
24241 include_deletions: bool,
24242 cx: &App,
24243) -> HighlightedText {
24244 let edits = edits
24245 .iter()
24246 .map(|(anchor, text)| {
24247 (
24248 anchor.start.text_anchor..anchor.end.text_anchor,
24249 text.clone(),
24250 )
24251 })
24252 .collect::<Vec<_>>();
24253
24254 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24255}
24256
24257fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
24258 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24259 // Just show the raw edit text with basic styling
24260 let mut text = String::new();
24261 let mut highlights = Vec::new();
24262
24263 let insertion_highlight_style = HighlightStyle {
24264 color: Some(cx.theme().colors().text),
24265 ..Default::default()
24266 };
24267
24268 for (_, edit_text) in edits {
24269 let start_offset = text.len();
24270 text.push_str(edit_text);
24271 let end_offset = text.len();
24272
24273 if start_offset < end_offset {
24274 highlights.push((start_offset..end_offset, insertion_highlight_style));
24275 }
24276 }
24277
24278 HighlightedText {
24279 text: text.into(),
24280 highlights,
24281 }
24282}
24283
24284pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24285 match severity {
24286 lsp::DiagnosticSeverity::ERROR => colors.error,
24287 lsp::DiagnosticSeverity::WARNING => colors.warning,
24288 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24289 lsp::DiagnosticSeverity::HINT => colors.info,
24290 _ => colors.ignored,
24291 }
24292}
24293
24294pub fn styled_runs_for_code_label<'a>(
24295 label: &'a CodeLabel,
24296 syntax_theme: &'a theme::SyntaxTheme,
24297) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24298 let fade_out = HighlightStyle {
24299 fade_out: Some(0.35),
24300 ..Default::default()
24301 };
24302
24303 let mut prev_end = label.filter_range.end;
24304 label
24305 .runs
24306 .iter()
24307 .enumerate()
24308 .flat_map(move |(ix, (range, highlight_id))| {
24309 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24310 style
24311 } else {
24312 return Default::default();
24313 };
24314 let muted_style = style.highlight(fade_out);
24315
24316 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24317 if range.start >= label.filter_range.end {
24318 if range.start > prev_end {
24319 runs.push((prev_end..range.start, fade_out));
24320 }
24321 runs.push((range.clone(), muted_style));
24322 } else if range.end <= label.filter_range.end {
24323 runs.push((range.clone(), style));
24324 } else {
24325 runs.push((range.start..label.filter_range.end, style));
24326 runs.push((label.filter_range.end..range.end, muted_style));
24327 }
24328 prev_end = cmp::max(prev_end, range.end);
24329
24330 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24331 runs.push((prev_end..label.text.len(), fade_out));
24332 }
24333
24334 runs
24335 })
24336}
24337
24338pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24339 let mut prev_index = 0;
24340 let mut prev_codepoint: Option<char> = None;
24341 text.char_indices()
24342 .chain([(text.len(), '\0')])
24343 .filter_map(move |(index, codepoint)| {
24344 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24345 let is_boundary = index == text.len()
24346 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24347 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24348 if is_boundary {
24349 let chunk = &text[prev_index..index];
24350 prev_index = index;
24351 Some(chunk)
24352 } else {
24353 None
24354 }
24355 })
24356}
24357
24358pub trait RangeToAnchorExt: Sized {
24359 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24360
24361 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24362 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24363 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24364 }
24365}
24366
24367impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24368 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24369 let start_offset = self.start.to_offset(snapshot);
24370 let end_offset = self.end.to_offset(snapshot);
24371 if start_offset == end_offset {
24372 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24373 } else {
24374 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24375 }
24376 }
24377}
24378
24379pub trait RowExt {
24380 fn as_f64(&self) -> f64;
24381
24382 fn next_row(&self) -> Self;
24383
24384 fn previous_row(&self) -> Self;
24385
24386 fn minus(&self, other: Self) -> u32;
24387}
24388
24389impl RowExt for DisplayRow {
24390 fn as_f64(&self) -> f64 {
24391 self.0 as _
24392 }
24393
24394 fn next_row(&self) -> Self {
24395 Self(self.0 + 1)
24396 }
24397
24398 fn previous_row(&self) -> Self {
24399 Self(self.0.saturating_sub(1))
24400 }
24401
24402 fn minus(&self, other: Self) -> u32 {
24403 self.0 - other.0
24404 }
24405}
24406
24407impl RowExt for MultiBufferRow {
24408 fn as_f64(&self) -> f64 {
24409 self.0 as _
24410 }
24411
24412 fn next_row(&self) -> Self {
24413 Self(self.0 + 1)
24414 }
24415
24416 fn previous_row(&self) -> Self {
24417 Self(self.0.saturating_sub(1))
24418 }
24419
24420 fn minus(&self, other: Self) -> u32 {
24421 self.0 - other.0
24422 }
24423}
24424
24425trait RowRangeExt {
24426 type Row;
24427
24428 fn len(&self) -> usize;
24429
24430 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24431}
24432
24433impl RowRangeExt for Range<MultiBufferRow> {
24434 type Row = MultiBufferRow;
24435
24436 fn len(&self) -> usize {
24437 (self.end.0 - self.start.0) as usize
24438 }
24439
24440 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24441 (self.start.0..self.end.0).map(MultiBufferRow)
24442 }
24443}
24444
24445impl RowRangeExt for Range<DisplayRow> {
24446 type Row = DisplayRow;
24447
24448 fn len(&self) -> usize {
24449 (self.end.0 - self.start.0) as usize
24450 }
24451
24452 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24453 (self.start.0..self.end.0).map(DisplayRow)
24454 }
24455}
24456
24457/// If select range has more than one line, we
24458/// just point the cursor to range.start.
24459fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24460 if range.start.row == range.end.row {
24461 range
24462 } else {
24463 range.start..range.start
24464 }
24465}
24466pub struct KillRing(ClipboardItem);
24467impl Global for KillRing {}
24468
24469const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24470
24471enum BreakpointPromptEditAction {
24472 Log,
24473 Condition,
24474 HitCondition,
24475}
24476
24477struct BreakpointPromptEditor {
24478 pub(crate) prompt: Entity<Editor>,
24479 editor: WeakEntity<Editor>,
24480 breakpoint_anchor: Anchor,
24481 breakpoint: Breakpoint,
24482 edit_action: BreakpointPromptEditAction,
24483 block_ids: HashSet<CustomBlockId>,
24484 editor_margins: Arc<Mutex<EditorMargins>>,
24485 _subscriptions: Vec<Subscription>,
24486}
24487
24488impl BreakpointPromptEditor {
24489 const MAX_LINES: u8 = 4;
24490
24491 fn new(
24492 editor: WeakEntity<Editor>,
24493 breakpoint_anchor: Anchor,
24494 breakpoint: Breakpoint,
24495 edit_action: BreakpointPromptEditAction,
24496 window: &mut Window,
24497 cx: &mut Context<Self>,
24498 ) -> Self {
24499 let base_text = match edit_action {
24500 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24501 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24502 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24503 }
24504 .map(|msg| msg.to_string())
24505 .unwrap_or_default();
24506
24507 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24508 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24509
24510 let prompt = cx.new(|cx| {
24511 let mut prompt = Editor::new(
24512 EditorMode::AutoHeight {
24513 min_lines: 1,
24514 max_lines: Some(Self::MAX_LINES as usize),
24515 },
24516 buffer,
24517 None,
24518 window,
24519 cx,
24520 );
24521 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24522 prompt.set_show_cursor_when_unfocused(false, cx);
24523 prompt.set_placeholder_text(
24524 match edit_action {
24525 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24526 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24527 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24528 },
24529 window,
24530 cx,
24531 );
24532
24533 prompt
24534 });
24535
24536 Self {
24537 prompt,
24538 editor,
24539 breakpoint_anchor,
24540 breakpoint,
24541 edit_action,
24542 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24543 block_ids: Default::default(),
24544 _subscriptions: vec![],
24545 }
24546 }
24547
24548 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24549 self.block_ids.extend(block_ids)
24550 }
24551
24552 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24553 if let Some(editor) = self.editor.upgrade() {
24554 let message = self
24555 .prompt
24556 .read(cx)
24557 .buffer
24558 .read(cx)
24559 .as_singleton()
24560 .expect("A multi buffer in breakpoint prompt isn't possible")
24561 .read(cx)
24562 .as_rope()
24563 .to_string();
24564
24565 editor.update(cx, |editor, cx| {
24566 editor.edit_breakpoint_at_anchor(
24567 self.breakpoint_anchor,
24568 self.breakpoint.clone(),
24569 match self.edit_action {
24570 BreakpointPromptEditAction::Log => {
24571 BreakpointEditAction::EditLogMessage(message.into())
24572 }
24573 BreakpointPromptEditAction::Condition => {
24574 BreakpointEditAction::EditCondition(message.into())
24575 }
24576 BreakpointPromptEditAction::HitCondition => {
24577 BreakpointEditAction::EditHitCondition(message.into())
24578 }
24579 },
24580 cx,
24581 );
24582
24583 editor.remove_blocks(self.block_ids.clone(), None, cx);
24584 cx.focus_self(window);
24585 });
24586 }
24587 }
24588
24589 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24590 self.editor
24591 .update(cx, |editor, cx| {
24592 editor.remove_blocks(self.block_ids.clone(), None, cx);
24593 window.focus(&editor.focus_handle);
24594 })
24595 .log_err();
24596 }
24597
24598 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24599 let settings = ThemeSettings::get_global(cx);
24600 let text_style = TextStyle {
24601 color: if self.prompt.read(cx).read_only(cx) {
24602 cx.theme().colors().text_disabled
24603 } else {
24604 cx.theme().colors().text
24605 },
24606 font_family: settings.buffer_font.family.clone(),
24607 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24608 font_size: settings.buffer_font_size(cx).into(),
24609 font_weight: settings.buffer_font.weight,
24610 line_height: relative(settings.buffer_line_height.value()),
24611 ..Default::default()
24612 };
24613 EditorElement::new(
24614 &self.prompt,
24615 EditorStyle {
24616 background: cx.theme().colors().editor_background,
24617 local_player: cx.theme().players().local(),
24618 text: text_style,
24619 ..Default::default()
24620 },
24621 )
24622 }
24623}
24624
24625impl Render for BreakpointPromptEditor {
24626 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24627 let editor_margins = *self.editor_margins.lock();
24628 let gutter_dimensions = editor_margins.gutter;
24629 h_flex()
24630 .key_context("Editor")
24631 .bg(cx.theme().colors().editor_background)
24632 .border_y_1()
24633 .border_color(cx.theme().status().info_border)
24634 .size_full()
24635 .py(window.line_height() / 2.5)
24636 .on_action(cx.listener(Self::confirm))
24637 .on_action(cx.listener(Self::cancel))
24638 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24639 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24640 }
24641}
24642
24643impl Focusable for BreakpointPromptEditor {
24644 fn focus_handle(&self, cx: &App) -> FocusHandle {
24645 self.prompt.focus_handle(cx)
24646 }
24647}
24648
24649fn all_edits_insertions_or_deletions(
24650 edits: &Vec<(Range<Anchor>, String)>,
24651 snapshot: &MultiBufferSnapshot,
24652) -> bool {
24653 let mut all_insertions = true;
24654 let mut all_deletions = true;
24655
24656 for (range, new_text) in edits.iter() {
24657 let range_is_empty = range.to_offset(snapshot).is_empty();
24658 let text_is_empty = new_text.is_empty();
24659
24660 if range_is_empty != text_is_empty {
24661 if range_is_empty {
24662 all_deletions = false;
24663 } else {
24664 all_insertions = false;
24665 }
24666 } else {
24667 return false;
24668 }
24669
24670 if !all_insertions && !all_deletions {
24671 return false;
24672 }
24673 }
24674 all_insertions || all_deletions
24675}
24676
24677struct MissingEditPredictionKeybindingTooltip;
24678
24679impl Render for MissingEditPredictionKeybindingTooltip {
24680 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24681 ui::tooltip_container(cx, |container, cx| {
24682 container
24683 .flex_shrink_0()
24684 .max_w_80()
24685 .min_h(rems_from_px(124.))
24686 .justify_between()
24687 .child(
24688 v_flex()
24689 .flex_1()
24690 .text_ui_sm(cx)
24691 .child(Label::new("Conflict with Accept Keybinding"))
24692 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24693 )
24694 .child(
24695 h_flex()
24696 .pb_1()
24697 .gap_1()
24698 .items_end()
24699 .w_full()
24700 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24701 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
24702 }))
24703 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24704 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24705 })),
24706 )
24707 })
24708 }
24709}
24710
24711#[derive(Debug, Clone, Copy, PartialEq)]
24712pub struct LineHighlight {
24713 pub background: Background,
24714 pub border: Option<gpui::Hsla>,
24715 pub include_gutter: bool,
24716 pub type_id: Option<TypeId>,
24717}
24718
24719struct LineManipulationResult {
24720 pub new_text: String,
24721 pub line_count_before: usize,
24722 pub line_count_after: usize,
24723}
24724
24725fn render_diff_hunk_controls(
24726 row: u32,
24727 status: &DiffHunkStatus,
24728 hunk_range: Range<Anchor>,
24729 is_created_file: bool,
24730 line_height: Pixels,
24731 editor: &Entity<Editor>,
24732 _window: &mut Window,
24733 cx: &mut App,
24734) -> AnyElement {
24735 h_flex()
24736 .h(line_height)
24737 .mr_1()
24738 .gap_1()
24739 .px_0p5()
24740 .pb_1()
24741 .border_x_1()
24742 .border_b_1()
24743 .border_color(cx.theme().colors().border_variant)
24744 .rounded_b_lg()
24745 .bg(cx.theme().colors().editor_background)
24746 .gap_1()
24747 .block_mouse_except_scroll()
24748 .shadow_md()
24749 .child(if status.has_secondary_hunk() {
24750 Button::new(("stage", row as u64), "Stage")
24751 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24752 .tooltip({
24753 let focus_handle = editor.focus_handle(cx);
24754 move |window, cx| {
24755 Tooltip::for_action_in(
24756 "Stage Hunk",
24757 &::git::ToggleStaged,
24758 &focus_handle,
24759 window,
24760 cx,
24761 )
24762 }
24763 })
24764 .on_click({
24765 let editor = editor.clone();
24766 move |_event, _window, cx| {
24767 editor.update(cx, |editor, cx| {
24768 editor.stage_or_unstage_diff_hunks(
24769 true,
24770 vec![hunk_range.start..hunk_range.start],
24771 cx,
24772 );
24773 });
24774 }
24775 })
24776 } else {
24777 Button::new(("unstage", row as u64), "Unstage")
24778 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24779 .tooltip({
24780 let focus_handle = editor.focus_handle(cx);
24781 move |window, cx| {
24782 Tooltip::for_action_in(
24783 "Unstage Hunk",
24784 &::git::ToggleStaged,
24785 &focus_handle,
24786 window,
24787 cx,
24788 )
24789 }
24790 })
24791 .on_click({
24792 let editor = editor.clone();
24793 move |_event, _window, cx| {
24794 editor.update(cx, |editor, cx| {
24795 editor.stage_or_unstage_diff_hunks(
24796 false,
24797 vec![hunk_range.start..hunk_range.start],
24798 cx,
24799 );
24800 });
24801 }
24802 })
24803 })
24804 .child(
24805 Button::new(("restore", row as u64), "Restore")
24806 .tooltip({
24807 let focus_handle = editor.focus_handle(cx);
24808 move |window, cx| {
24809 Tooltip::for_action_in(
24810 "Restore Hunk",
24811 &::git::Restore,
24812 &focus_handle,
24813 window,
24814 cx,
24815 )
24816 }
24817 })
24818 .on_click({
24819 let editor = editor.clone();
24820 move |_event, window, cx| {
24821 editor.update(cx, |editor, cx| {
24822 let snapshot = editor.snapshot(window, cx);
24823 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
24824 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24825 });
24826 }
24827 })
24828 .disabled(is_created_file),
24829 )
24830 .when(
24831 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24832 |el| {
24833 el.child(
24834 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24835 .shape(IconButtonShape::Square)
24836 .icon_size(IconSize::Small)
24837 // .disabled(!has_multiple_hunks)
24838 .tooltip({
24839 let focus_handle = editor.focus_handle(cx);
24840 move |window, cx| {
24841 Tooltip::for_action_in(
24842 "Next Hunk",
24843 &GoToHunk,
24844 &focus_handle,
24845 window,
24846 cx,
24847 )
24848 }
24849 })
24850 .on_click({
24851 let editor = editor.clone();
24852 move |_event, window, cx| {
24853 editor.update(cx, |editor, cx| {
24854 let snapshot = editor.snapshot(window, cx);
24855 let position =
24856 hunk_range.end.to_point(&snapshot.buffer_snapshot());
24857 editor.go_to_hunk_before_or_after_position(
24858 &snapshot,
24859 position,
24860 Direction::Next,
24861 window,
24862 cx,
24863 );
24864 editor.expand_selected_diff_hunks(cx);
24865 });
24866 }
24867 }),
24868 )
24869 .child(
24870 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24871 .shape(IconButtonShape::Square)
24872 .icon_size(IconSize::Small)
24873 // .disabled(!has_multiple_hunks)
24874 .tooltip({
24875 let focus_handle = editor.focus_handle(cx);
24876 move |window, cx| {
24877 Tooltip::for_action_in(
24878 "Previous Hunk",
24879 &GoToPreviousHunk,
24880 &focus_handle,
24881 window,
24882 cx,
24883 )
24884 }
24885 })
24886 .on_click({
24887 let editor = editor.clone();
24888 move |_event, window, cx| {
24889 editor.update(cx, |editor, cx| {
24890 let snapshot = editor.snapshot(window, cx);
24891 let point =
24892 hunk_range.start.to_point(&snapshot.buffer_snapshot());
24893 editor.go_to_hunk_before_or_after_position(
24894 &snapshot,
24895 point,
24896 Direction::Prev,
24897 window,
24898 cx,
24899 );
24900 editor.expand_selected_diff_hunks(cx);
24901 });
24902 }
24903 }),
24904 )
24905 },
24906 )
24907 .into_any_element()
24908}
24909
24910pub fn multibuffer_context_lines(cx: &App) -> u32 {
24911 EditorSettings::try_get(cx)
24912 .map(|settings| settings.excerpt_context_lines)
24913 .unwrap_or(2)
24914 .min(32)
24915}