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::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_split);
362 workspace.register_action(Editor::new_file_vertical);
363 workspace.register_action(Editor::new_file_horizontal);
364 workspace.register_action(Editor::cancel_language_server_work);
365 workspace.register_action(Editor::toggle_focus);
366 },
367 )
368 .detach();
369
370 cx.on_action(move |_: &workspace::NewFile, cx| {
371 let app_state = workspace::AppState::global(cx);
372 if let Some(app_state) = app_state.upgrade() {
373 workspace::open_new(
374 Default::default(),
375 app_state,
376 cx,
377 |workspace, window, cx| {
378 Editor::new_file(workspace, &Default::default(), window, cx)
379 },
380 )
381 .detach();
382 }
383 });
384 cx.on_action(move |_: &workspace::NewWindow, cx| {
385 let app_state = workspace::AppState::global(cx);
386 if let Some(app_state) = app_state.upgrade() {
387 workspace::open_new(
388 Default::default(),
389 app_state,
390 cx,
391 |workspace, window, cx| {
392 cx.activate(true);
393 Editor::new_file(workspace, &Default::default(), window, cx)
394 },
395 )
396 .detach();
397 }
398 });
399}
400
401pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
402 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
403}
404
405pub trait DiagnosticRenderer {
406 fn render_group(
407 &self,
408 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
409 buffer_id: BufferId,
410 snapshot: EditorSnapshot,
411 editor: WeakEntity<Editor>,
412 cx: &mut App,
413 ) -> Vec<BlockProperties<Anchor>>;
414
415 fn render_hover(
416 &self,
417 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
418 range: Range<Point>,
419 buffer_id: BufferId,
420 cx: &mut App,
421 ) -> Option<Entity<markdown::Markdown>>;
422
423 fn open_link(
424 &self,
425 editor: &mut Editor,
426 link: SharedString,
427 window: &mut Window,
428 cx: &mut Context<Editor>,
429 );
430}
431
432pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
433
434impl GlobalDiagnosticRenderer {
435 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
436 cx.try_global::<Self>().map(|g| g.0.clone())
437 }
438}
439
440impl gpui::Global for GlobalDiagnosticRenderer {}
441pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
442 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
443}
444
445pub struct SearchWithinRange;
446
447trait InvalidationRegion {
448 fn ranges(&self) -> &[Range<Anchor>];
449}
450
451#[derive(Clone, Debug, PartialEq)]
452pub enum SelectPhase {
453 Begin {
454 position: DisplayPoint,
455 add: bool,
456 click_count: usize,
457 },
458 BeginColumnar {
459 position: DisplayPoint,
460 reset: bool,
461 mode: ColumnarMode,
462 goal_column: u32,
463 },
464 Extend {
465 position: DisplayPoint,
466 click_count: usize,
467 },
468 Update {
469 position: DisplayPoint,
470 goal_column: u32,
471 scroll_delta: gpui::Point<f32>,
472 },
473 End,
474}
475
476#[derive(Clone, Debug, PartialEq)]
477pub enum ColumnarMode {
478 FromMouse,
479 FromSelection,
480}
481
482#[derive(Clone, Debug)]
483pub enum SelectMode {
484 Character,
485 Word(Range<Anchor>),
486 Line(Range<Anchor>),
487 All,
488}
489
490#[derive(Clone, PartialEq, Eq, Debug)]
491pub enum EditorMode {
492 SingleLine,
493 AutoHeight {
494 min_lines: usize,
495 max_lines: Option<usize>,
496 },
497 Full {
498 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
499 scale_ui_elements_with_buffer_font_size: bool,
500 /// When set to `true`, the editor will render a background for the active line.
501 show_active_line_background: bool,
502 /// When set to `true`, the editor's height will be determined by its content.
503 sized_by_content: bool,
504 },
505 Minimap {
506 parent: WeakEntity<Editor>,
507 },
508}
509
510impl EditorMode {
511 pub fn full() -> Self {
512 Self::Full {
513 scale_ui_elements_with_buffer_font_size: true,
514 show_active_line_background: true,
515 sized_by_content: false,
516 }
517 }
518
519 #[inline]
520 pub fn is_full(&self) -> bool {
521 matches!(self, Self::Full { .. })
522 }
523
524 #[inline]
525 pub fn is_single_line(&self) -> bool {
526 matches!(self, Self::SingleLine { .. })
527 }
528
529 #[inline]
530 fn is_minimap(&self) -> bool {
531 matches!(self, Self::Minimap { .. })
532 }
533}
534
535#[derive(Copy, Clone, Debug)]
536pub enum SoftWrap {
537 /// Prefer not to wrap at all.
538 ///
539 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
540 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
541 GitDiff,
542 /// Prefer a single line generally, unless an overly long line is encountered.
543 None,
544 /// Soft wrap lines that exceed the editor width.
545 EditorWidth,
546 /// Soft wrap lines at the preferred line length.
547 Column(u32),
548 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
549 Bounded(u32),
550}
551
552#[derive(Clone)]
553pub struct EditorStyle {
554 pub background: Hsla,
555 pub border: Hsla,
556 pub local_player: PlayerColor,
557 pub text: TextStyle,
558 pub scrollbar_width: Pixels,
559 pub syntax: Arc<SyntaxTheme>,
560 pub status: StatusColors,
561 pub inlay_hints_style: HighlightStyle,
562 pub edit_prediction_styles: EditPredictionStyles,
563 pub unnecessary_code_fade: f32,
564 pub show_underlines: bool,
565}
566
567impl Default for EditorStyle {
568 fn default() -> Self {
569 Self {
570 background: Hsla::default(),
571 border: Hsla::default(),
572 local_player: PlayerColor::default(),
573 text: TextStyle::default(),
574 scrollbar_width: Pixels::default(),
575 syntax: Default::default(),
576 // HACK: Status colors don't have a real default.
577 // We should look into removing the status colors from the editor
578 // style and retrieve them directly from the theme.
579 status: StatusColors::dark(),
580 inlay_hints_style: HighlightStyle::default(),
581 edit_prediction_styles: EditPredictionStyles {
582 insertion: HighlightStyle::default(),
583 whitespace: HighlightStyle::default(),
584 },
585 unnecessary_code_fade: Default::default(),
586 show_underlines: true,
587 }
588 }
589}
590
591pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
592 let show_background = language_settings::language_settings(None, None, cx)
593 .inlay_hints
594 .show_background;
595
596 let mut style = cx.theme().syntax().get("hint");
597
598 if style.color.is_none() {
599 style.color = Some(cx.theme().status().hint);
600 }
601
602 if !show_background {
603 style.background_color = None;
604 return style;
605 }
606
607 if style.background_color.is_none() {
608 style.background_color = Some(cx.theme().status().hint_background);
609 }
610
611 style
612}
613
614pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
615 EditPredictionStyles {
616 insertion: HighlightStyle {
617 color: Some(cx.theme().status().predictive),
618 ..HighlightStyle::default()
619 },
620 whitespace: HighlightStyle {
621 background_color: Some(cx.theme().status().created_background),
622 ..HighlightStyle::default()
623 },
624 }
625}
626
627type CompletionId = usize;
628
629pub(crate) enum EditDisplayMode {
630 TabAccept,
631 DiffPopover,
632 Inline,
633}
634
635enum EditPrediction {
636 Edit {
637 edits: Vec<(Range<Anchor>, String)>,
638 edit_preview: Option<EditPreview>,
639 display_mode: EditDisplayMode,
640 snapshot: BufferSnapshot,
641 },
642 /// Move to a specific location in the active editor
643 MoveWithin {
644 target: Anchor,
645 snapshot: BufferSnapshot,
646 },
647 /// Move to a specific location in a different editor (not the active one)
648 MoveOutside {
649 target: language::Anchor,
650 snapshot: BufferSnapshot,
651 },
652}
653
654struct EditPredictionState {
655 inlay_ids: Vec<InlayId>,
656 completion: EditPrediction,
657 completion_id: Option<SharedString>,
658 invalidation_range: Option<Range<Anchor>>,
659}
660
661enum EditPredictionSettings {
662 Disabled,
663 Enabled {
664 show_in_menu: bool,
665 preview_requires_modifier: bool,
666 },
667}
668
669enum EditPredictionHighlight {}
670
671#[derive(Debug, Clone)]
672struct InlineDiagnostic {
673 message: SharedString,
674 group_id: usize,
675 is_primary: bool,
676 start: Point,
677 severity: lsp::DiagnosticSeverity,
678}
679
680pub enum MenuEditPredictionsPolicy {
681 Never,
682 ByProvider,
683}
684
685pub enum EditPredictionPreview {
686 /// Modifier is not pressed
687 Inactive { released_too_fast: bool },
688 /// Modifier pressed
689 Active {
690 since: Instant,
691 previous_scroll_position: Option<ScrollAnchor>,
692 },
693}
694
695impl EditPredictionPreview {
696 pub fn released_too_fast(&self) -> bool {
697 match self {
698 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
699 EditPredictionPreview::Active { .. } => false,
700 }
701 }
702
703 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
704 if let EditPredictionPreview::Active {
705 previous_scroll_position,
706 ..
707 } = self
708 {
709 *previous_scroll_position = scroll_position;
710 }
711 }
712}
713
714pub struct ContextMenuOptions {
715 pub min_entries_visible: usize,
716 pub max_entries_visible: usize,
717 pub placement: Option<ContextMenuPlacement>,
718}
719
720#[derive(Debug, Clone, PartialEq, Eq)]
721pub enum ContextMenuPlacement {
722 Above,
723 Below,
724}
725
726#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
727struct EditorActionId(usize);
728
729impl EditorActionId {
730 pub fn post_inc(&mut self) -> Self {
731 let answer = self.0;
732
733 *self = Self(answer + 1);
734
735 Self(answer)
736 }
737}
738
739// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
740// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
741
742type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
743type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
744
745#[derive(Default)]
746struct ScrollbarMarkerState {
747 scrollbar_size: Size<Pixels>,
748 dirty: bool,
749 markers: Arc<[PaintQuad]>,
750 pending_refresh: Option<Task<Result<()>>>,
751}
752
753impl ScrollbarMarkerState {
754 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
755 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
756 }
757}
758
759#[derive(Clone, Copy, PartialEq, Eq)]
760pub enum MinimapVisibility {
761 Disabled,
762 Enabled {
763 /// The configuration currently present in the users settings.
764 setting_configuration: bool,
765 /// Whether to override the currently set visibility from the users setting.
766 toggle_override: bool,
767 },
768}
769
770impl MinimapVisibility {
771 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
772 if mode.is_full() {
773 Self::Enabled {
774 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
775 toggle_override: false,
776 }
777 } else {
778 Self::Disabled
779 }
780 }
781
782 fn hidden(&self) -> Self {
783 match *self {
784 Self::Enabled {
785 setting_configuration,
786 ..
787 } => Self::Enabled {
788 setting_configuration,
789 toggle_override: setting_configuration,
790 },
791 Self::Disabled => Self::Disabled,
792 }
793 }
794
795 fn disabled(&self) -> bool {
796 matches!(*self, Self::Disabled)
797 }
798
799 fn settings_visibility(&self) -> bool {
800 match *self {
801 Self::Enabled {
802 setting_configuration,
803 ..
804 } => setting_configuration,
805 _ => false,
806 }
807 }
808
809 fn visible(&self) -> bool {
810 match *self {
811 Self::Enabled {
812 setting_configuration,
813 toggle_override,
814 } => setting_configuration ^ toggle_override,
815 _ => false,
816 }
817 }
818
819 fn toggle_visibility(&self) -> Self {
820 match *self {
821 Self::Enabled {
822 toggle_override,
823 setting_configuration,
824 } => Self::Enabled {
825 setting_configuration,
826 toggle_override: !toggle_override,
827 },
828 Self::Disabled => Self::Disabled,
829 }
830 }
831}
832
833#[derive(Clone, Debug)]
834struct RunnableTasks {
835 templates: Vec<(TaskSourceKind, TaskTemplate)>,
836 offset: multi_buffer::Anchor,
837 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
838 column: u32,
839 // Values of all named captures, including those starting with '_'
840 extra_variables: HashMap<String, String>,
841 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
842 context_range: Range<BufferOffset>,
843}
844
845impl RunnableTasks {
846 fn resolve<'a>(
847 &'a self,
848 cx: &'a task::TaskContext,
849 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
850 self.templates.iter().filter_map(|(kind, template)| {
851 template
852 .resolve_task(&kind.to_id_base(), cx)
853 .map(|task| (kind.clone(), task))
854 })
855 }
856}
857
858#[derive(Clone)]
859pub struct ResolvedTasks {
860 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
861 position: Anchor,
862}
863
864#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
865struct BufferOffset(usize);
866
867/// Addons allow storing per-editor state in other crates (e.g. Vim)
868pub trait Addon: 'static {
869 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
870
871 fn render_buffer_header_controls(
872 &self,
873 _: &ExcerptInfo,
874 _: &Window,
875 _: &App,
876 ) -> Option<AnyElement> {
877 None
878 }
879
880 fn to_any(&self) -> &dyn std::any::Any;
881
882 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
883 None
884 }
885}
886
887struct ChangeLocation {
888 current: Option<Vec<Anchor>>,
889 original: Vec<Anchor>,
890}
891impl ChangeLocation {
892 fn locations(&self) -> &[Anchor] {
893 self.current.as_ref().unwrap_or(&self.original)
894 }
895}
896
897/// A set of caret positions, registered when the editor was edited.
898pub struct ChangeList {
899 changes: Vec<ChangeLocation>,
900 /// Currently "selected" change.
901 position: Option<usize>,
902}
903
904impl ChangeList {
905 pub fn new() -> Self {
906 Self {
907 changes: Vec::new(),
908 position: None,
909 }
910 }
911
912 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
913 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
914 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
915 if self.changes.is_empty() {
916 return None;
917 }
918
919 let prev = self.position.unwrap_or(self.changes.len());
920 let next = if direction == Direction::Prev {
921 prev.saturating_sub(count)
922 } else {
923 (prev + count).min(self.changes.len() - 1)
924 };
925 self.position = Some(next);
926 self.changes.get(next).map(|change| change.locations())
927 }
928
929 /// Adds a new change to the list, resetting the change list position.
930 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
931 self.position.take();
932 if let Some(last) = self.changes.last_mut()
933 && group
934 {
935 last.current = Some(new_positions)
936 } else {
937 self.changes.push(ChangeLocation {
938 original: new_positions,
939 current: None,
940 });
941 }
942 }
943
944 pub fn last(&self) -> Option<&[Anchor]> {
945 self.changes.last().map(|change| change.locations())
946 }
947
948 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
949 self.changes.last().map(|change| change.original.as_slice())
950 }
951
952 pub fn invert_last_group(&mut self) {
953 if let Some(last) = self.changes.last_mut()
954 && let Some(current) = last.current.as_mut()
955 {
956 mem::swap(&mut last.original, current);
957 }
958 }
959}
960
961#[derive(Clone)]
962struct InlineBlamePopoverState {
963 scroll_handle: ScrollHandle,
964 commit_message: Option<ParsedCommitMessage>,
965 markdown: Entity<Markdown>,
966}
967
968struct InlineBlamePopover {
969 position: gpui::Point<Pixels>,
970 hide_task: Option<Task<()>>,
971 popover_bounds: Option<Bounds<Pixels>>,
972 popover_state: InlineBlamePopoverState,
973 keyboard_grace: bool,
974}
975
976enum SelectionDragState {
977 /// State when no drag related activity is detected.
978 None,
979 /// State when the mouse is down on a selection that is about to be dragged.
980 ReadyToDrag {
981 selection: Selection<Anchor>,
982 click_position: gpui::Point<Pixels>,
983 mouse_down_time: Instant,
984 },
985 /// State when the mouse is dragging the selection in the editor.
986 Dragging {
987 selection: Selection<Anchor>,
988 drop_cursor: Selection<Anchor>,
989 hide_drop_cursor: bool,
990 },
991}
992
993enum ColumnarSelectionState {
994 FromMouse {
995 selection_tail: Anchor,
996 display_point: Option<DisplayPoint>,
997 },
998 FromSelection {
999 selection_tail: Anchor,
1000 },
1001}
1002
1003/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1004/// a breakpoint on them.
1005#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1006struct PhantomBreakpointIndicator {
1007 display_row: DisplayRow,
1008 /// There's a small debounce between hovering over the line and showing the indicator.
1009 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1010 is_active: bool,
1011 collides_with_existing_breakpoint: bool,
1012}
1013
1014/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1015///
1016/// See the [module level documentation](self) for more information.
1017pub struct Editor {
1018 focus_handle: FocusHandle,
1019 last_focused_descendant: Option<WeakFocusHandle>,
1020 /// The text buffer being edited
1021 buffer: Entity<MultiBuffer>,
1022 /// Map of how text in the buffer should be displayed.
1023 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1024 pub display_map: Entity<DisplayMap>,
1025 placeholder_display_map: Option<Entity<DisplayMap>>,
1026 pub selections: SelectionsCollection,
1027 pub scroll_manager: ScrollManager,
1028 /// When inline assist editors are linked, they all render cursors because
1029 /// typing enters text into each of them, even the ones that aren't focused.
1030 pub(crate) show_cursor_when_unfocused: bool,
1031 columnar_selection_state: Option<ColumnarSelectionState>,
1032 add_selections_state: Option<AddSelectionsState>,
1033 select_next_state: Option<SelectNextState>,
1034 select_prev_state: Option<SelectNextState>,
1035 selection_history: SelectionHistory,
1036 defer_selection_effects: bool,
1037 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1038 autoclose_regions: Vec<AutocloseRegion>,
1039 snippet_stack: InvalidationStack<SnippetState>,
1040 select_syntax_node_history: SelectSyntaxNodeHistory,
1041 ime_transaction: Option<TransactionId>,
1042 pub diagnostics_max_severity: DiagnosticSeverity,
1043 active_diagnostics: ActiveDiagnostic,
1044 show_inline_diagnostics: bool,
1045 inline_diagnostics_update: Task<()>,
1046 inline_diagnostics_enabled: bool,
1047 diagnostics_enabled: bool,
1048 word_completions_enabled: bool,
1049 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1050 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1051 hard_wrap: Option<usize>,
1052 project: Option<Entity<Project>>,
1053 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1054 completion_provider: Option<Rc<dyn CompletionProvider>>,
1055 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1056 blink_manager: Entity<BlinkManager>,
1057 show_cursor_names: bool,
1058 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1059 pub show_local_selections: bool,
1060 mode: EditorMode,
1061 show_breadcrumbs: bool,
1062 show_gutter: bool,
1063 show_scrollbars: ScrollbarAxes,
1064 minimap_visibility: MinimapVisibility,
1065 offset_content: bool,
1066 disable_expand_excerpt_buttons: bool,
1067 show_line_numbers: Option<bool>,
1068 use_relative_line_numbers: Option<bool>,
1069 show_git_diff_gutter: Option<bool>,
1070 show_code_actions: Option<bool>,
1071 show_runnables: Option<bool>,
1072 show_breakpoints: Option<bool>,
1073 show_wrap_guides: Option<bool>,
1074 show_indent_guides: Option<bool>,
1075 highlight_order: usize,
1076 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1077 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1078 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1079 scrollbar_marker_state: ScrollbarMarkerState,
1080 active_indent_guides_state: ActiveIndentGuidesState,
1081 nav_history: Option<ItemNavHistory>,
1082 context_menu: RefCell<Option<CodeContextMenu>>,
1083 context_menu_options: Option<ContextMenuOptions>,
1084 mouse_context_menu: Option<MouseContextMenu>,
1085 completion_tasks: Vec<(CompletionId, Task<()>)>,
1086 inline_blame_popover: Option<InlineBlamePopover>,
1087 inline_blame_popover_show_task: Option<Task<()>>,
1088 signature_help_state: SignatureHelpState,
1089 auto_signature_help: Option<bool>,
1090 find_all_references_task_sources: Vec<Anchor>,
1091 next_completion_id: CompletionId,
1092 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1093 code_actions_task: Option<Task<Result<()>>>,
1094 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1095 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1096 document_highlights_task: Option<Task<()>>,
1097 linked_editing_range_task: Option<Task<Option<()>>>,
1098 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1099 pending_rename: Option<RenameState>,
1100 searchable: bool,
1101 cursor_shape: CursorShape,
1102 current_line_highlight: Option<CurrentLineHighlight>,
1103 collapse_matches: bool,
1104 autoindent_mode: Option<AutoindentMode>,
1105 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1106 input_enabled: bool,
1107 use_modal_editing: bool,
1108 read_only: bool,
1109 leader_id: Option<CollaboratorId>,
1110 remote_id: Option<ViewId>,
1111 pub hover_state: HoverState,
1112 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1113 gutter_hovered: bool,
1114 hovered_link_state: Option<HoveredLinkState>,
1115 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1116 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1117 active_edit_prediction: Option<EditPredictionState>,
1118 /// Used to prevent flickering as the user types while the menu is open
1119 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1120 edit_prediction_settings: EditPredictionSettings,
1121 edit_predictions_hidden_for_vim_mode: bool,
1122 show_edit_predictions_override: Option<bool>,
1123 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1124 edit_prediction_preview: EditPredictionPreview,
1125 edit_prediction_indent_conflict: bool,
1126 edit_prediction_requires_modifier_in_indent_conflict: bool,
1127 inlay_hint_cache: InlayHintCache,
1128 next_inlay_id: u32,
1129 next_color_inlay_id: u32,
1130 _subscriptions: Vec<Subscription>,
1131 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1132 gutter_dimensions: GutterDimensions,
1133 style: Option<EditorStyle>,
1134 text_style_refinement: Option<TextStyleRefinement>,
1135 next_editor_action_id: EditorActionId,
1136 editor_actions: Rc<
1137 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1138 >,
1139 use_autoclose: bool,
1140 use_auto_surround: bool,
1141 auto_replace_emoji_shortcode: bool,
1142 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1143 show_git_blame_gutter: bool,
1144 show_git_blame_inline: bool,
1145 show_git_blame_inline_delay_task: Option<Task<()>>,
1146 git_blame_inline_enabled: bool,
1147 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1148 serialize_dirty_buffers: bool,
1149 show_selection_menu: Option<bool>,
1150 blame: Option<Entity<GitBlame>>,
1151 blame_subscription: Option<Subscription>,
1152 custom_context_menu: Option<
1153 Box<
1154 dyn 'static
1155 + Fn(
1156 &mut Self,
1157 DisplayPoint,
1158 &mut Window,
1159 &mut Context<Self>,
1160 ) -> Option<Entity<ui::ContextMenu>>,
1161 >,
1162 >,
1163 last_bounds: Option<Bounds<Pixels>>,
1164 last_position_map: Option<Rc<PositionMap>>,
1165 expect_bounds_change: Option<Bounds<Pixels>>,
1166 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1167 tasks_update_task: Option<Task<()>>,
1168 breakpoint_store: Option<Entity<BreakpointStore>>,
1169 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1170 hovered_diff_hunk_row: Option<DisplayRow>,
1171 pull_diagnostics_task: Task<()>,
1172 in_project_search: bool,
1173 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1174 breadcrumb_header: Option<String>,
1175 focused_block: Option<FocusedBlock>,
1176 next_scroll_position: NextScrollCursorCenterTopBottom,
1177 addons: HashMap<TypeId, Box<dyn Addon>>,
1178 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1179 load_diff_task: Option<Shared<Task<()>>>,
1180 /// Whether we are temporarily displaying a diff other than git's
1181 temporary_diff_override: bool,
1182 selection_mark_mode: bool,
1183 toggle_fold_multiple_buffers: Task<()>,
1184 _scroll_cursor_center_top_bottom_task: Task<()>,
1185 serialize_selections: Task<()>,
1186 serialize_folds: Task<()>,
1187 mouse_cursor_hidden: bool,
1188 minimap: Option<Entity<Self>>,
1189 hide_mouse_mode: HideMouseMode,
1190 pub change_list: ChangeList,
1191 inline_value_cache: InlineValueCache,
1192 selection_drag_state: SelectionDragState,
1193 colors: Option<LspColorData>,
1194 post_scroll_update: Task<()>,
1195 refresh_colors_task: Task<()>,
1196 folding_newlines: Task<()>,
1197 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1198}
1199
1200#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1201enum NextScrollCursorCenterTopBottom {
1202 #[default]
1203 Center,
1204 Top,
1205 Bottom,
1206}
1207
1208impl NextScrollCursorCenterTopBottom {
1209 fn next(&self) -> Self {
1210 match self {
1211 Self::Center => Self::Top,
1212 Self::Top => Self::Bottom,
1213 Self::Bottom => Self::Center,
1214 }
1215 }
1216}
1217
1218#[derive(Clone)]
1219pub struct EditorSnapshot {
1220 pub mode: EditorMode,
1221 show_gutter: bool,
1222 show_line_numbers: Option<bool>,
1223 show_git_diff_gutter: Option<bool>,
1224 show_code_actions: Option<bool>,
1225 show_runnables: Option<bool>,
1226 show_breakpoints: Option<bool>,
1227 git_blame_gutter_max_author_length: Option<usize>,
1228 pub display_snapshot: DisplaySnapshot,
1229 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1230 is_focused: bool,
1231 scroll_anchor: ScrollAnchor,
1232 ongoing_scroll: OngoingScroll,
1233 current_line_highlight: CurrentLineHighlight,
1234 gutter_hovered: bool,
1235}
1236
1237#[derive(Default, Debug, Clone, Copy)]
1238pub struct GutterDimensions {
1239 pub left_padding: Pixels,
1240 pub right_padding: Pixels,
1241 pub width: Pixels,
1242 pub margin: Pixels,
1243 pub git_blame_entries_width: Option<Pixels>,
1244}
1245
1246impl GutterDimensions {
1247 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1248 Self {
1249 margin: Self::default_gutter_margin(font_id, font_size, cx),
1250 ..Default::default()
1251 }
1252 }
1253
1254 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1255 -cx.text_system().descent(font_id, font_size)
1256 }
1257 /// The full width of the space taken up by the gutter.
1258 pub fn full_width(&self) -> Pixels {
1259 self.margin + self.width
1260 }
1261
1262 /// The width of the space reserved for the fold indicators,
1263 /// use alongside 'justify_end' and `gutter_width` to
1264 /// right align content with the line numbers
1265 pub fn fold_area_width(&self) -> Pixels {
1266 self.margin + self.right_padding
1267 }
1268}
1269
1270struct CharacterDimensions {
1271 em_width: Pixels,
1272 em_advance: Pixels,
1273 line_height: Pixels,
1274}
1275
1276#[derive(Debug)]
1277pub struct RemoteSelection {
1278 pub replica_id: ReplicaId,
1279 pub selection: Selection<Anchor>,
1280 pub cursor_shape: CursorShape,
1281 pub collaborator_id: CollaboratorId,
1282 pub line_mode: bool,
1283 pub user_name: Option<SharedString>,
1284 pub color: PlayerColor,
1285}
1286
1287#[derive(Clone, Debug)]
1288struct SelectionHistoryEntry {
1289 selections: Arc<[Selection<Anchor>]>,
1290 select_next_state: Option<SelectNextState>,
1291 select_prev_state: Option<SelectNextState>,
1292 add_selections_state: Option<AddSelectionsState>,
1293}
1294
1295#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1296enum SelectionHistoryMode {
1297 Normal,
1298 Undoing,
1299 Redoing,
1300 Skipping,
1301}
1302
1303#[derive(Clone, PartialEq, Eq, Hash)]
1304struct HoveredCursor {
1305 replica_id: ReplicaId,
1306 selection_id: usize,
1307}
1308
1309impl Default for SelectionHistoryMode {
1310 fn default() -> Self {
1311 Self::Normal
1312 }
1313}
1314
1315#[derive(Debug)]
1316/// SelectionEffects controls the side-effects of updating the selection.
1317///
1318/// The default behaviour does "what you mostly want":
1319/// - it pushes to the nav history if the cursor moved by >10 lines
1320/// - it re-triggers completion requests
1321/// - it scrolls to fit
1322///
1323/// You might want to modify these behaviours. For example when doing a "jump"
1324/// like go to definition, we always want to add to nav history; but when scrolling
1325/// in vim mode we never do.
1326///
1327/// Similarly, you might want to disable scrolling if you don't want the viewport to
1328/// move.
1329#[derive(Clone)]
1330pub struct SelectionEffects {
1331 nav_history: Option<bool>,
1332 completions: bool,
1333 scroll: Option<Autoscroll>,
1334}
1335
1336impl Default for SelectionEffects {
1337 fn default() -> Self {
1338 Self {
1339 nav_history: None,
1340 completions: true,
1341 scroll: Some(Autoscroll::fit()),
1342 }
1343 }
1344}
1345impl SelectionEffects {
1346 pub fn scroll(scroll: Autoscroll) -> Self {
1347 Self {
1348 scroll: Some(scroll),
1349 ..Default::default()
1350 }
1351 }
1352
1353 pub fn no_scroll() -> Self {
1354 Self {
1355 scroll: None,
1356 ..Default::default()
1357 }
1358 }
1359
1360 pub fn completions(self, completions: bool) -> Self {
1361 Self {
1362 completions,
1363 ..self
1364 }
1365 }
1366
1367 pub fn nav_history(self, nav_history: bool) -> Self {
1368 Self {
1369 nav_history: Some(nav_history),
1370 ..self
1371 }
1372 }
1373}
1374
1375struct DeferredSelectionEffectsState {
1376 changed: bool,
1377 effects: SelectionEffects,
1378 old_cursor_position: Anchor,
1379 history_entry: SelectionHistoryEntry,
1380}
1381
1382#[derive(Default)]
1383struct SelectionHistory {
1384 #[allow(clippy::type_complexity)]
1385 selections_by_transaction:
1386 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1387 mode: SelectionHistoryMode,
1388 undo_stack: VecDeque<SelectionHistoryEntry>,
1389 redo_stack: VecDeque<SelectionHistoryEntry>,
1390}
1391
1392impl SelectionHistory {
1393 #[track_caller]
1394 fn insert_transaction(
1395 &mut self,
1396 transaction_id: TransactionId,
1397 selections: Arc<[Selection<Anchor>]>,
1398 ) {
1399 if selections.is_empty() {
1400 log::error!(
1401 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1402 std::panic::Location::caller()
1403 );
1404 return;
1405 }
1406 self.selections_by_transaction
1407 .insert(transaction_id, (selections, None));
1408 }
1409
1410 #[allow(clippy::type_complexity)]
1411 fn transaction(
1412 &self,
1413 transaction_id: TransactionId,
1414 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1415 self.selections_by_transaction.get(&transaction_id)
1416 }
1417
1418 #[allow(clippy::type_complexity)]
1419 fn transaction_mut(
1420 &mut self,
1421 transaction_id: TransactionId,
1422 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1423 self.selections_by_transaction.get_mut(&transaction_id)
1424 }
1425
1426 fn push(&mut self, entry: SelectionHistoryEntry) {
1427 if !entry.selections.is_empty() {
1428 match self.mode {
1429 SelectionHistoryMode::Normal => {
1430 self.push_undo(entry);
1431 self.redo_stack.clear();
1432 }
1433 SelectionHistoryMode::Undoing => self.push_redo(entry),
1434 SelectionHistoryMode::Redoing => self.push_undo(entry),
1435 SelectionHistoryMode::Skipping => {}
1436 }
1437 }
1438 }
1439
1440 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1441 if self
1442 .undo_stack
1443 .back()
1444 .is_none_or(|e| e.selections != entry.selections)
1445 {
1446 self.undo_stack.push_back(entry);
1447 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1448 self.undo_stack.pop_front();
1449 }
1450 }
1451 }
1452
1453 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1454 if self
1455 .redo_stack
1456 .back()
1457 .is_none_or(|e| e.selections != entry.selections)
1458 {
1459 self.redo_stack.push_back(entry);
1460 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1461 self.redo_stack.pop_front();
1462 }
1463 }
1464 }
1465}
1466
1467#[derive(Clone, Copy)]
1468pub struct RowHighlightOptions {
1469 pub autoscroll: bool,
1470 pub include_gutter: bool,
1471}
1472
1473impl Default for RowHighlightOptions {
1474 fn default() -> Self {
1475 Self {
1476 autoscroll: Default::default(),
1477 include_gutter: true,
1478 }
1479 }
1480}
1481
1482struct RowHighlight {
1483 index: usize,
1484 range: Range<Anchor>,
1485 color: Hsla,
1486 options: RowHighlightOptions,
1487 type_id: TypeId,
1488}
1489
1490#[derive(Clone, Debug)]
1491struct AddSelectionsState {
1492 groups: Vec<AddSelectionsGroup>,
1493}
1494
1495#[derive(Clone, Debug)]
1496struct AddSelectionsGroup {
1497 above: bool,
1498 stack: Vec<usize>,
1499}
1500
1501#[derive(Clone)]
1502struct SelectNextState {
1503 query: AhoCorasick,
1504 wordwise: bool,
1505 done: bool,
1506}
1507
1508impl std::fmt::Debug for SelectNextState {
1509 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1510 f.debug_struct(std::any::type_name::<Self>())
1511 .field("wordwise", &self.wordwise)
1512 .field("done", &self.done)
1513 .finish()
1514 }
1515}
1516
1517#[derive(Debug)]
1518struct AutocloseRegion {
1519 selection_id: usize,
1520 range: Range<Anchor>,
1521 pair: BracketPair,
1522}
1523
1524#[derive(Debug)]
1525struct SnippetState {
1526 ranges: Vec<Vec<Range<Anchor>>>,
1527 active_index: usize,
1528 choices: Vec<Option<Vec<String>>>,
1529}
1530
1531#[doc(hidden)]
1532pub struct RenameState {
1533 pub range: Range<Anchor>,
1534 pub old_name: Arc<str>,
1535 pub editor: Entity<Editor>,
1536 block_id: CustomBlockId,
1537}
1538
1539struct InvalidationStack<T>(Vec<T>);
1540
1541struct RegisteredEditPredictionProvider {
1542 provider: Arc<dyn EditPredictionProviderHandle>,
1543 _subscription: Subscription,
1544}
1545
1546#[derive(Debug, PartialEq, Eq)]
1547pub struct ActiveDiagnosticGroup {
1548 pub active_range: Range<Anchor>,
1549 pub active_message: String,
1550 pub group_id: usize,
1551 pub blocks: HashSet<CustomBlockId>,
1552}
1553
1554#[derive(Debug, PartialEq, Eq)]
1555
1556pub(crate) enum ActiveDiagnostic {
1557 None,
1558 All,
1559 Group(ActiveDiagnosticGroup),
1560}
1561
1562#[derive(Serialize, Deserialize, Clone, Debug)]
1563pub struct ClipboardSelection {
1564 /// The number of bytes in this selection.
1565 pub len: usize,
1566 /// Whether this was a full-line selection.
1567 pub is_entire_line: bool,
1568 /// The indentation of the first line when this content was originally copied.
1569 pub first_line_indent: u32,
1570}
1571
1572// selections, scroll behavior, was newest selection reversed
1573type SelectSyntaxNodeHistoryState = (
1574 Box<[Selection<usize>]>,
1575 SelectSyntaxNodeScrollBehavior,
1576 bool,
1577);
1578
1579#[derive(Default)]
1580struct SelectSyntaxNodeHistory {
1581 stack: Vec<SelectSyntaxNodeHistoryState>,
1582 // disable temporarily to allow changing selections without losing the stack
1583 pub disable_clearing: bool,
1584}
1585
1586impl SelectSyntaxNodeHistory {
1587 pub fn try_clear(&mut self) {
1588 if !self.disable_clearing {
1589 self.stack.clear();
1590 }
1591 }
1592
1593 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1594 self.stack.push(selection);
1595 }
1596
1597 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1598 self.stack.pop()
1599 }
1600}
1601
1602enum SelectSyntaxNodeScrollBehavior {
1603 CursorTop,
1604 FitSelection,
1605 CursorBottom,
1606}
1607
1608#[derive(Debug)]
1609pub(crate) struct NavigationData {
1610 cursor_anchor: Anchor,
1611 cursor_position: Point,
1612 scroll_anchor: ScrollAnchor,
1613 scroll_top_row: u32,
1614}
1615
1616#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1617pub enum GotoDefinitionKind {
1618 Symbol,
1619 Declaration,
1620 Type,
1621 Implementation,
1622}
1623
1624#[derive(Debug, Clone)]
1625enum InlayHintRefreshReason {
1626 ModifiersChanged(bool),
1627 Toggle(bool),
1628 SettingsChange(InlayHintSettings),
1629 NewLinesShown,
1630 BufferEdited(HashSet<Arc<Language>>),
1631 RefreshRequested,
1632 ExcerptsRemoved(Vec<ExcerptId>),
1633}
1634
1635impl InlayHintRefreshReason {
1636 fn description(&self) -> &'static str {
1637 match self {
1638 Self::ModifiersChanged(_) => "modifiers changed",
1639 Self::Toggle(_) => "toggle",
1640 Self::SettingsChange(_) => "settings change",
1641 Self::NewLinesShown => "new lines shown",
1642 Self::BufferEdited(_) => "buffer edited",
1643 Self::RefreshRequested => "refresh requested",
1644 Self::ExcerptsRemoved(_) => "excerpts removed",
1645 }
1646 }
1647}
1648
1649pub enum FormatTarget {
1650 Buffers(HashSet<Entity<Buffer>>),
1651 Ranges(Vec<Range<MultiBufferPoint>>),
1652}
1653
1654pub(crate) struct FocusedBlock {
1655 id: BlockId,
1656 focus_handle: WeakFocusHandle,
1657}
1658
1659#[derive(Clone)]
1660enum JumpData {
1661 MultiBufferRow {
1662 row: MultiBufferRow,
1663 line_offset_from_top: u32,
1664 },
1665 MultiBufferPoint {
1666 excerpt_id: ExcerptId,
1667 position: Point,
1668 anchor: text::Anchor,
1669 line_offset_from_top: u32,
1670 },
1671}
1672
1673pub enum MultibufferSelectionMode {
1674 First,
1675 All,
1676}
1677
1678#[derive(Clone, Copy, Debug, Default)]
1679pub struct RewrapOptions {
1680 pub override_language_settings: bool,
1681 pub preserve_existing_whitespace: bool,
1682}
1683
1684impl Editor {
1685 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1686 let buffer = cx.new(|cx| Buffer::local("", cx));
1687 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1688 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1689 }
1690
1691 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1692 let buffer = cx.new(|cx| Buffer::local("", cx));
1693 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1694 Self::new(EditorMode::full(), buffer, None, window, cx)
1695 }
1696
1697 pub fn auto_height(
1698 min_lines: usize,
1699 max_lines: usize,
1700 window: &mut Window,
1701 cx: &mut Context<Self>,
1702 ) -> Self {
1703 let buffer = cx.new(|cx| Buffer::local("", cx));
1704 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1705 Self::new(
1706 EditorMode::AutoHeight {
1707 min_lines,
1708 max_lines: Some(max_lines),
1709 },
1710 buffer,
1711 None,
1712 window,
1713 cx,
1714 )
1715 }
1716
1717 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1718 /// The editor grows as tall as needed to fit its content.
1719 pub fn auto_height_unbounded(
1720 min_lines: usize,
1721 window: &mut Window,
1722 cx: &mut Context<Self>,
1723 ) -> Self {
1724 let buffer = cx.new(|cx| Buffer::local("", cx));
1725 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1726 Self::new(
1727 EditorMode::AutoHeight {
1728 min_lines,
1729 max_lines: None,
1730 },
1731 buffer,
1732 None,
1733 window,
1734 cx,
1735 )
1736 }
1737
1738 pub fn for_buffer(
1739 buffer: Entity<Buffer>,
1740 project: Option<Entity<Project>>,
1741 window: &mut Window,
1742 cx: &mut Context<Self>,
1743 ) -> Self {
1744 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1745 Self::new(EditorMode::full(), buffer, project, window, cx)
1746 }
1747
1748 pub fn for_multibuffer(
1749 buffer: Entity<MultiBuffer>,
1750 project: Option<Entity<Project>>,
1751 window: &mut Window,
1752 cx: &mut Context<Self>,
1753 ) -> Self {
1754 Self::new(EditorMode::full(), buffer, project, window, cx)
1755 }
1756
1757 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1758 let mut clone = Self::new(
1759 self.mode.clone(),
1760 self.buffer.clone(),
1761 self.project.clone(),
1762 window,
1763 cx,
1764 );
1765 self.display_map.update(cx, |display_map, cx| {
1766 let snapshot = display_map.snapshot(cx);
1767 clone.display_map.update(cx, |display_map, cx| {
1768 display_map.set_state(&snapshot, cx);
1769 });
1770 });
1771 clone.folds_did_change(cx);
1772 clone.selections.clone_state(&self.selections);
1773 clone.scroll_manager.clone_state(&self.scroll_manager);
1774 clone.searchable = self.searchable;
1775 clone.read_only = self.read_only;
1776 clone
1777 }
1778
1779 pub fn new(
1780 mode: EditorMode,
1781 buffer: Entity<MultiBuffer>,
1782 project: Option<Entity<Project>>,
1783 window: &mut Window,
1784 cx: &mut Context<Self>,
1785 ) -> Self {
1786 Editor::new_internal(mode, buffer, project, None, window, cx)
1787 }
1788
1789 fn new_internal(
1790 mode: EditorMode,
1791 multi_buffer: Entity<MultiBuffer>,
1792 project: Option<Entity<Project>>,
1793 display_map: Option<Entity<DisplayMap>>,
1794 window: &mut Window,
1795 cx: &mut Context<Self>,
1796 ) -> Self {
1797 debug_assert!(
1798 display_map.is_none() || mode.is_minimap(),
1799 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1800 );
1801
1802 let full_mode = mode.is_full();
1803 let is_minimap = mode.is_minimap();
1804 let diagnostics_max_severity = if full_mode {
1805 EditorSettings::get_global(cx)
1806 .diagnostics_max_severity
1807 .unwrap_or(DiagnosticSeverity::Hint)
1808 } else {
1809 DiagnosticSeverity::Off
1810 };
1811 let style = window.text_style();
1812 let font_size = style.font_size.to_pixels(window.rem_size());
1813 let editor = cx.entity().downgrade();
1814 let fold_placeholder = FoldPlaceholder {
1815 constrain_width: false,
1816 render: Arc::new(move |fold_id, fold_range, cx| {
1817 let editor = editor.clone();
1818 div()
1819 .id(fold_id)
1820 .bg(cx.theme().colors().ghost_element_background)
1821 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1822 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1823 .rounded_xs()
1824 .size_full()
1825 .cursor_pointer()
1826 .child("⋯")
1827 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1828 .on_click(move |_, _window, cx| {
1829 editor
1830 .update(cx, |editor, cx| {
1831 editor.unfold_ranges(
1832 &[fold_range.start..fold_range.end],
1833 true,
1834 false,
1835 cx,
1836 );
1837 cx.stop_propagation();
1838 })
1839 .ok();
1840 })
1841 .into_any()
1842 }),
1843 merge_adjacent: true,
1844 ..FoldPlaceholder::default()
1845 };
1846 let display_map = display_map.unwrap_or_else(|| {
1847 cx.new(|cx| {
1848 DisplayMap::new(
1849 multi_buffer.clone(),
1850 style.font(),
1851 font_size,
1852 None,
1853 FILE_HEADER_HEIGHT,
1854 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1855 fold_placeholder,
1856 diagnostics_max_severity,
1857 cx,
1858 )
1859 })
1860 });
1861
1862 let selections = SelectionsCollection::new(display_map.clone(), multi_buffer.clone());
1863
1864 let blink_manager = cx.new(|cx| {
1865 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1866 if is_minimap {
1867 blink_manager.disable(cx);
1868 }
1869 blink_manager
1870 });
1871
1872 let soft_wrap_mode_override =
1873 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1874
1875 let mut project_subscriptions = Vec::new();
1876 if full_mode && let Some(project) = project.as_ref() {
1877 project_subscriptions.push(cx.subscribe_in(
1878 project,
1879 window,
1880 |editor, _, event, window, cx| match event {
1881 project::Event::RefreshCodeLens => {
1882 // we always query lens with actions, without storing them, always refreshing them
1883 }
1884 project::Event::RefreshInlayHints => {
1885 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1886 }
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 editor.registered_buffers.clear();
1892 editor.register_visible_buffers(cx);
1893 }
1894 project::Event::LanguageServerAdded(..) => {
1895 if editor.tasks_update_task.is_none() {
1896 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1897 }
1898 }
1899 project::Event::SnippetEdit(id, snippet_edits) => {
1900 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1901 let focus_handle = editor.focus_handle(cx);
1902 if focus_handle.is_focused(window) {
1903 let snapshot = buffer.read(cx).snapshot();
1904 for (range, snippet) in snippet_edits {
1905 let editor_range =
1906 language::range_from_lsp(*range).to_offset(&snapshot);
1907 editor
1908 .insert_snippet(
1909 &[editor_range],
1910 snippet.clone(),
1911 window,
1912 cx,
1913 )
1914 .ok();
1915 }
1916 }
1917 }
1918 }
1919 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1920 let buffer_id = *buffer_id;
1921 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
1922 let registered = editor.register_buffer(buffer_id, cx);
1923 if registered {
1924 editor.update_lsp_data(Some(buffer_id), window, cx);
1925 editor.refresh_inlay_hints(
1926 InlayHintRefreshReason::RefreshRequested,
1927 cx,
1928 );
1929 refresh_linked_ranges(editor, window, cx);
1930 editor.refresh_code_actions(window, cx);
1931 editor.refresh_document_highlights(cx);
1932 }
1933 }
1934 }
1935
1936 project::Event::EntryRenamed(transaction) => {
1937 let Some(workspace) = editor.workspace() else {
1938 return;
1939 };
1940 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1941 else {
1942 return;
1943 };
1944 if active_editor.entity_id() == cx.entity_id() {
1945 let edited_buffers_already_open = {
1946 let other_editors: Vec<Entity<Editor>> = workspace
1947 .read(cx)
1948 .panes()
1949 .iter()
1950 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1951 .filter(|editor| editor.entity_id() != cx.entity_id())
1952 .collect();
1953
1954 transaction.0.keys().all(|buffer| {
1955 other_editors.iter().any(|editor| {
1956 let multi_buffer = editor.read(cx).buffer();
1957 multi_buffer.read(cx).is_singleton()
1958 && multi_buffer.read(cx).as_singleton().map_or(
1959 false,
1960 |singleton| {
1961 singleton.entity_id() == buffer.entity_id()
1962 },
1963 )
1964 })
1965 })
1966 };
1967
1968 if !edited_buffers_already_open {
1969 let workspace = workspace.downgrade();
1970 let transaction = transaction.clone();
1971 cx.defer_in(window, move |_, window, cx| {
1972 cx.spawn_in(window, async move |editor, cx| {
1973 Self::open_project_transaction(
1974 &editor,
1975 workspace,
1976 transaction,
1977 "Rename".to_string(),
1978 cx,
1979 )
1980 .await
1981 .ok()
1982 })
1983 .detach();
1984 });
1985 }
1986 }
1987 }
1988
1989 _ => {}
1990 },
1991 ));
1992 if let Some(task_inventory) = project
1993 .read(cx)
1994 .task_store()
1995 .read(cx)
1996 .task_inventory()
1997 .cloned()
1998 {
1999 project_subscriptions.push(cx.observe_in(
2000 &task_inventory,
2001 window,
2002 |editor, _, window, cx| {
2003 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2004 },
2005 ));
2006 };
2007
2008 project_subscriptions.push(cx.subscribe_in(
2009 &project.read(cx).breakpoint_store(),
2010 window,
2011 |editor, _, event, window, cx| match event {
2012 BreakpointStoreEvent::ClearDebugLines => {
2013 editor.clear_row_highlights::<ActiveDebugLine>();
2014 editor.refresh_inline_values(cx);
2015 }
2016 BreakpointStoreEvent::SetDebugLine => {
2017 if editor.go_to_active_debug_line(window, cx) {
2018 cx.stop_propagation();
2019 }
2020
2021 editor.refresh_inline_values(cx);
2022 }
2023 _ => {}
2024 },
2025 ));
2026 let git_store = project.read(cx).git_store().clone();
2027 let project = project.clone();
2028 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2029 if let GitStoreEvent::RepositoryUpdated(
2030 _,
2031 RepositoryEvent::Updated {
2032 new_instance: true, ..
2033 },
2034 _,
2035 ) = event
2036 {
2037 this.load_diff_task = Some(
2038 update_uncommitted_diff_for_buffer(
2039 cx.entity(),
2040 &project,
2041 this.buffer.read(cx).all_buffers(),
2042 this.buffer.clone(),
2043 cx,
2044 )
2045 .shared(),
2046 );
2047 }
2048 }));
2049 }
2050
2051 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2052
2053 let inlay_hint_settings =
2054 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2055 let focus_handle = cx.focus_handle();
2056 if !is_minimap {
2057 cx.on_focus(&focus_handle, window, Self::handle_focus)
2058 .detach();
2059 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2060 .detach();
2061 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2062 .detach();
2063 cx.on_blur(&focus_handle, window, Self::handle_blur)
2064 .detach();
2065 cx.observe_pending_input(window, Self::observe_pending_input)
2066 .detach();
2067 }
2068
2069 let show_indent_guides =
2070 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2071 Some(false)
2072 } else {
2073 None
2074 };
2075
2076 let breakpoint_store = match (&mode, project.as_ref()) {
2077 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2078 _ => None,
2079 };
2080
2081 let mut code_action_providers = Vec::new();
2082 let mut load_uncommitted_diff = None;
2083 if let Some(project) = project.clone() {
2084 load_uncommitted_diff = Some(
2085 update_uncommitted_diff_for_buffer(
2086 cx.entity(),
2087 &project,
2088 multi_buffer.read(cx).all_buffers(),
2089 multi_buffer.clone(),
2090 cx,
2091 )
2092 .shared(),
2093 );
2094 code_action_providers.push(Rc::new(project) as Rc<_>);
2095 }
2096
2097 let mut editor = Self {
2098 focus_handle,
2099 show_cursor_when_unfocused: false,
2100 last_focused_descendant: None,
2101 buffer: multi_buffer.clone(),
2102 display_map: display_map.clone(),
2103 placeholder_display_map: None,
2104 selections,
2105 scroll_manager: ScrollManager::new(cx),
2106 columnar_selection_state: None,
2107 add_selections_state: None,
2108 select_next_state: None,
2109 select_prev_state: None,
2110 selection_history: SelectionHistory::default(),
2111 defer_selection_effects: false,
2112 deferred_selection_effects_state: None,
2113 autoclose_regions: Vec::new(),
2114 snippet_stack: InvalidationStack::default(),
2115 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2116 ime_transaction: None,
2117 active_diagnostics: ActiveDiagnostic::None,
2118 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2119 inline_diagnostics_update: Task::ready(()),
2120 inline_diagnostics: Vec::new(),
2121 soft_wrap_mode_override,
2122 diagnostics_max_severity,
2123 hard_wrap: None,
2124 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2125 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2126 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2127 project,
2128 blink_manager: blink_manager.clone(),
2129 show_local_selections: true,
2130 show_scrollbars: ScrollbarAxes {
2131 horizontal: full_mode,
2132 vertical: full_mode,
2133 },
2134 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2135 offset_content: !matches!(mode, EditorMode::SingleLine),
2136 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2137 show_gutter: full_mode,
2138 show_line_numbers: (!full_mode).then_some(false),
2139 use_relative_line_numbers: None,
2140 disable_expand_excerpt_buttons: !full_mode,
2141 show_git_diff_gutter: None,
2142 show_code_actions: None,
2143 show_runnables: None,
2144 show_breakpoints: None,
2145 show_wrap_guides: None,
2146 show_indent_guides,
2147 highlight_order: 0,
2148 highlighted_rows: HashMap::default(),
2149 background_highlights: HashMap::default(),
2150 gutter_highlights: HashMap::default(),
2151 scrollbar_marker_state: ScrollbarMarkerState::default(),
2152 active_indent_guides_state: ActiveIndentGuidesState::default(),
2153 nav_history: None,
2154 context_menu: RefCell::new(None),
2155 context_menu_options: None,
2156 mouse_context_menu: None,
2157 completion_tasks: Vec::new(),
2158 inline_blame_popover: None,
2159 inline_blame_popover_show_task: None,
2160 signature_help_state: SignatureHelpState::default(),
2161 auto_signature_help: None,
2162 find_all_references_task_sources: Vec::new(),
2163 next_completion_id: 0,
2164 next_inlay_id: 0,
2165 code_action_providers,
2166 available_code_actions: None,
2167 code_actions_task: None,
2168 quick_selection_highlight_task: None,
2169 debounced_selection_highlight_task: None,
2170 document_highlights_task: None,
2171 linked_editing_range_task: None,
2172 pending_rename: None,
2173 searchable: !is_minimap,
2174 cursor_shape: EditorSettings::get_global(cx)
2175 .cursor_shape
2176 .unwrap_or_default(),
2177 current_line_highlight: None,
2178 autoindent_mode: Some(AutoindentMode::EachLine),
2179 collapse_matches: false,
2180 workspace: None,
2181 input_enabled: !is_minimap,
2182 use_modal_editing: full_mode,
2183 read_only: is_minimap,
2184 use_autoclose: true,
2185 use_auto_surround: true,
2186 auto_replace_emoji_shortcode: false,
2187 jsx_tag_auto_close_enabled_in_any_buffer: false,
2188 leader_id: None,
2189 remote_id: None,
2190 hover_state: HoverState::default(),
2191 pending_mouse_down: None,
2192 hovered_link_state: None,
2193 edit_prediction_provider: None,
2194 active_edit_prediction: None,
2195 stale_edit_prediction_in_menu: None,
2196 edit_prediction_preview: EditPredictionPreview::Inactive {
2197 released_too_fast: false,
2198 },
2199 inline_diagnostics_enabled: full_mode,
2200 diagnostics_enabled: full_mode,
2201 word_completions_enabled: full_mode,
2202 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2203 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2204 gutter_hovered: false,
2205 pixel_position_of_newest_cursor: None,
2206 last_bounds: None,
2207 last_position_map: None,
2208 expect_bounds_change: None,
2209 gutter_dimensions: GutterDimensions::default(),
2210 style: None,
2211 show_cursor_names: false,
2212 hovered_cursors: HashMap::default(),
2213 next_editor_action_id: EditorActionId::default(),
2214 editor_actions: Rc::default(),
2215 edit_predictions_hidden_for_vim_mode: false,
2216 show_edit_predictions_override: None,
2217 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2218 edit_prediction_settings: EditPredictionSettings::Disabled,
2219 edit_prediction_indent_conflict: false,
2220 edit_prediction_requires_modifier_in_indent_conflict: true,
2221 custom_context_menu: None,
2222 show_git_blame_gutter: false,
2223 show_git_blame_inline: false,
2224 show_selection_menu: None,
2225 show_git_blame_inline_delay_task: None,
2226 git_blame_inline_enabled: full_mode
2227 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2228 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2229 serialize_dirty_buffers: !is_minimap
2230 && ProjectSettings::get_global(cx)
2231 .session
2232 .restore_unsaved_buffers,
2233 blame: None,
2234 blame_subscription: None,
2235 tasks: BTreeMap::default(),
2236
2237 breakpoint_store,
2238 gutter_breakpoint_indicator: (None, None),
2239 hovered_diff_hunk_row: None,
2240 _subscriptions: (!is_minimap)
2241 .then(|| {
2242 vec![
2243 cx.observe(&multi_buffer, Self::on_buffer_changed),
2244 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2245 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2246 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2247 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2248 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2249 cx.observe_window_activation(window, |editor, window, cx| {
2250 let active = window.is_window_active();
2251 editor.blink_manager.update(cx, |blink_manager, cx| {
2252 if active {
2253 blink_manager.enable(cx);
2254 } else {
2255 blink_manager.disable(cx);
2256 }
2257 });
2258 if active {
2259 editor.show_mouse_cursor(cx);
2260 }
2261 }),
2262 ]
2263 })
2264 .unwrap_or_default(),
2265 tasks_update_task: None,
2266 pull_diagnostics_task: Task::ready(()),
2267 colors: None,
2268 refresh_colors_task: Task::ready(()),
2269 next_color_inlay_id: 0,
2270 post_scroll_update: Task::ready(()),
2271 linked_edit_ranges: Default::default(),
2272 in_project_search: false,
2273 previous_search_ranges: None,
2274 breadcrumb_header: None,
2275 focused_block: None,
2276 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2277 addons: HashMap::default(),
2278 registered_buffers: HashMap::default(),
2279 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2280 selection_mark_mode: false,
2281 toggle_fold_multiple_buffers: Task::ready(()),
2282 serialize_selections: Task::ready(()),
2283 serialize_folds: Task::ready(()),
2284 text_style_refinement: None,
2285 load_diff_task: load_uncommitted_diff,
2286 temporary_diff_override: false,
2287 mouse_cursor_hidden: false,
2288 minimap: None,
2289 hide_mouse_mode: EditorSettings::get_global(cx)
2290 .hide_mouse
2291 .unwrap_or_default(),
2292 change_list: ChangeList::new(),
2293 mode,
2294 selection_drag_state: SelectionDragState::None,
2295 folding_newlines: Task::ready(()),
2296 lookup_key: None,
2297 };
2298
2299 if is_minimap {
2300 return editor;
2301 }
2302
2303 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2304 editor
2305 ._subscriptions
2306 .push(cx.observe(breakpoints, |_, _, cx| {
2307 cx.notify();
2308 }));
2309 }
2310 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2311 editor._subscriptions.extend(project_subscriptions);
2312
2313 editor._subscriptions.push(cx.subscribe_in(
2314 &cx.entity(),
2315 window,
2316 |editor, _, e: &EditorEvent, window, cx| match e {
2317 EditorEvent::ScrollPositionChanged { local, .. } => {
2318 if *local {
2319 let new_anchor = editor.scroll_manager.anchor();
2320 let snapshot = editor.snapshot(window, cx);
2321 editor.update_restoration_data(cx, move |data| {
2322 data.scroll_position = (
2323 new_anchor.top_row(snapshot.buffer_snapshot()),
2324 new_anchor.offset,
2325 );
2326 });
2327 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2328 editor.inline_blame_popover.take();
2329 }
2330 }
2331 EditorEvent::Edited { .. } => {
2332 if !vim_enabled(cx) {
2333 let display_map = editor.display_snapshot(cx);
2334 let selections = editor.selections.all_adjusted_display(&display_map);
2335 let pop_state = editor
2336 .change_list
2337 .last()
2338 .map(|previous| {
2339 previous.len() == selections.len()
2340 && previous.iter().enumerate().all(|(ix, p)| {
2341 p.to_display_point(&display_map).row()
2342 == selections[ix].head().row()
2343 })
2344 })
2345 .unwrap_or(false);
2346 let new_positions = selections
2347 .into_iter()
2348 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2349 .collect();
2350 editor
2351 .change_list
2352 .push_to_change_list(pop_state, new_positions);
2353 }
2354 }
2355 _ => (),
2356 },
2357 ));
2358
2359 if let Some(dap_store) = editor
2360 .project
2361 .as_ref()
2362 .map(|project| project.read(cx).dap_store())
2363 {
2364 let weak_editor = cx.weak_entity();
2365
2366 editor
2367 ._subscriptions
2368 .push(
2369 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2370 let session_entity = cx.entity();
2371 weak_editor
2372 .update(cx, |editor, cx| {
2373 editor._subscriptions.push(
2374 cx.subscribe(&session_entity, Self::on_debug_session_event),
2375 );
2376 })
2377 .ok();
2378 }),
2379 );
2380
2381 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2382 editor
2383 ._subscriptions
2384 .push(cx.subscribe(&session, Self::on_debug_session_event));
2385 }
2386 }
2387
2388 // skip adding the initial selection to selection history
2389 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2390 editor.end_selection(window, cx);
2391 editor.selection_history.mode = SelectionHistoryMode::Normal;
2392
2393 editor.scroll_manager.show_scrollbars(window, cx);
2394 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2395
2396 if full_mode {
2397 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2398 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2399
2400 if editor.git_blame_inline_enabled {
2401 editor.start_git_blame_inline(false, window, cx);
2402 }
2403
2404 editor.go_to_active_debug_line(window, cx);
2405
2406 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2407 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2408 }
2409
2410 editor.minimap =
2411 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2412 editor.colors = Some(LspColorData::new(cx));
2413 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2414 }
2415
2416 editor
2417 }
2418
2419 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2420 self.selections.display_map(cx)
2421 }
2422
2423 pub fn deploy_mouse_context_menu(
2424 &mut self,
2425 position: gpui::Point<Pixels>,
2426 context_menu: Entity<ContextMenu>,
2427 window: &mut Window,
2428 cx: &mut Context<Self>,
2429 ) {
2430 self.mouse_context_menu = Some(MouseContextMenu::new(
2431 self,
2432 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2433 context_menu,
2434 window,
2435 cx,
2436 ));
2437 }
2438
2439 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2440 self.mouse_context_menu
2441 .as_ref()
2442 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2443 }
2444
2445 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2446 if self
2447 .selections
2448 .pending_anchor()
2449 .is_some_and(|pending_selection| {
2450 let snapshot = self.buffer().read(cx).snapshot(cx);
2451 pending_selection.range().includes(range, &snapshot)
2452 })
2453 {
2454 return true;
2455 }
2456
2457 self.selections
2458 .disjoint_in_range::<usize>(range.clone(), &self.display_snapshot(cx))
2459 .into_iter()
2460 .any(|selection| {
2461 // This is needed to cover a corner case, if we just check for an existing
2462 // selection in the fold range, having a cursor at the start of the fold
2463 // marks it as selected. Non-empty selections don't cause this.
2464 let length = selection.end - selection.start;
2465 length > 0
2466 })
2467 }
2468
2469 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2470 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2471 }
2472
2473 fn key_context_internal(
2474 &self,
2475 has_active_edit_prediction: bool,
2476 window: &mut Window,
2477 cx: &mut App,
2478 ) -> KeyContext {
2479 let mut key_context = KeyContext::new_with_defaults();
2480 key_context.add("Editor");
2481 let mode = match self.mode {
2482 EditorMode::SingleLine => "single_line",
2483 EditorMode::AutoHeight { .. } => "auto_height",
2484 EditorMode::Minimap { .. } => "minimap",
2485 EditorMode::Full { .. } => "full",
2486 };
2487
2488 if EditorSettings::jupyter_enabled(cx) {
2489 key_context.add("jupyter");
2490 }
2491
2492 key_context.set("mode", mode);
2493 if self.pending_rename.is_some() {
2494 key_context.add("renaming");
2495 }
2496
2497 match self.context_menu.borrow().as_ref() {
2498 Some(CodeContextMenu::Completions(menu)) => {
2499 if menu.visible() {
2500 key_context.add("menu");
2501 key_context.add("showing_completions");
2502 }
2503 }
2504 Some(CodeContextMenu::CodeActions(menu)) => {
2505 if menu.visible() {
2506 key_context.add("menu");
2507 key_context.add("showing_code_actions")
2508 }
2509 }
2510 None => {}
2511 }
2512
2513 if self.signature_help_state.has_multiple_signatures() {
2514 key_context.add("showing_signature_help");
2515 }
2516
2517 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2518 if !self.focus_handle(cx).contains_focused(window, cx)
2519 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2520 {
2521 for addon in self.addons.values() {
2522 addon.extend_key_context(&mut key_context, cx)
2523 }
2524 }
2525
2526 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2527 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2528 Some(
2529 file.full_path(cx)
2530 .extension()?
2531 .to_string_lossy()
2532 .into_owned(),
2533 )
2534 }) {
2535 key_context.set("extension", extension);
2536 }
2537 } else {
2538 key_context.add("multibuffer");
2539 }
2540
2541 if has_active_edit_prediction {
2542 if self.edit_prediction_in_conflict() {
2543 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2544 } else {
2545 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2546 key_context.add("copilot_suggestion");
2547 }
2548 }
2549
2550 if self.selection_mark_mode {
2551 key_context.add("selection_mode");
2552 }
2553
2554 let disjoint = self.selections.disjoint_anchors();
2555 let snapshot = self.snapshot(window, cx);
2556 let snapshot = snapshot.buffer_snapshot();
2557 if self.mode == EditorMode::SingleLine
2558 && let [selection] = disjoint
2559 && selection.start == selection.end
2560 && selection.end.to_offset(snapshot) == snapshot.len()
2561 {
2562 key_context.add("end_of_input");
2563 }
2564
2565 key_context
2566 }
2567
2568 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2569 self.last_bounds.as_ref()
2570 }
2571
2572 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2573 if self.mouse_cursor_hidden {
2574 self.mouse_cursor_hidden = false;
2575 cx.notify();
2576 }
2577 }
2578
2579 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2580 let hide_mouse_cursor = match origin {
2581 HideMouseCursorOrigin::TypingAction => {
2582 matches!(
2583 self.hide_mouse_mode,
2584 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2585 )
2586 }
2587 HideMouseCursorOrigin::MovementAction => {
2588 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2589 }
2590 };
2591 if self.mouse_cursor_hidden != hide_mouse_cursor {
2592 self.mouse_cursor_hidden = hide_mouse_cursor;
2593 cx.notify();
2594 }
2595 }
2596
2597 pub fn edit_prediction_in_conflict(&self) -> bool {
2598 if !self.show_edit_predictions_in_menu() {
2599 return false;
2600 }
2601
2602 let showing_completions = self
2603 .context_menu
2604 .borrow()
2605 .as_ref()
2606 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2607
2608 showing_completions
2609 || self.edit_prediction_requires_modifier()
2610 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2611 // bindings to insert tab characters.
2612 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2613 }
2614
2615 pub fn accept_edit_prediction_keybind(
2616 &self,
2617 accept_partial: bool,
2618 window: &mut Window,
2619 cx: &mut App,
2620 ) -> AcceptEditPredictionBinding {
2621 let key_context = self.key_context_internal(true, window, cx);
2622 let in_conflict = self.edit_prediction_in_conflict();
2623
2624 let bindings = if accept_partial {
2625 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2626 } else {
2627 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2628 };
2629
2630 // TODO: if the binding contains multiple keystrokes, display all of them, not
2631 // just the first one.
2632 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2633 !in_conflict
2634 || binding
2635 .keystrokes()
2636 .first()
2637 .is_some_and(|keystroke| keystroke.modifiers().modified())
2638 }))
2639 }
2640
2641 pub fn new_file(
2642 workspace: &mut Workspace,
2643 _: &workspace::NewFile,
2644 window: &mut Window,
2645 cx: &mut Context<Workspace>,
2646 ) {
2647 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2648 "Failed to create buffer",
2649 window,
2650 cx,
2651 |e, _, _| match e.error_code() {
2652 ErrorCode::RemoteUpgradeRequired => Some(format!(
2653 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2654 e.error_tag("required").unwrap_or("the latest version")
2655 )),
2656 _ => None,
2657 },
2658 );
2659 }
2660
2661 pub fn new_in_workspace(
2662 workspace: &mut Workspace,
2663 window: &mut Window,
2664 cx: &mut Context<Workspace>,
2665 ) -> Task<Result<Entity<Editor>>> {
2666 let project = workspace.project().clone();
2667 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2668
2669 cx.spawn_in(window, async move |workspace, cx| {
2670 let buffer = create.await?;
2671 workspace.update_in(cx, |workspace, window, cx| {
2672 let editor =
2673 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2674 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2675 editor
2676 })
2677 })
2678 }
2679
2680 fn new_file_vertical(
2681 workspace: &mut Workspace,
2682 _: &workspace::NewFileSplitVertical,
2683 window: &mut Window,
2684 cx: &mut Context<Workspace>,
2685 ) {
2686 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2687 }
2688
2689 fn new_file_horizontal(
2690 workspace: &mut Workspace,
2691 _: &workspace::NewFileSplitHorizontal,
2692 window: &mut Window,
2693 cx: &mut Context<Workspace>,
2694 ) {
2695 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2696 }
2697
2698 fn new_file_split(
2699 workspace: &mut Workspace,
2700 action: &workspace::NewFileSplit,
2701 window: &mut Window,
2702 cx: &mut Context<Workspace>,
2703 ) {
2704 Self::new_file_in_direction(workspace, action.0, window, cx)
2705 }
2706
2707 fn new_file_in_direction(
2708 workspace: &mut Workspace,
2709 direction: SplitDirection,
2710 window: &mut Window,
2711 cx: &mut Context<Workspace>,
2712 ) {
2713 let project = workspace.project().clone();
2714 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2715
2716 cx.spawn_in(window, async move |workspace, cx| {
2717 let buffer = create.await?;
2718 workspace.update_in(cx, move |workspace, window, cx| {
2719 workspace.split_item(
2720 direction,
2721 Box::new(
2722 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2723 ),
2724 window,
2725 cx,
2726 )
2727 })?;
2728 anyhow::Ok(())
2729 })
2730 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2731 match e.error_code() {
2732 ErrorCode::RemoteUpgradeRequired => Some(format!(
2733 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2734 e.error_tag("required").unwrap_or("the latest version")
2735 )),
2736 _ => None,
2737 }
2738 });
2739 }
2740
2741 pub fn leader_id(&self) -> Option<CollaboratorId> {
2742 self.leader_id
2743 }
2744
2745 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2746 &self.buffer
2747 }
2748
2749 pub fn project(&self) -> Option<&Entity<Project>> {
2750 self.project.as_ref()
2751 }
2752
2753 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2754 self.workspace.as_ref()?.0.upgrade()
2755 }
2756
2757 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2758 self.buffer().read(cx).title(cx)
2759 }
2760
2761 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2762 let git_blame_gutter_max_author_length = self
2763 .render_git_blame_gutter(cx)
2764 .then(|| {
2765 if let Some(blame) = self.blame.as_ref() {
2766 let max_author_length =
2767 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2768 Some(max_author_length)
2769 } else {
2770 None
2771 }
2772 })
2773 .flatten();
2774
2775 EditorSnapshot {
2776 mode: self.mode.clone(),
2777 show_gutter: self.show_gutter,
2778 show_line_numbers: self.show_line_numbers,
2779 show_git_diff_gutter: self.show_git_diff_gutter,
2780 show_code_actions: self.show_code_actions,
2781 show_runnables: self.show_runnables,
2782 show_breakpoints: self.show_breakpoints,
2783 git_blame_gutter_max_author_length,
2784 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2785 placeholder_display_snapshot: self
2786 .placeholder_display_map
2787 .as_ref()
2788 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2789 scroll_anchor: self.scroll_manager.anchor(),
2790 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2791 is_focused: self.focus_handle.is_focused(window),
2792 current_line_highlight: self
2793 .current_line_highlight
2794 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2795 gutter_hovered: self.gutter_hovered,
2796 }
2797 }
2798
2799 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2800 self.buffer.read(cx).language_at(point, cx)
2801 }
2802
2803 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2804 self.buffer.read(cx).read(cx).file_at(point).cloned()
2805 }
2806
2807 pub fn active_excerpt(
2808 &self,
2809 cx: &App,
2810 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2811 self.buffer
2812 .read(cx)
2813 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2814 }
2815
2816 pub fn mode(&self) -> &EditorMode {
2817 &self.mode
2818 }
2819
2820 pub fn set_mode(&mut self, mode: EditorMode) {
2821 self.mode = mode;
2822 }
2823
2824 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2825 self.collaboration_hub.as_deref()
2826 }
2827
2828 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2829 self.collaboration_hub = Some(hub);
2830 }
2831
2832 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2833 self.in_project_search = in_project_search;
2834 }
2835
2836 pub fn set_custom_context_menu(
2837 &mut self,
2838 f: impl 'static
2839 + Fn(
2840 &mut Self,
2841 DisplayPoint,
2842 &mut Window,
2843 &mut Context<Self>,
2844 ) -> Option<Entity<ui::ContextMenu>>,
2845 ) {
2846 self.custom_context_menu = Some(Box::new(f))
2847 }
2848
2849 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2850 self.completion_provider = provider;
2851 }
2852
2853 #[cfg(any(test, feature = "test-support"))]
2854 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2855 self.completion_provider.clone()
2856 }
2857
2858 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2859 self.semantics_provider.clone()
2860 }
2861
2862 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2863 self.semantics_provider = provider;
2864 }
2865
2866 pub fn set_edit_prediction_provider<T>(
2867 &mut self,
2868 provider: Option<Entity<T>>,
2869 window: &mut Window,
2870 cx: &mut Context<Self>,
2871 ) where
2872 T: EditPredictionProvider,
2873 {
2874 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2875 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2876 if this.focus_handle.is_focused(window) {
2877 this.update_visible_edit_prediction(window, cx);
2878 }
2879 }),
2880 provider: Arc::new(provider),
2881 });
2882 self.update_edit_prediction_settings(cx);
2883 self.refresh_edit_prediction(false, false, window, cx);
2884 }
2885
2886 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2887 self.placeholder_display_map
2888 .as_ref()
2889 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2890 }
2891
2892 pub fn set_placeholder_text(
2893 &mut self,
2894 placeholder_text: &str,
2895 window: &mut Window,
2896 cx: &mut Context<Self>,
2897 ) {
2898 let multibuffer = cx
2899 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2900
2901 let style = window.text_style();
2902
2903 self.placeholder_display_map = Some(cx.new(|cx| {
2904 DisplayMap::new(
2905 multibuffer,
2906 style.font(),
2907 style.font_size.to_pixels(window.rem_size()),
2908 None,
2909 FILE_HEADER_HEIGHT,
2910 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2911 Default::default(),
2912 DiagnosticSeverity::Off,
2913 cx,
2914 )
2915 }));
2916 cx.notify();
2917 }
2918
2919 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2920 self.cursor_shape = cursor_shape;
2921
2922 // Disrupt blink for immediate user feedback that the cursor shape has changed
2923 self.blink_manager.update(cx, BlinkManager::show_cursor);
2924
2925 cx.notify();
2926 }
2927
2928 pub fn set_current_line_highlight(
2929 &mut self,
2930 current_line_highlight: Option<CurrentLineHighlight>,
2931 ) {
2932 self.current_line_highlight = current_line_highlight;
2933 }
2934
2935 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2936 self.collapse_matches = collapse_matches;
2937 }
2938
2939 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2940 if self.collapse_matches {
2941 return range.start..range.start;
2942 }
2943 range.clone()
2944 }
2945
2946 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2947 if self.display_map.read(cx).clip_at_line_ends != clip {
2948 self.display_map
2949 .update(cx, |map, _| map.clip_at_line_ends = clip);
2950 }
2951 }
2952
2953 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2954 self.input_enabled = input_enabled;
2955 }
2956
2957 pub fn set_edit_predictions_hidden_for_vim_mode(
2958 &mut self,
2959 hidden: bool,
2960 window: &mut Window,
2961 cx: &mut Context<Self>,
2962 ) {
2963 if hidden != self.edit_predictions_hidden_for_vim_mode {
2964 self.edit_predictions_hidden_for_vim_mode = hidden;
2965 if hidden {
2966 self.update_visible_edit_prediction(window, cx);
2967 } else {
2968 self.refresh_edit_prediction(true, false, window, cx);
2969 }
2970 }
2971 }
2972
2973 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2974 self.menu_edit_predictions_policy = value;
2975 }
2976
2977 pub fn set_autoindent(&mut self, autoindent: bool) {
2978 if autoindent {
2979 self.autoindent_mode = Some(AutoindentMode::EachLine);
2980 } else {
2981 self.autoindent_mode = None;
2982 }
2983 }
2984
2985 pub fn read_only(&self, cx: &App) -> bool {
2986 self.read_only || self.buffer.read(cx).read_only()
2987 }
2988
2989 pub fn set_read_only(&mut self, read_only: bool) {
2990 self.read_only = read_only;
2991 }
2992
2993 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2994 self.use_autoclose = autoclose;
2995 }
2996
2997 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2998 self.use_auto_surround = auto_surround;
2999 }
3000
3001 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3002 self.auto_replace_emoji_shortcode = auto_replace;
3003 }
3004
3005 pub fn toggle_edit_predictions(
3006 &mut self,
3007 _: &ToggleEditPrediction,
3008 window: &mut Window,
3009 cx: &mut Context<Self>,
3010 ) {
3011 if self.show_edit_predictions_override.is_some() {
3012 self.set_show_edit_predictions(None, window, cx);
3013 } else {
3014 let show_edit_predictions = !self.edit_predictions_enabled();
3015 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3016 }
3017 }
3018
3019 pub fn set_show_edit_predictions(
3020 &mut self,
3021 show_edit_predictions: Option<bool>,
3022 window: &mut Window,
3023 cx: &mut Context<Self>,
3024 ) {
3025 self.show_edit_predictions_override = show_edit_predictions;
3026 self.update_edit_prediction_settings(cx);
3027
3028 if let Some(false) = show_edit_predictions {
3029 self.discard_edit_prediction(false, cx);
3030 } else {
3031 self.refresh_edit_prediction(false, true, window, cx);
3032 }
3033 }
3034
3035 fn edit_predictions_disabled_in_scope(
3036 &self,
3037 buffer: &Entity<Buffer>,
3038 buffer_position: language::Anchor,
3039 cx: &App,
3040 ) -> bool {
3041 let snapshot = buffer.read(cx).snapshot();
3042 let settings = snapshot.settings_at(buffer_position, cx);
3043
3044 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3045 return false;
3046 };
3047
3048 scope.override_name().is_some_and(|scope_name| {
3049 settings
3050 .edit_predictions_disabled_in
3051 .iter()
3052 .any(|s| s == scope_name)
3053 })
3054 }
3055
3056 pub fn set_use_modal_editing(&mut self, to: bool) {
3057 self.use_modal_editing = to;
3058 }
3059
3060 pub fn use_modal_editing(&self) -> bool {
3061 self.use_modal_editing
3062 }
3063
3064 fn selections_did_change(
3065 &mut self,
3066 local: bool,
3067 old_cursor_position: &Anchor,
3068 effects: SelectionEffects,
3069 window: &mut Window,
3070 cx: &mut Context<Self>,
3071 ) {
3072 window.invalidate_character_coordinates();
3073
3074 // Copy selections to primary selection buffer
3075 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3076 if local {
3077 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3078 let buffer_handle = self.buffer.read(cx).read(cx);
3079
3080 let mut text = String::new();
3081 for (index, selection) in selections.iter().enumerate() {
3082 let text_for_selection = buffer_handle
3083 .text_for_range(selection.start..selection.end)
3084 .collect::<String>();
3085
3086 text.push_str(&text_for_selection);
3087 if index != selections.len() - 1 {
3088 text.push('\n');
3089 }
3090 }
3091
3092 if !text.is_empty() {
3093 cx.write_to_primary(ClipboardItem::new_string(text));
3094 }
3095 }
3096
3097 let selection_anchors = self.selections.disjoint_anchors_arc();
3098
3099 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3100 self.buffer.update(cx, |buffer, cx| {
3101 buffer.set_active_selections(
3102 &selection_anchors,
3103 self.selections.line_mode(),
3104 self.cursor_shape,
3105 cx,
3106 )
3107 });
3108 }
3109 let display_map = self
3110 .display_map
3111 .update(cx, |display_map, cx| display_map.snapshot(cx));
3112 let buffer = display_map.buffer_snapshot();
3113 if self.selections.count() == 1 {
3114 self.add_selections_state = None;
3115 }
3116 self.select_next_state = None;
3117 self.select_prev_state = None;
3118 self.select_syntax_node_history.try_clear();
3119 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3120 self.snippet_stack.invalidate(&selection_anchors, buffer);
3121 self.take_rename(false, window, cx);
3122
3123 let newest_selection = self.selections.newest_anchor();
3124 let new_cursor_position = newest_selection.head();
3125 let selection_start = newest_selection.start;
3126
3127 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3128 self.push_to_nav_history(
3129 *old_cursor_position,
3130 Some(new_cursor_position.to_point(buffer)),
3131 false,
3132 effects.nav_history == Some(true),
3133 cx,
3134 );
3135 }
3136
3137 if local {
3138 if let Some(buffer_id) = new_cursor_position.buffer_id {
3139 self.register_buffer(buffer_id, cx);
3140 }
3141
3142 let mut context_menu = self.context_menu.borrow_mut();
3143 let completion_menu = match context_menu.as_ref() {
3144 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3145 Some(CodeContextMenu::CodeActions(_)) => {
3146 *context_menu = None;
3147 None
3148 }
3149 None => None,
3150 };
3151 let completion_position = completion_menu.map(|menu| menu.initial_position);
3152 drop(context_menu);
3153
3154 if effects.completions
3155 && let Some(completion_position) = completion_position
3156 {
3157 let start_offset = selection_start.to_offset(buffer);
3158 let position_matches = start_offset == completion_position.to_offset(buffer);
3159 let continue_showing = if position_matches {
3160 if self.snippet_stack.is_empty() {
3161 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3162 == Some(CharKind::Word)
3163 } else {
3164 // Snippet choices can be shown even when the cursor is in whitespace.
3165 // Dismissing the menu with actions like backspace is handled by
3166 // invalidation regions.
3167 true
3168 }
3169 } else {
3170 false
3171 };
3172
3173 if continue_showing {
3174 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3175 } else {
3176 self.hide_context_menu(window, cx);
3177 }
3178 }
3179
3180 hide_hover(self, cx);
3181
3182 if old_cursor_position.to_display_point(&display_map).row()
3183 != new_cursor_position.to_display_point(&display_map).row()
3184 {
3185 self.available_code_actions.take();
3186 }
3187 self.refresh_code_actions(window, cx);
3188 self.refresh_document_highlights(cx);
3189 refresh_linked_ranges(self, window, cx);
3190
3191 self.refresh_selected_text_highlights(false, window, cx);
3192 refresh_matching_bracket_highlights(self, cx);
3193 self.update_visible_edit_prediction(window, cx);
3194 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3195 self.inline_blame_popover.take();
3196 if self.git_blame_inline_enabled {
3197 self.start_inline_blame_timer(window, cx);
3198 }
3199 }
3200
3201 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3202 cx.emit(EditorEvent::SelectionsChanged { local });
3203
3204 let selections = &self.selections.disjoint_anchors_arc();
3205 if selections.len() == 1 {
3206 cx.emit(SearchEvent::ActiveMatchChanged)
3207 }
3208 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3209 let inmemory_selections = selections
3210 .iter()
3211 .map(|s| {
3212 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3213 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3214 })
3215 .collect();
3216 self.update_restoration_data(cx, |data| {
3217 data.selections = inmemory_selections;
3218 });
3219
3220 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3221 && let Some(workspace_id) =
3222 self.workspace.as_ref().and_then(|workspace| workspace.1)
3223 {
3224 let snapshot = self.buffer().read(cx).snapshot(cx);
3225 let selections = selections.clone();
3226 let background_executor = cx.background_executor().clone();
3227 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3228 self.serialize_selections = cx.background_spawn(async move {
3229 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3230 let db_selections = selections
3231 .iter()
3232 .map(|selection| {
3233 (
3234 selection.start.to_offset(&snapshot),
3235 selection.end.to_offset(&snapshot),
3236 )
3237 })
3238 .collect();
3239
3240 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3241 .await
3242 .with_context(|| {
3243 format!(
3244 "persisting editor selections for editor {editor_id}, \
3245 workspace {workspace_id:?}"
3246 )
3247 })
3248 .log_err();
3249 });
3250 }
3251 }
3252
3253 cx.notify();
3254 }
3255
3256 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3257 use text::ToOffset as _;
3258 use text::ToPoint as _;
3259
3260 if self.mode.is_minimap()
3261 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3262 {
3263 return;
3264 }
3265
3266 if !self.buffer().read(cx).is_singleton() {
3267 return;
3268 }
3269
3270 let display_snapshot = self
3271 .display_map
3272 .update(cx, |display_map, cx| display_map.snapshot(cx));
3273 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3274 return;
3275 };
3276 let inmemory_folds = display_snapshot
3277 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3278 .map(|fold| {
3279 fold.range.start.text_anchor.to_point(&snapshot)
3280 ..fold.range.end.text_anchor.to_point(&snapshot)
3281 })
3282 .collect();
3283 self.update_restoration_data(cx, |data| {
3284 data.folds = inmemory_folds;
3285 });
3286
3287 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3288 return;
3289 };
3290 let background_executor = cx.background_executor().clone();
3291 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3292 let db_folds = display_snapshot
3293 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3294 .map(|fold| {
3295 (
3296 fold.range.start.text_anchor.to_offset(&snapshot),
3297 fold.range.end.text_anchor.to_offset(&snapshot),
3298 )
3299 })
3300 .collect();
3301 self.serialize_folds = cx.background_spawn(async move {
3302 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3303 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3304 .await
3305 .with_context(|| {
3306 format!(
3307 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3308 )
3309 })
3310 .log_err();
3311 });
3312 }
3313
3314 pub fn sync_selections(
3315 &mut self,
3316 other: Entity<Editor>,
3317 cx: &mut Context<Self>,
3318 ) -> gpui::Subscription {
3319 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3320 if !other_selections.is_empty() {
3321 self.selections.change_with(cx, |selections| {
3322 selections.select_anchors(other_selections);
3323 });
3324 }
3325
3326 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3327 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3328 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3329 if other_selections.is_empty() {
3330 return;
3331 }
3332 this.selections.change_with(cx, |selections| {
3333 selections.select_anchors(other_selections);
3334 });
3335 }
3336 });
3337
3338 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3339 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3340 let these_selections = this.selections.disjoint_anchors().to_vec();
3341 if these_selections.is_empty() {
3342 return;
3343 }
3344 other.update(cx, |other_editor, cx| {
3345 other_editor.selections.change_with(cx, |selections| {
3346 selections.select_anchors(these_selections);
3347 })
3348 });
3349 }
3350 });
3351
3352 Subscription::join(other_subscription, this_subscription)
3353 }
3354
3355 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3356 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3357 /// effects of selection change occur at the end of the transaction.
3358 pub fn change_selections<R>(
3359 &mut self,
3360 effects: SelectionEffects,
3361 window: &mut Window,
3362 cx: &mut Context<Self>,
3363 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3364 ) -> R {
3365 if let Some(state) = &mut self.deferred_selection_effects_state {
3366 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3367 state.effects.completions = effects.completions;
3368 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3369 let (changed, result) = self.selections.change_with(cx, change);
3370 state.changed |= changed;
3371 return result;
3372 }
3373 let mut state = DeferredSelectionEffectsState {
3374 changed: false,
3375 effects,
3376 old_cursor_position: self.selections.newest_anchor().head(),
3377 history_entry: SelectionHistoryEntry {
3378 selections: self.selections.disjoint_anchors_arc(),
3379 select_next_state: self.select_next_state.clone(),
3380 select_prev_state: self.select_prev_state.clone(),
3381 add_selections_state: self.add_selections_state.clone(),
3382 },
3383 };
3384 let (changed, result) = self.selections.change_with(cx, change);
3385 state.changed = state.changed || changed;
3386 if self.defer_selection_effects {
3387 self.deferred_selection_effects_state = Some(state);
3388 } else {
3389 self.apply_selection_effects(state, window, cx);
3390 }
3391 result
3392 }
3393
3394 /// Defers the effects of selection change, so that the effects of multiple calls to
3395 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3396 /// to selection history and the state of popovers based on selection position aren't
3397 /// erroneously updated.
3398 pub fn with_selection_effects_deferred<R>(
3399 &mut self,
3400 window: &mut Window,
3401 cx: &mut Context<Self>,
3402 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3403 ) -> R {
3404 let already_deferred = self.defer_selection_effects;
3405 self.defer_selection_effects = true;
3406 let result = update(self, window, cx);
3407 if !already_deferred {
3408 self.defer_selection_effects = false;
3409 if let Some(state) = self.deferred_selection_effects_state.take() {
3410 self.apply_selection_effects(state, window, cx);
3411 }
3412 }
3413 result
3414 }
3415
3416 fn apply_selection_effects(
3417 &mut self,
3418 state: DeferredSelectionEffectsState,
3419 window: &mut Window,
3420 cx: &mut Context<Self>,
3421 ) {
3422 if state.changed {
3423 self.selection_history.push(state.history_entry);
3424
3425 if let Some(autoscroll) = state.effects.scroll {
3426 self.request_autoscroll(autoscroll, cx);
3427 }
3428
3429 let old_cursor_position = &state.old_cursor_position;
3430
3431 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3432
3433 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3434 self.show_signature_help(&ShowSignatureHelp, window, cx);
3435 }
3436 }
3437 }
3438
3439 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3440 where
3441 I: IntoIterator<Item = (Range<S>, T)>,
3442 S: ToOffset,
3443 T: Into<Arc<str>>,
3444 {
3445 if self.read_only(cx) {
3446 return;
3447 }
3448
3449 self.buffer
3450 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3451 }
3452
3453 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3454 where
3455 I: IntoIterator<Item = (Range<S>, T)>,
3456 S: ToOffset,
3457 T: Into<Arc<str>>,
3458 {
3459 if self.read_only(cx) {
3460 return;
3461 }
3462
3463 self.buffer.update(cx, |buffer, cx| {
3464 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3465 });
3466 }
3467
3468 pub fn edit_with_block_indent<I, S, T>(
3469 &mut self,
3470 edits: I,
3471 original_indent_columns: Vec<Option<u32>>,
3472 cx: &mut Context<Self>,
3473 ) where
3474 I: IntoIterator<Item = (Range<S>, T)>,
3475 S: ToOffset,
3476 T: Into<Arc<str>>,
3477 {
3478 if self.read_only(cx) {
3479 return;
3480 }
3481
3482 self.buffer.update(cx, |buffer, cx| {
3483 buffer.edit(
3484 edits,
3485 Some(AutoindentMode::Block {
3486 original_indent_columns,
3487 }),
3488 cx,
3489 )
3490 });
3491 }
3492
3493 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3494 self.hide_context_menu(window, cx);
3495
3496 match phase {
3497 SelectPhase::Begin {
3498 position,
3499 add,
3500 click_count,
3501 } => self.begin_selection(position, add, click_count, window, cx),
3502 SelectPhase::BeginColumnar {
3503 position,
3504 goal_column,
3505 reset,
3506 mode,
3507 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3508 SelectPhase::Extend {
3509 position,
3510 click_count,
3511 } => self.extend_selection(position, click_count, window, cx),
3512 SelectPhase::Update {
3513 position,
3514 goal_column,
3515 scroll_delta,
3516 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3517 SelectPhase::End => self.end_selection(window, cx),
3518 }
3519 }
3520
3521 fn extend_selection(
3522 &mut self,
3523 position: DisplayPoint,
3524 click_count: usize,
3525 window: &mut Window,
3526 cx: &mut Context<Self>,
3527 ) {
3528 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3529 let tail = self.selections.newest::<usize>(&display_map).tail();
3530 let click_count = click_count.max(match self.selections.select_mode() {
3531 SelectMode::Character => 1,
3532 SelectMode::Word(_) => 2,
3533 SelectMode::Line(_) => 3,
3534 SelectMode::All => 4,
3535 });
3536 self.begin_selection(position, false, click_count, window, cx);
3537
3538 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3539
3540 let current_selection = match self.selections.select_mode() {
3541 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3542 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3543 };
3544
3545 let mut pending_selection = self
3546 .selections
3547 .pending_anchor()
3548 .cloned()
3549 .expect("extend_selection not called with pending selection");
3550
3551 if pending_selection
3552 .start
3553 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3554 == Ordering::Greater
3555 {
3556 pending_selection.start = current_selection.start;
3557 }
3558 if pending_selection
3559 .end
3560 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3561 == Ordering::Less
3562 {
3563 pending_selection.end = current_selection.end;
3564 pending_selection.reversed = true;
3565 }
3566
3567 let mut pending_mode = self.selections.pending_mode().unwrap();
3568 match &mut pending_mode {
3569 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3570 _ => {}
3571 }
3572
3573 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3574 SelectionEffects::scroll(Autoscroll::fit())
3575 } else {
3576 SelectionEffects::no_scroll()
3577 };
3578
3579 self.change_selections(effects, window, cx, |s| {
3580 s.set_pending(pending_selection.clone(), pending_mode);
3581 s.set_is_extending(true);
3582 });
3583 }
3584
3585 fn begin_selection(
3586 &mut self,
3587 position: DisplayPoint,
3588 add: bool,
3589 click_count: usize,
3590 window: &mut Window,
3591 cx: &mut Context<Self>,
3592 ) {
3593 if !self.focus_handle.is_focused(window) {
3594 self.last_focused_descendant = None;
3595 window.focus(&self.focus_handle);
3596 }
3597
3598 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3599 let buffer = display_map.buffer_snapshot();
3600 let position = display_map.clip_point(position, Bias::Left);
3601
3602 let start;
3603 let end;
3604 let mode;
3605 let mut auto_scroll;
3606 match click_count {
3607 1 => {
3608 start = buffer.anchor_before(position.to_point(&display_map));
3609 end = start;
3610 mode = SelectMode::Character;
3611 auto_scroll = true;
3612 }
3613 2 => {
3614 let position = display_map
3615 .clip_point(position, Bias::Left)
3616 .to_offset(&display_map, Bias::Left);
3617 let (range, _) = buffer.surrounding_word(position, None);
3618 start = buffer.anchor_before(range.start);
3619 end = buffer.anchor_before(range.end);
3620 mode = SelectMode::Word(start..end);
3621 auto_scroll = true;
3622 }
3623 3 => {
3624 let position = display_map
3625 .clip_point(position, Bias::Left)
3626 .to_point(&display_map);
3627 let line_start = display_map.prev_line_boundary(position).0;
3628 let next_line_start = buffer.clip_point(
3629 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3630 Bias::Left,
3631 );
3632 start = buffer.anchor_before(line_start);
3633 end = buffer.anchor_before(next_line_start);
3634 mode = SelectMode::Line(start..end);
3635 auto_scroll = true;
3636 }
3637 _ => {
3638 start = buffer.anchor_before(0);
3639 end = buffer.anchor_before(buffer.len());
3640 mode = SelectMode::All;
3641 auto_scroll = false;
3642 }
3643 }
3644 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3645
3646 let point_to_delete: Option<usize> = {
3647 let selected_points: Vec<Selection<Point>> =
3648 self.selections.disjoint_in_range(start..end, &display_map);
3649
3650 if !add || click_count > 1 {
3651 None
3652 } else if !selected_points.is_empty() {
3653 Some(selected_points[0].id)
3654 } else {
3655 let clicked_point_already_selected =
3656 self.selections.disjoint_anchors().iter().find(|selection| {
3657 selection.start.to_point(buffer) == start.to_point(buffer)
3658 || selection.end.to_point(buffer) == end.to_point(buffer)
3659 });
3660
3661 clicked_point_already_selected.map(|selection| selection.id)
3662 }
3663 };
3664
3665 let selections_count = self.selections.count();
3666 let effects = if auto_scroll {
3667 SelectionEffects::default()
3668 } else {
3669 SelectionEffects::no_scroll()
3670 };
3671
3672 self.change_selections(effects, window, cx, |s| {
3673 if let Some(point_to_delete) = point_to_delete {
3674 s.delete(point_to_delete);
3675
3676 if selections_count == 1 {
3677 s.set_pending_anchor_range(start..end, mode);
3678 }
3679 } else {
3680 if !add {
3681 s.clear_disjoint();
3682 }
3683
3684 s.set_pending_anchor_range(start..end, mode);
3685 }
3686 });
3687 }
3688
3689 fn begin_columnar_selection(
3690 &mut self,
3691 position: DisplayPoint,
3692 goal_column: u32,
3693 reset: bool,
3694 mode: ColumnarMode,
3695 window: &mut Window,
3696 cx: &mut Context<Self>,
3697 ) {
3698 if !self.focus_handle.is_focused(window) {
3699 self.last_focused_descendant = None;
3700 window.focus(&self.focus_handle);
3701 }
3702
3703 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3704
3705 if reset {
3706 let pointer_position = display_map
3707 .buffer_snapshot()
3708 .anchor_before(position.to_point(&display_map));
3709
3710 self.change_selections(
3711 SelectionEffects::scroll(Autoscroll::newest()),
3712 window,
3713 cx,
3714 |s| {
3715 s.clear_disjoint();
3716 s.set_pending_anchor_range(
3717 pointer_position..pointer_position,
3718 SelectMode::Character,
3719 );
3720 },
3721 );
3722 };
3723
3724 let tail = self.selections.newest::<Point>(&display_map).tail();
3725 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3726 self.columnar_selection_state = match mode {
3727 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3728 selection_tail: selection_anchor,
3729 display_point: if reset {
3730 if position.column() != goal_column {
3731 Some(DisplayPoint::new(position.row(), goal_column))
3732 } else {
3733 None
3734 }
3735 } else {
3736 None
3737 },
3738 }),
3739 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3740 selection_tail: selection_anchor,
3741 }),
3742 };
3743
3744 if !reset {
3745 self.select_columns(position, goal_column, &display_map, window, cx);
3746 }
3747 }
3748
3749 fn update_selection(
3750 &mut self,
3751 position: DisplayPoint,
3752 goal_column: u32,
3753 scroll_delta: gpui::Point<f32>,
3754 window: &mut Window,
3755 cx: &mut Context<Self>,
3756 ) {
3757 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3758
3759 if self.columnar_selection_state.is_some() {
3760 self.select_columns(position, goal_column, &display_map, window, cx);
3761 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3762 let buffer = display_map.buffer_snapshot();
3763 let head;
3764 let tail;
3765 let mode = self.selections.pending_mode().unwrap();
3766 match &mode {
3767 SelectMode::Character => {
3768 head = position.to_point(&display_map);
3769 tail = pending.tail().to_point(buffer);
3770 }
3771 SelectMode::Word(original_range) => {
3772 let offset = display_map
3773 .clip_point(position, Bias::Left)
3774 .to_offset(&display_map, Bias::Left);
3775 let original_range = original_range.to_offset(buffer);
3776
3777 let head_offset = if buffer.is_inside_word(offset, None)
3778 || original_range.contains(&offset)
3779 {
3780 let (word_range, _) = buffer.surrounding_word(offset, None);
3781 if word_range.start < original_range.start {
3782 word_range.start
3783 } else {
3784 word_range.end
3785 }
3786 } else {
3787 offset
3788 };
3789
3790 head = head_offset.to_point(buffer);
3791 if head_offset <= original_range.start {
3792 tail = original_range.end.to_point(buffer);
3793 } else {
3794 tail = original_range.start.to_point(buffer);
3795 }
3796 }
3797 SelectMode::Line(original_range) => {
3798 let original_range = original_range.to_point(display_map.buffer_snapshot());
3799
3800 let position = display_map
3801 .clip_point(position, Bias::Left)
3802 .to_point(&display_map);
3803 let line_start = display_map.prev_line_boundary(position).0;
3804 let next_line_start = buffer.clip_point(
3805 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3806 Bias::Left,
3807 );
3808
3809 if line_start < original_range.start {
3810 head = line_start
3811 } else {
3812 head = next_line_start
3813 }
3814
3815 if head <= original_range.start {
3816 tail = original_range.end;
3817 } else {
3818 tail = original_range.start;
3819 }
3820 }
3821 SelectMode::All => {
3822 return;
3823 }
3824 };
3825
3826 if head < tail {
3827 pending.start = buffer.anchor_before(head);
3828 pending.end = buffer.anchor_before(tail);
3829 pending.reversed = true;
3830 } else {
3831 pending.start = buffer.anchor_before(tail);
3832 pending.end = buffer.anchor_before(head);
3833 pending.reversed = false;
3834 }
3835
3836 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3837 s.set_pending(pending.clone(), mode);
3838 });
3839 } else {
3840 log::error!("update_selection dispatched with no pending selection");
3841 return;
3842 }
3843
3844 self.apply_scroll_delta(scroll_delta, window, cx);
3845 cx.notify();
3846 }
3847
3848 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3849 self.columnar_selection_state.take();
3850 if let Some(pending_mode) = self.selections.pending_mode() {
3851 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3852 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3853 s.select(selections);
3854 s.clear_pending();
3855 if s.is_extending() {
3856 s.set_is_extending(false);
3857 } else {
3858 s.set_select_mode(pending_mode);
3859 }
3860 });
3861 }
3862 }
3863
3864 fn select_columns(
3865 &mut self,
3866 head: DisplayPoint,
3867 goal_column: u32,
3868 display_map: &DisplaySnapshot,
3869 window: &mut Window,
3870 cx: &mut Context<Self>,
3871 ) {
3872 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3873 return;
3874 };
3875
3876 let tail = match columnar_state {
3877 ColumnarSelectionState::FromMouse {
3878 selection_tail,
3879 display_point,
3880 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3881 ColumnarSelectionState::FromSelection { selection_tail } => {
3882 selection_tail.to_display_point(display_map)
3883 }
3884 };
3885
3886 let start_row = cmp::min(tail.row(), head.row());
3887 let end_row = cmp::max(tail.row(), head.row());
3888 let start_column = cmp::min(tail.column(), goal_column);
3889 let end_column = cmp::max(tail.column(), goal_column);
3890 let reversed = start_column < tail.column();
3891
3892 let selection_ranges = (start_row.0..=end_row.0)
3893 .map(DisplayRow)
3894 .filter_map(|row| {
3895 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3896 || start_column <= display_map.line_len(row))
3897 && !display_map.is_block_line(row)
3898 {
3899 let start = display_map
3900 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3901 .to_point(display_map);
3902 let end = display_map
3903 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3904 .to_point(display_map);
3905 if reversed {
3906 Some(end..start)
3907 } else {
3908 Some(start..end)
3909 }
3910 } else {
3911 None
3912 }
3913 })
3914 .collect::<Vec<_>>();
3915 if selection_ranges.is_empty() {
3916 return;
3917 }
3918
3919 let ranges = match columnar_state {
3920 ColumnarSelectionState::FromMouse { .. } => {
3921 let mut non_empty_ranges = selection_ranges
3922 .iter()
3923 .filter(|selection_range| selection_range.start != selection_range.end)
3924 .peekable();
3925 if non_empty_ranges.peek().is_some() {
3926 non_empty_ranges.cloned().collect()
3927 } else {
3928 selection_ranges
3929 }
3930 }
3931 _ => selection_ranges,
3932 };
3933
3934 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3935 s.select_ranges(ranges);
3936 });
3937 cx.notify();
3938 }
3939
3940 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
3941 self.selections
3942 .all_adjusted(snapshot)
3943 .iter()
3944 .any(|selection| !selection.is_empty())
3945 }
3946
3947 pub fn has_pending_nonempty_selection(&self) -> bool {
3948 let pending_nonempty_selection = match self.selections.pending_anchor() {
3949 Some(Selection { start, end, .. }) => start != end,
3950 None => false,
3951 };
3952
3953 pending_nonempty_selection
3954 || (self.columnar_selection_state.is_some()
3955 && self.selections.disjoint_anchors().len() > 1)
3956 }
3957
3958 pub fn has_pending_selection(&self) -> bool {
3959 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3960 }
3961
3962 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3963 self.selection_mark_mode = false;
3964 self.selection_drag_state = SelectionDragState::None;
3965
3966 if self.clear_expanded_diff_hunks(cx) {
3967 cx.notify();
3968 return;
3969 }
3970 if self.dismiss_menus_and_popups(true, window, cx) {
3971 return;
3972 }
3973
3974 if self.mode.is_full()
3975 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3976 {
3977 return;
3978 }
3979
3980 cx.propagate();
3981 }
3982
3983 pub fn dismiss_menus_and_popups(
3984 &mut self,
3985 is_user_requested: bool,
3986 window: &mut Window,
3987 cx: &mut Context<Self>,
3988 ) -> bool {
3989 if self.take_rename(false, window, cx).is_some() {
3990 return true;
3991 }
3992
3993 if self.hide_blame_popover(true, cx) {
3994 return true;
3995 }
3996
3997 if hide_hover(self, cx) {
3998 return true;
3999 }
4000
4001 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
4002 return true;
4003 }
4004
4005 if self.hide_context_menu(window, cx).is_some() {
4006 return true;
4007 }
4008
4009 if self.mouse_context_menu.take().is_some() {
4010 return true;
4011 }
4012
4013 if is_user_requested && self.discard_edit_prediction(true, cx) {
4014 return true;
4015 }
4016
4017 if self.snippet_stack.pop().is_some() {
4018 return true;
4019 }
4020
4021 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4022 self.dismiss_diagnostics(cx);
4023 return true;
4024 }
4025
4026 false
4027 }
4028
4029 fn linked_editing_ranges_for(
4030 &self,
4031 selection: Range<text::Anchor>,
4032 cx: &App,
4033 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4034 if self.linked_edit_ranges.is_empty() {
4035 return None;
4036 }
4037 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4038 selection.end.buffer_id.and_then(|end_buffer_id| {
4039 if selection.start.buffer_id != Some(end_buffer_id) {
4040 return None;
4041 }
4042 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4043 let snapshot = buffer.read(cx).snapshot();
4044 self.linked_edit_ranges
4045 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4046 .map(|ranges| (ranges, snapshot, buffer))
4047 })?;
4048 use text::ToOffset as TO;
4049 // find offset from the start of current range to current cursor position
4050 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4051
4052 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4053 let start_difference = start_offset - start_byte_offset;
4054 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4055 let end_difference = end_offset - start_byte_offset;
4056 // Current range has associated linked ranges.
4057 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4058 for range in linked_ranges.iter() {
4059 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4060 let end_offset = start_offset + end_difference;
4061 let start_offset = start_offset + start_difference;
4062 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4063 continue;
4064 }
4065 if self.selections.disjoint_anchor_ranges().any(|s| {
4066 if s.start.buffer_id != selection.start.buffer_id
4067 || s.end.buffer_id != selection.end.buffer_id
4068 {
4069 return false;
4070 }
4071 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4072 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4073 }) {
4074 continue;
4075 }
4076 let start = buffer_snapshot.anchor_after(start_offset);
4077 let end = buffer_snapshot.anchor_after(end_offset);
4078 linked_edits
4079 .entry(buffer.clone())
4080 .or_default()
4081 .push(start..end);
4082 }
4083 Some(linked_edits)
4084 }
4085
4086 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4087 let text: Arc<str> = text.into();
4088
4089 if self.read_only(cx) {
4090 return;
4091 }
4092
4093 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4094
4095 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4096 let mut bracket_inserted = false;
4097 let mut edits = Vec::new();
4098 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4099 let mut new_selections = Vec::with_capacity(selections.len());
4100 let mut new_autoclose_regions = Vec::new();
4101 let snapshot = self.buffer.read(cx).read(cx);
4102 let mut clear_linked_edit_ranges = false;
4103
4104 for (selection, autoclose_region) in
4105 self.selections_with_autoclose_regions(selections, &snapshot)
4106 {
4107 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4108 // Determine if the inserted text matches the opening or closing
4109 // bracket of any of this language's bracket pairs.
4110 let mut bracket_pair = None;
4111 let mut is_bracket_pair_start = false;
4112 let mut is_bracket_pair_end = false;
4113 if !text.is_empty() {
4114 let mut bracket_pair_matching_end = None;
4115 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4116 // and they are removing the character that triggered IME popup.
4117 for (pair, enabled) in scope.brackets() {
4118 if !pair.close && !pair.surround {
4119 continue;
4120 }
4121
4122 if enabled && pair.start.ends_with(text.as_ref()) {
4123 let prefix_len = pair.start.len() - text.len();
4124 let preceding_text_matches_prefix = prefix_len == 0
4125 || (selection.start.column >= (prefix_len as u32)
4126 && snapshot.contains_str_at(
4127 Point::new(
4128 selection.start.row,
4129 selection.start.column - (prefix_len as u32),
4130 ),
4131 &pair.start[..prefix_len],
4132 ));
4133 if preceding_text_matches_prefix {
4134 bracket_pair = Some(pair.clone());
4135 is_bracket_pair_start = true;
4136 break;
4137 }
4138 }
4139 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4140 {
4141 // take first bracket pair matching end, but don't break in case a later bracket
4142 // pair matches start
4143 bracket_pair_matching_end = Some(pair.clone());
4144 }
4145 }
4146 if let Some(end) = bracket_pair_matching_end
4147 && bracket_pair.is_none()
4148 {
4149 bracket_pair = Some(end);
4150 is_bracket_pair_end = true;
4151 }
4152 }
4153
4154 if let Some(bracket_pair) = bracket_pair {
4155 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4156 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4157 let auto_surround =
4158 self.use_auto_surround && snapshot_settings.use_auto_surround;
4159 if selection.is_empty() {
4160 if is_bracket_pair_start {
4161 // If the inserted text is a suffix of an opening bracket and the
4162 // selection is preceded by the rest of the opening bracket, then
4163 // insert the closing bracket.
4164 let following_text_allows_autoclose = snapshot
4165 .chars_at(selection.start)
4166 .next()
4167 .is_none_or(|c| scope.should_autoclose_before(c));
4168
4169 let preceding_text_allows_autoclose = selection.start.column == 0
4170 || snapshot
4171 .reversed_chars_at(selection.start)
4172 .next()
4173 .is_none_or(|c| {
4174 bracket_pair.start != bracket_pair.end
4175 || !snapshot
4176 .char_classifier_at(selection.start)
4177 .is_word(c)
4178 });
4179
4180 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4181 && bracket_pair.start.len() == 1
4182 {
4183 let target = bracket_pair.start.chars().next().unwrap();
4184 let current_line_count = snapshot
4185 .reversed_chars_at(selection.start)
4186 .take_while(|&c| c != '\n')
4187 .filter(|&c| c == target)
4188 .count();
4189 current_line_count % 2 == 1
4190 } else {
4191 false
4192 };
4193
4194 if autoclose
4195 && bracket_pair.close
4196 && following_text_allows_autoclose
4197 && preceding_text_allows_autoclose
4198 && !is_closing_quote
4199 {
4200 let anchor = snapshot.anchor_before(selection.end);
4201 new_selections.push((selection.map(|_| anchor), text.len()));
4202 new_autoclose_regions.push((
4203 anchor,
4204 text.len(),
4205 selection.id,
4206 bracket_pair.clone(),
4207 ));
4208 edits.push((
4209 selection.range(),
4210 format!("{}{}", text, bracket_pair.end).into(),
4211 ));
4212 bracket_inserted = true;
4213 continue;
4214 }
4215 }
4216
4217 if let Some(region) = autoclose_region {
4218 // If the selection is followed by an auto-inserted closing bracket,
4219 // then don't insert that closing bracket again; just move the selection
4220 // past the closing bracket.
4221 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4222 && text.as_ref() == region.pair.end.as_str()
4223 && snapshot.contains_str_at(region.range.end, text.as_ref());
4224 if should_skip {
4225 let anchor = snapshot.anchor_after(selection.end);
4226 new_selections
4227 .push((selection.map(|_| anchor), region.pair.end.len()));
4228 continue;
4229 }
4230 }
4231
4232 let always_treat_brackets_as_autoclosed = snapshot
4233 .language_settings_at(selection.start, cx)
4234 .always_treat_brackets_as_autoclosed;
4235 if always_treat_brackets_as_autoclosed
4236 && is_bracket_pair_end
4237 && snapshot.contains_str_at(selection.end, text.as_ref())
4238 {
4239 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4240 // and the inserted text is a closing bracket and the selection is followed
4241 // by the closing bracket then move the selection past the closing bracket.
4242 let anchor = snapshot.anchor_after(selection.end);
4243 new_selections.push((selection.map(|_| anchor), text.len()));
4244 continue;
4245 }
4246 }
4247 // If an opening bracket is 1 character long and is typed while
4248 // text is selected, then surround that text with the bracket pair.
4249 else if auto_surround
4250 && bracket_pair.surround
4251 && is_bracket_pair_start
4252 && bracket_pair.start.chars().count() == 1
4253 {
4254 edits.push((selection.start..selection.start, text.clone()));
4255 edits.push((
4256 selection.end..selection.end,
4257 bracket_pair.end.as_str().into(),
4258 ));
4259 bracket_inserted = true;
4260 new_selections.push((
4261 Selection {
4262 id: selection.id,
4263 start: snapshot.anchor_after(selection.start),
4264 end: snapshot.anchor_before(selection.end),
4265 reversed: selection.reversed,
4266 goal: selection.goal,
4267 },
4268 0,
4269 ));
4270 continue;
4271 }
4272 }
4273 }
4274
4275 if self.auto_replace_emoji_shortcode
4276 && selection.is_empty()
4277 && text.as_ref().ends_with(':')
4278 && let Some(possible_emoji_short_code) =
4279 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4280 && !possible_emoji_short_code.is_empty()
4281 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4282 {
4283 let emoji_shortcode_start = Point::new(
4284 selection.start.row,
4285 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4286 );
4287
4288 // Remove shortcode from buffer
4289 edits.push((
4290 emoji_shortcode_start..selection.start,
4291 "".to_string().into(),
4292 ));
4293 new_selections.push((
4294 Selection {
4295 id: selection.id,
4296 start: snapshot.anchor_after(emoji_shortcode_start),
4297 end: snapshot.anchor_before(selection.start),
4298 reversed: selection.reversed,
4299 goal: selection.goal,
4300 },
4301 0,
4302 ));
4303
4304 // Insert emoji
4305 let selection_start_anchor = snapshot.anchor_after(selection.start);
4306 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4307 edits.push((selection.start..selection.end, emoji.to_string().into()));
4308
4309 continue;
4310 }
4311
4312 // If not handling any auto-close operation, then just replace the selected
4313 // text with the given input and move the selection to the end of the
4314 // newly inserted text.
4315 let anchor = snapshot.anchor_after(selection.end);
4316 if !self.linked_edit_ranges.is_empty() {
4317 let start_anchor = snapshot.anchor_before(selection.start);
4318
4319 let is_word_char = text.chars().next().is_none_or(|char| {
4320 let classifier = snapshot
4321 .char_classifier_at(start_anchor.to_offset(&snapshot))
4322 .scope_context(Some(CharScopeContext::LinkedEdit));
4323 classifier.is_word(char)
4324 });
4325
4326 if is_word_char {
4327 if let Some(ranges) = self
4328 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4329 {
4330 for (buffer, edits) in ranges {
4331 linked_edits
4332 .entry(buffer.clone())
4333 .or_default()
4334 .extend(edits.into_iter().map(|range| (range, text.clone())));
4335 }
4336 }
4337 } else {
4338 clear_linked_edit_ranges = true;
4339 }
4340 }
4341
4342 new_selections.push((selection.map(|_| anchor), 0));
4343 edits.push((selection.start..selection.end, text.clone()));
4344 }
4345
4346 drop(snapshot);
4347
4348 self.transact(window, cx, |this, window, cx| {
4349 if clear_linked_edit_ranges {
4350 this.linked_edit_ranges.clear();
4351 }
4352 let initial_buffer_versions =
4353 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4354
4355 this.buffer.update(cx, |buffer, cx| {
4356 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4357 });
4358 for (buffer, edits) in linked_edits {
4359 buffer.update(cx, |buffer, cx| {
4360 let snapshot = buffer.snapshot();
4361 let edits = edits
4362 .into_iter()
4363 .map(|(range, text)| {
4364 use text::ToPoint as TP;
4365 let end_point = TP::to_point(&range.end, &snapshot);
4366 let start_point = TP::to_point(&range.start, &snapshot);
4367 (start_point..end_point, text)
4368 })
4369 .sorted_by_key(|(range, _)| range.start);
4370 buffer.edit(edits, None, cx);
4371 })
4372 }
4373 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4374 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4375 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4376 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4377 .zip(new_selection_deltas)
4378 .map(|(selection, delta)| Selection {
4379 id: selection.id,
4380 start: selection.start + delta,
4381 end: selection.end + delta,
4382 reversed: selection.reversed,
4383 goal: SelectionGoal::None,
4384 })
4385 .collect::<Vec<_>>();
4386
4387 let mut i = 0;
4388 for (position, delta, selection_id, pair) in new_autoclose_regions {
4389 let position = position.to_offset(map.buffer_snapshot()) + delta;
4390 let start = map.buffer_snapshot().anchor_before(position);
4391 let end = map.buffer_snapshot().anchor_after(position);
4392 while let Some(existing_state) = this.autoclose_regions.get(i) {
4393 match existing_state
4394 .range
4395 .start
4396 .cmp(&start, map.buffer_snapshot())
4397 {
4398 Ordering::Less => i += 1,
4399 Ordering::Greater => break,
4400 Ordering::Equal => {
4401 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4402 Ordering::Less => i += 1,
4403 Ordering::Equal => break,
4404 Ordering::Greater => break,
4405 }
4406 }
4407 }
4408 }
4409 this.autoclose_regions.insert(
4410 i,
4411 AutocloseRegion {
4412 selection_id,
4413 range: start..end,
4414 pair,
4415 },
4416 );
4417 }
4418
4419 let had_active_edit_prediction = this.has_active_edit_prediction();
4420 this.change_selections(
4421 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4422 window,
4423 cx,
4424 |s| s.select(new_selections),
4425 );
4426
4427 if !bracket_inserted
4428 && let Some(on_type_format_task) =
4429 this.trigger_on_type_formatting(text.to_string(), window, cx)
4430 {
4431 on_type_format_task.detach_and_log_err(cx);
4432 }
4433
4434 let editor_settings = EditorSettings::get_global(cx);
4435 if bracket_inserted
4436 && (editor_settings.auto_signature_help
4437 || editor_settings.show_signature_help_after_edits)
4438 {
4439 this.show_signature_help(&ShowSignatureHelp, window, cx);
4440 }
4441
4442 let trigger_in_words =
4443 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4444 if this.hard_wrap.is_some() {
4445 let latest: Range<Point> = this.selections.newest(&map).range();
4446 if latest.is_empty()
4447 && this
4448 .buffer()
4449 .read(cx)
4450 .snapshot(cx)
4451 .line_len(MultiBufferRow(latest.start.row))
4452 == latest.start.column
4453 {
4454 this.rewrap_impl(
4455 RewrapOptions {
4456 override_language_settings: true,
4457 preserve_existing_whitespace: true,
4458 },
4459 cx,
4460 )
4461 }
4462 }
4463 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4464 refresh_linked_ranges(this, window, cx);
4465 this.refresh_edit_prediction(true, false, window, cx);
4466 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4467 });
4468 }
4469
4470 fn find_possible_emoji_shortcode_at_position(
4471 snapshot: &MultiBufferSnapshot,
4472 position: Point,
4473 ) -> Option<String> {
4474 let mut chars = Vec::new();
4475 let mut found_colon = false;
4476 for char in snapshot.reversed_chars_at(position).take(100) {
4477 // Found a possible emoji shortcode in the middle of the buffer
4478 if found_colon {
4479 if char.is_whitespace() {
4480 chars.reverse();
4481 return Some(chars.iter().collect());
4482 }
4483 // If the previous character is not a whitespace, we are in the middle of a word
4484 // and we only want to complete the shortcode if the word is made up of other emojis
4485 let mut containing_word = String::new();
4486 for ch in snapshot
4487 .reversed_chars_at(position)
4488 .skip(chars.len() + 1)
4489 .take(100)
4490 {
4491 if ch.is_whitespace() {
4492 break;
4493 }
4494 containing_word.push(ch);
4495 }
4496 let containing_word = containing_word.chars().rev().collect::<String>();
4497 if util::word_consists_of_emojis(containing_word.as_str()) {
4498 chars.reverse();
4499 return Some(chars.iter().collect());
4500 }
4501 }
4502
4503 if char.is_whitespace() || !char.is_ascii() {
4504 return None;
4505 }
4506 if char == ':' {
4507 found_colon = true;
4508 } else {
4509 chars.push(char);
4510 }
4511 }
4512 // Found a possible emoji shortcode at the beginning of the buffer
4513 chars.reverse();
4514 Some(chars.iter().collect())
4515 }
4516
4517 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4518 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4519 self.transact(window, cx, |this, window, cx| {
4520 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4521 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
4522 let multi_buffer = this.buffer.read(cx);
4523 let buffer = multi_buffer.snapshot(cx);
4524 selections
4525 .iter()
4526 .map(|selection| {
4527 let start_point = selection.start.to_point(&buffer);
4528 let mut existing_indent =
4529 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4530 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4531 let start = selection.start;
4532 let end = selection.end;
4533 let selection_is_empty = start == end;
4534 let language_scope = buffer.language_scope_at(start);
4535 let (
4536 comment_delimiter,
4537 doc_delimiter,
4538 insert_extra_newline,
4539 indent_on_newline,
4540 indent_on_extra_newline,
4541 ) = if let Some(language) = &language_scope {
4542 let mut insert_extra_newline =
4543 insert_extra_newline_brackets(&buffer, start..end, language)
4544 || insert_extra_newline_tree_sitter(&buffer, start..end);
4545
4546 // Comment extension on newline is allowed only for cursor selections
4547 let comment_delimiter = maybe!({
4548 if !selection_is_empty {
4549 return None;
4550 }
4551
4552 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4553 return None;
4554 }
4555
4556 let delimiters = language.line_comment_prefixes();
4557 let max_len_of_delimiter =
4558 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4559 let (snapshot, range) =
4560 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4561
4562 let num_of_whitespaces = snapshot
4563 .chars_for_range(range.clone())
4564 .take_while(|c| c.is_whitespace())
4565 .count();
4566 let comment_candidate = snapshot
4567 .chars_for_range(range.clone())
4568 .skip(num_of_whitespaces)
4569 .take(max_len_of_delimiter)
4570 .collect::<String>();
4571 let (delimiter, trimmed_len) = delimiters
4572 .iter()
4573 .filter_map(|delimiter| {
4574 let prefix = delimiter.trim_end();
4575 if comment_candidate.starts_with(prefix) {
4576 Some((delimiter, prefix.len()))
4577 } else {
4578 None
4579 }
4580 })
4581 .max_by_key(|(_, len)| *len)?;
4582
4583 if let Some(BlockCommentConfig {
4584 start: block_start, ..
4585 }) = language.block_comment()
4586 {
4587 let block_start_trimmed = block_start.trim_end();
4588 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4589 let line_content = snapshot
4590 .chars_for_range(range)
4591 .skip(num_of_whitespaces)
4592 .take(block_start_trimmed.len())
4593 .collect::<String>();
4594
4595 if line_content.starts_with(block_start_trimmed) {
4596 return None;
4597 }
4598 }
4599 }
4600
4601 let cursor_is_placed_after_comment_marker =
4602 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4603 if cursor_is_placed_after_comment_marker {
4604 Some(delimiter.clone())
4605 } else {
4606 None
4607 }
4608 });
4609
4610 let mut indent_on_newline = IndentSize::spaces(0);
4611 let mut indent_on_extra_newline = IndentSize::spaces(0);
4612
4613 let doc_delimiter = maybe!({
4614 if !selection_is_empty {
4615 return None;
4616 }
4617
4618 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4619 return None;
4620 }
4621
4622 let BlockCommentConfig {
4623 start: start_tag,
4624 end: end_tag,
4625 prefix: delimiter,
4626 tab_size: len,
4627 } = language.documentation_comment()?;
4628 let is_within_block_comment = buffer
4629 .language_scope_at(start_point)
4630 .is_some_and(|scope| scope.override_name() == Some("comment"));
4631 if !is_within_block_comment {
4632 return None;
4633 }
4634
4635 let (snapshot, range) =
4636 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4637
4638 let num_of_whitespaces = snapshot
4639 .chars_for_range(range.clone())
4640 .take_while(|c| c.is_whitespace())
4641 .count();
4642
4643 // 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.
4644 let column = start_point.column;
4645 let cursor_is_after_start_tag = {
4646 let start_tag_len = start_tag.len();
4647 let start_tag_line = snapshot
4648 .chars_for_range(range.clone())
4649 .skip(num_of_whitespaces)
4650 .take(start_tag_len)
4651 .collect::<String>();
4652 if start_tag_line.starts_with(start_tag.as_ref()) {
4653 num_of_whitespaces + start_tag_len <= column as usize
4654 } else {
4655 false
4656 }
4657 };
4658
4659 let cursor_is_after_delimiter = {
4660 let delimiter_trim = delimiter.trim_end();
4661 let delimiter_line = snapshot
4662 .chars_for_range(range.clone())
4663 .skip(num_of_whitespaces)
4664 .take(delimiter_trim.len())
4665 .collect::<String>();
4666 if delimiter_line.starts_with(delimiter_trim) {
4667 num_of_whitespaces + delimiter_trim.len() <= column as usize
4668 } else {
4669 false
4670 }
4671 };
4672
4673 let cursor_is_before_end_tag_if_exists = {
4674 let mut char_position = 0u32;
4675 let mut end_tag_offset = None;
4676
4677 'outer: for chunk in snapshot.text_for_range(range) {
4678 if let Some(byte_pos) = chunk.find(&**end_tag) {
4679 let chars_before_match =
4680 chunk[..byte_pos].chars().count() as u32;
4681 end_tag_offset =
4682 Some(char_position + chars_before_match);
4683 break 'outer;
4684 }
4685 char_position += chunk.chars().count() as u32;
4686 }
4687
4688 if let Some(end_tag_offset) = end_tag_offset {
4689 let cursor_is_before_end_tag = column <= end_tag_offset;
4690 if cursor_is_after_start_tag {
4691 if cursor_is_before_end_tag {
4692 insert_extra_newline = true;
4693 }
4694 let cursor_is_at_start_of_end_tag =
4695 column == end_tag_offset;
4696 if cursor_is_at_start_of_end_tag {
4697 indent_on_extra_newline.len = *len;
4698 }
4699 }
4700 cursor_is_before_end_tag
4701 } else {
4702 true
4703 }
4704 };
4705
4706 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4707 && cursor_is_before_end_tag_if_exists
4708 {
4709 if cursor_is_after_start_tag {
4710 indent_on_newline.len = *len;
4711 }
4712 Some(delimiter.clone())
4713 } else {
4714 None
4715 }
4716 });
4717
4718 (
4719 comment_delimiter,
4720 doc_delimiter,
4721 insert_extra_newline,
4722 indent_on_newline,
4723 indent_on_extra_newline,
4724 )
4725 } else {
4726 (
4727 None,
4728 None,
4729 false,
4730 IndentSize::default(),
4731 IndentSize::default(),
4732 )
4733 };
4734
4735 let prevent_auto_indent = doc_delimiter.is_some();
4736 let delimiter = comment_delimiter.or(doc_delimiter);
4737
4738 let capacity_for_delimiter =
4739 delimiter.as_deref().map(str::len).unwrap_or_default();
4740 let mut new_text = String::with_capacity(
4741 1 + capacity_for_delimiter
4742 + existing_indent.len as usize
4743 + indent_on_newline.len as usize
4744 + indent_on_extra_newline.len as usize,
4745 );
4746 new_text.push('\n');
4747 new_text.extend(existing_indent.chars());
4748 new_text.extend(indent_on_newline.chars());
4749
4750 if let Some(delimiter) = &delimiter {
4751 new_text.push_str(delimiter);
4752 }
4753
4754 if insert_extra_newline {
4755 new_text.push('\n');
4756 new_text.extend(existing_indent.chars());
4757 new_text.extend(indent_on_extra_newline.chars());
4758 }
4759
4760 let anchor = buffer.anchor_after(end);
4761 let new_selection = selection.map(|_| anchor);
4762 (
4763 ((start..end, new_text), prevent_auto_indent),
4764 (insert_extra_newline, new_selection),
4765 )
4766 })
4767 .unzip()
4768 };
4769
4770 let mut auto_indent_edits = Vec::new();
4771 let mut edits = Vec::new();
4772 for (edit, prevent_auto_indent) in edits_with_flags {
4773 if prevent_auto_indent {
4774 edits.push(edit);
4775 } else {
4776 auto_indent_edits.push(edit);
4777 }
4778 }
4779 if !edits.is_empty() {
4780 this.edit(edits, cx);
4781 }
4782 if !auto_indent_edits.is_empty() {
4783 this.edit_with_autoindent(auto_indent_edits, cx);
4784 }
4785
4786 let buffer = this.buffer.read(cx).snapshot(cx);
4787 let new_selections = selection_info
4788 .into_iter()
4789 .map(|(extra_newline_inserted, new_selection)| {
4790 let mut cursor = new_selection.end.to_point(&buffer);
4791 if extra_newline_inserted {
4792 cursor.row -= 1;
4793 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4794 }
4795 new_selection.map(|_| cursor)
4796 })
4797 .collect();
4798
4799 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4800 this.refresh_edit_prediction(true, false, window, cx);
4801 });
4802 }
4803
4804 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4805 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4806
4807 let buffer = self.buffer.read(cx);
4808 let snapshot = buffer.snapshot(cx);
4809
4810 let mut edits = Vec::new();
4811 let mut rows = Vec::new();
4812
4813 for (rows_inserted, selection) in self
4814 .selections
4815 .all_adjusted(&self.display_snapshot(cx))
4816 .into_iter()
4817 .enumerate()
4818 {
4819 let cursor = selection.head();
4820 let row = cursor.row;
4821
4822 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4823
4824 let newline = "\n".to_string();
4825 edits.push((start_of_line..start_of_line, newline));
4826
4827 rows.push(row + rows_inserted as u32);
4828 }
4829
4830 self.transact(window, cx, |editor, window, cx| {
4831 editor.edit(edits, cx);
4832
4833 editor.change_selections(Default::default(), window, cx, |s| {
4834 let mut index = 0;
4835 s.move_cursors_with(|map, _, _| {
4836 let row = rows[index];
4837 index += 1;
4838
4839 let point = Point::new(row, 0);
4840 let boundary = map.next_line_boundary(point).1;
4841 let clipped = map.clip_point(boundary, Bias::Left);
4842
4843 (clipped, SelectionGoal::None)
4844 });
4845 });
4846
4847 let mut indent_edits = Vec::new();
4848 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4849 for row in rows {
4850 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4851 for (row, indent) in indents {
4852 if indent.len == 0 {
4853 continue;
4854 }
4855
4856 let text = match indent.kind {
4857 IndentKind::Space => " ".repeat(indent.len as usize),
4858 IndentKind::Tab => "\t".repeat(indent.len as usize),
4859 };
4860 let point = Point::new(row.0, 0);
4861 indent_edits.push((point..point, text));
4862 }
4863 }
4864 editor.edit(indent_edits, cx);
4865 });
4866 }
4867
4868 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4869 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4870
4871 let buffer = self.buffer.read(cx);
4872 let snapshot = buffer.snapshot(cx);
4873
4874 let mut edits = Vec::new();
4875 let mut rows = Vec::new();
4876 let mut rows_inserted = 0;
4877
4878 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
4879 let cursor = selection.head();
4880 let row = cursor.row;
4881
4882 let point = Point::new(row + 1, 0);
4883 let start_of_line = snapshot.clip_point(point, Bias::Left);
4884
4885 let newline = "\n".to_string();
4886 edits.push((start_of_line..start_of_line, newline));
4887
4888 rows_inserted += 1;
4889 rows.push(row + rows_inserted);
4890 }
4891
4892 self.transact(window, cx, |editor, window, cx| {
4893 editor.edit(edits, cx);
4894
4895 editor.change_selections(Default::default(), window, cx, |s| {
4896 let mut index = 0;
4897 s.move_cursors_with(|map, _, _| {
4898 let row = rows[index];
4899 index += 1;
4900
4901 let point = Point::new(row, 0);
4902 let boundary = map.next_line_boundary(point).1;
4903 let clipped = map.clip_point(boundary, Bias::Left);
4904
4905 (clipped, SelectionGoal::None)
4906 });
4907 });
4908
4909 let mut indent_edits = Vec::new();
4910 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4911 for row in rows {
4912 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4913 for (row, indent) in indents {
4914 if indent.len == 0 {
4915 continue;
4916 }
4917
4918 let text = match indent.kind {
4919 IndentKind::Space => " ".repeat(indent.len as usize),
4920 IndentKind::Tab => "\t".repeat(indent.len as usize),
4921 };
4922 let point = Point::new(row.0, 0);
4923 indent_edits.push((point..point, text));
4924 }
4925 }
4926 editor.edit(indent_edits, cx);
4927 });
4928 }
4929
4930 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4931 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4932 original_indent_columns: Vec::new(),
4933 });
4934 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4935 }
4936
4937 fn insert_with_autoindent_mode(
4938 &mut self,
4939 text: &str,
4940 autoindent_mode: Option<AutoindentMode>,
4941 window: &mut Window,
4942 cx: &mut Context<Self>,
4943 ) {
4944 if self.read_only(cx) {
4945 return;
4946 }
4947
4948 let text: Arc<str> = text.into();
4949 self.transact(window, cx, |this, window, cx| {
4950 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
4951 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4952 let anchors = {
4953 let snapshot = buffer.read(cx);
4954 old_selections
4955 .iter()
4956 .map(|s| {
4957 let anchor = snapshot.anchor_after(s.head());
4958 s.map(|_| anchor)
4959 })
4960 .collect::<Vec<_>>()
4961 };
4962 buffer.edit(
4963 old_selections
4964 .iter()
4965 .map(|s| (s.start..s.end, text.clone())),
4966 autoindent_mode,
4967 cx,
4968 );
4969 anchors
4970 });
4971
4972 this.change_selections(Default::default(), window, cx, |s| {
4973 s.select_anchors(selection_anchors);
4974 });
4975
4976 cx.notify();
4977 });
4978 }
4979
4980 fn trigger_completion_on_input(
4981 &mut self,
4982 text: &str,
4983 trigger_in_words: bool,
4984 window: &mut Window,
4985 cx: &mut Context<Self>,
4986 ) {
4987 let completions_source = self
4988 .context_menu
4989 .borrow()
4990 .as_ref()
4991 .and_then(|menu| match menu {
4992 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4993 CodeContextMenu::CodeActions(_) => None,
4994 });
4995
4996 match completions_source {
4997 Some(CompletionsMenuSource::Words { .. }) => {
4998 self.open_or_update_completions_menu(
4999 Some(CompletionsMenuSource::Words {
5000 ignore_threshold: false,
5001 }),
5002 None,
5003 window,
5004 cx,
5005 );
5006 }
5007 Some(CompletionsMenuSource::Normal)
5008 | Some(CompletionsMenuSource::SnippetChoices)
5009 | None
5010 if self.is_completion_trigger(
5011 text,
5012 trigger_in_words,
5013 completions_source.is_some(),
5014 cx,
5015 ) =>
5016 {
5017 self.show_completions(
5018 &ShowCompletions {
5019 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
5020 },
5021 window,
5022 cx,
5023 )
5024 }
5025 _ => {
5026 self.hide_context_menu(window, cx);
5027 }
5028 }
5029 }
5030
5031 fn is_completion_trigger(
5032 &self,
5033 text: &str,
5034 trigger_in_words: bool,
5035 menu_is_open: bool,
5036 cx: &mut Context<Self>,
5037 ) -> bool {
5038 let position = self.selections.newest_anchor().head();
5039 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
5040 return false;
5041 };
5042
5043 if let Some(completion_provider) = &self.completion_provider {
5044 completion_provider.is_completion_trigger(
5045 &buffer,
5046 position.text_anchor,
5047 text,
5048 trigger_in_words,
5049 menu_is_open,
5050 cx,
5051 )
5052 } else {
5053 false
5054 }
5055 }
5056
5057 /// If any empty selections is touching the start of its innermost containing autoclose
5058 /// region, expand it to select the brackets.
5059 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5060 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5061 let buffer = self.buffer.read(cx).read(cx);
5062 let new_selections = self
5063 .selections_with_autoclose_regions(selections, &buffer)
5064 .map(|(mut selection, region)| {
5065 if !selection.is_empty() {
5066 return selection;
5067 }
5068
5069 if let Some(region) = region {
5070 let mut range = region.range.to_offset(&buffer);
5071 if selection.start == range.start && range.start >= region.pair.start.len() {
5072 range.start -= region.pair.start.len();
5073 if buffer.contains_str_at(range.start, ®ion.pair.start)
5074 && buffer.contains_str_at(range.end, ®ion.pair.end)
5075 {
5076 range.end += region.pair.end.len();
5077 selection.start = range.start;
5078 selection.end = range.end;
5079
5080 return selection;
5081 }
5082 }
5083 }
5084
5085 let always_treat_brackets_as_autoclosed = buffer
5086 .language_settings_at(selection.start, cx)
5087 .always_treat_brackets_as_autoclosed;
5088
5089 if !always_treat_brackets_as_autoclosed {
5090 return selection;
5091 }
5092
5093 if let Some(scope) = buffer.language_scope_at(selection.start) {
5094 for (pair, enabled) in scope.brackets() {
5095 if !enabled || !pair.close {
5096 continue;
5097 }
5098
5099 if buffer.contains_str_at(selection.start, &pair.end) {
5100 let pair_start_len = pair.start.len();
5101 if buffer.contains_str_at(
5102 selection.start.saturating_sub(pair_start_len),
5103 &pair.start,
5104 ) {
5105 selection.start -= pair_start_len;
5106 selection.end += pair.end.len();
5107
5108 return selection;
5109 }
5110 }
5111 }
5112 }
5113
5114 selection
5115 })
5116 .collect();
5117
5118 drop(buffer);
5119 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5120 selections.select(new_selections)
5121 });
5122 }
5123
5124 /// Iterate the given selections, and for each one, find the smallest surrounding
5125 /// autoclose region. This uses the ordering of the selections and the autoclose
5126 /// regions to avoid repeated comparisons.
5127 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5128 &'a self,
5129 selections: impl IntoIterator<Item = Selection<D>>,
5130 buffer: &'a MultiBufferSnapshot,
5131 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5132 let mut i = 0;
5133 let mut regions = self.autoclose_regions.as_slice();
5134 selections.into_iter().map(move |selection| {
5135 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5136
5137 let mut enclosing = None;
5138 while let Some(pair_state) = regions.get(i) {
5139 if pair_state.range.end.to_offset(buffer) < range.start {
5140 regions = ®ions[i + 1..];
5141 i = 0;
5142 } else if pair_state.range.start.to_offset(buffer) > range.end {
5143 break;
5144 } else {
5145 if pair_state.selection_id == selection.id {
5146 enclosing = Some(pair_state);
5147 }
5148 i += 1;
5149 }
5150 }
5151
5152 (selection, enclosing)
5153 })
5154 }
5155
5156 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5157 fn invalidate_autoclose_regions(
5158 &mut self,
5159 mut selections: &[Selection<Anchor>],
5160 buffer: &MultiBufferSnapshot,
5161 ) {
5162 self.autoclose_regions.retain(|state| {
5163 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5164 return false;
5165 }
5166
5167 let mut i = 0;
5168 while let Some(selection) = selections.get(i) {
5169 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5170 selections = &selections[1..];
5171 continue;
5172 }
5173 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5174 break;
5175 }
5176 if selection.id == state.selection_id {
5177 return true;
5178 } else {
5179 i += 1;
5180 }
5181 }
5182 false
5183 });
5184 }
5185
5186 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5187 let offset = position.to_offset(buffer);
5188 let (word_range, kind) =
5189 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5190 if offset > word_range.start && kind == Some(CharKind::Word) {
5191 Some(
5192 buffer
5193 .text_for_range(word_range.start..offset)
5194 .collect::<String>(),
5195 )
5196 } else {
5197 None
5198 }
5199 }
5200
5201 pub fn toggle_inline_values(
5202 &mut self,
5203 _: &ToggleInlineValues,
5204 _: &mut Window,
5205 cx: &mut Context<Self>,
5206 ) {
5207 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5208
5209 self.refresh_inline_values(cx);
5210 }
5211
5212 pub fn toggle_inlay_hints(
5213 &mut self,
5214 _: &ToggleInlayHints,
5215 _: &mut Window,
5216 cx: &mut Context<Self>,
5217 ) {
5218 self.refresh_inlay_hints(
5219 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5220 cx,
5221 );
5222 }
5223
5224 pub fn inlay_hints_enabled(&self) -> bool {
5225 self.inlay_hint_cache.enabled
5226 }
5227
5228 pub fn inline_values_enabled(&self) -> bool {
5229 self.inline_value_cache.enabled
5230 }
5231
5232 #[cfg(any(test, feature = "test-support"))]
5233 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5234 self.display_map
5235 .read(cx)
5236 .current_inlays()
5237 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5238 .cloned()
5239 .collect()
5240 }
5241
5242 #[cfg(any(test, feature = "test-support"))]
5243 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5244 self.display_map
5245 .read(cx)
5246 .current_inlays()
5247 .cloned()
5248 .collect()
5249 }
5250
5251 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5252 if self.semantics_provider.is_none() || !self.mode.is_full() {
5253 return;
5254 }
5255
5256 let reason_description = reason.description();
5257 let ignore_debounce = matches!(
5258 reason,
5259 InlayHintRefreshReason::SettingsChange(_)
5260 | InlayHintRefreshReason::Toggle(_)
5261 | InlayHintRefreshReason::ExcerptsRemoved(_)
5262 | InlayHintRefreshReason::ModifiersChanged(_)
5263 );
5264 let (invalidate_cache, required_languages) = match reason {
5265 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5266 match self.inlay_hint_cache.modifiers_override(enabled) {
5267 Some(enabled) => {
5268 if enabled {
5269 (InvalidationStrategy::RefreshRequested, None)
5270 } else {
5271 self.clear_inlay_hints(cx);
5272 return;
5273 }
5274 }
5275 None => return,
5276 }
5277 }
5278 InlayHintRefreshReason::Toggle(enabled) => {
5279 if self.inlay_hint_cache.toggle(enabled) {
5280 if enabled {
5281 (InvalidationStrategy::RefreshRequested, None)
5282 } else {
5283 self.clear_inlay_hints(cx);
5284 return;
5285 }
5286 } else {
5287 return;
5288 }
5289 }
5290 InlayHintRefreshReason::SettingsChange(new_settings) => {
5291 match self.inlay_hint_cache.update_settings(
5292 &self.buffer,
5293 new_settings,
5294 self.visible_inlay_hints(cx).cloned().collect::<Vec<_>>(),
5295 cx,
5296 ) {
5297 ControlFlow::Break(Some(InlaySplice {
5298 to_remove,
5299 to_insert,
5300 })) => {
5301 self.splice_inlays(&to_remove, to_insert, cx);
5302 return;
5303 }
5304 ControlFlow::Break(None) => return,
5305 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5306 }
5307 }
5308 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5309 if let Some(InlaySplice {
5310 to_remove,
5311 to_insert,
5312 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5313 {
5314 self.splice_inlays(&to_remove, to_insert, cx);
5315 }
5316 self.display_map.update(cx, |display_map, cx| {
5317 display_map.remove_inlays_for_excerpts(&excerpts_removed, cx)
5318 });
5319 return;
5320 }
5321 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5322 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5323 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5324 }
5325 InlayHintRefreshReason::RefreshRequested => {
5326 (InvalidationStrategy::RefreshRequested, None)
5327 }
5328 };
5329
5330 let mut visible_excerpts = self.visible_excerpts(required_languages.as_ref(), cx);
5331 visible_excerpts.retain(|_, (buffer, _, _)| {
5332 self.registered_buffers
5333 .contains_key(&buffer.read(cx).remote_id())
5334 });
5335
5336 if let Some(InlaySplice {
5337 to_remove,
5338 to_insert,
5339 }) = self.inlay_hint_cache.spawn_hint_refresh(
5340 reason_description,
5341 visible_excerpts,
5342 invalidate_cache,
5343 ignore_debounce,
5344 cx,
5345 ) {
5346 self.splice_inlays(&to_remove, to_insert, cx);
5347 }
5348 }
5349
5350 pub fn clear_inlay_hints(&self, cx: &mut Context<Editor>) {
5351 self.splice_inlays(
5352 &self
5353 .visible_inlay_hints(cx)
5354 .map(|inlay| inlay.id)
5355 .collect::<Vec<_>>(),
5356 Vec::new(),
5357 cx,
5358 );
5359 }
5360
5361 fn visible_inlay_hints<'a>(
5362 &'a self,
5363 cx: &'a Context<Editor>,
5364 ) -> impl Iterator<Item = &'a Inlay> {
5365 self.display_map
5366 .read(cx)
5367 .current_inlays()
5368 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5369 }
5370
5371 pub fn visible_excerpts(
5372 &self,
5373 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5374 cx: &mut Context<Editor>,
5375 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5376 let Some(project) = self.project() else {
5377 return HashMap::default();
5378 };
5379 let project = project.read(cx);
5380 let multi_buffer = self.buffer().read(cx);
5381 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5382 let multi_buffer_visible_start = self
5383 .scroll_manager
5384 .anchor()
5385 .anchor
5386 .to_point(&multi_buffer_snapshot);
5387 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5388 multi_buffer_visible_start
5389 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5390 Bias::Left,
5391 );
5392 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5393 multi_buffer_snapshot
5394 .range_to_buffer_ranges(multi_buffer_visible_range)
5395 .into_iter()
5396 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5397 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5398 let buffer_file = project::File::from_dyn(buffer.file())?;
5399 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5400 let worktree_entry = buffer_worktree
5401 .read(cx)
5402 .entry_for_id(buffer_file.project_entry_id()?)?;
5403 if worktree_entry.is_ignored {
5404 return None;
5405 }
5406
5407 let language = buffer.language()?;
5408 if let Some(restrict_to_languages) = restrict_to_languages
5409 && !restrict_to_languages.contains(language)
5410 {
5411 return None;
5412 }
5413 Some((
5414 excerpt_id,
5415 (
5416 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5417 buffer.version().clone(),
5418 excerpt_visible_range,
5419 ),
5420 ))
5421 })
5422 .collect()
5423 }
5424
5425 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5426 TextLayoutDetails {
5427 text_system: window.text_system().clone(),
5428 editor_style: self.style.clone().unwrap(),
5429 rem_size: window.rem_size(),
5430 scroll_anchor: self.scroll_manager.anchor(),
5431 visible_rows: self.visible_line_count(),
5432 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5433 }
5434 }
5435
5436 pub fn splice_inlays(
5437 &self,
5438 to_remove: &[InlayId],
5439 to_insert: Vec<Inlay>,
5440 cx: &mut Context<Self>,
5441 ) {
5442 self.display_map.update(cx, |display_map, cx| {
5443 display_map.splice_inlays(to_remove, to_insert, cx)
5444 });
5445 cx.notify();
5446 }
5447
5448 fn trigger_on_type_formatting(
5449 &self,
5450 input: String,
5451 window: &mut Window,
5452 cx: &mut Context<Self>,
5453 ) -> Option<Task<Result<()>>> {
5454 if input.len() != 1 {
5455 return None;
5456 }
5457
5458 let project = self.project()?;
5459 let position = self.selections.newest_anchor().head();
5460 let (buffer, buffer_position) = self
5461 .buffer
5462 .read(cx)
5463 .text_anchor_for_position(position, cx)?;
5464
5465 let settings = language_settings::language_settings(
5466 buffer
5467 .read(cx)
5468 .language_at(buffer_position)
5469 .map(|l| l.name()),
5470 buffer.read(cx).file(),
5471 cx,
5472 );
5473 if !settings.use_on_type_format {
5474 return None;
5475 }
5476
5477 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5478 // hence we do LSP request & edit on host side only — add formats to host's history.
5479 let push_to_lsp_host_history = true;
5480 // If this is not the host, append its history with new edits.
5481 let push_to_client_history = project.read(cx).is_via_collab();
5482
5483 let on_type_formatting = project.update(cx, |project, cx| {
5484 project.on_type_format(
5485 buffer.clone(),
5486 buffer_position,
5487 input,
5488 push_to_lsp_host_history,
5489 cx,
5490 )
5491 });
5492 Some(cx.spawn_in(window, async move |editor, cx| {
5493 if let Some(transaction) = on_type_formatting.await? {
5494 if push_to_client_history {
5495 buffer
5496 .update(cx, |buffer, _| {
5497 buffer.push_transaction(transaction, Instant::now());
5498 buffer.finalize_last_transaction();
5499 })
5500 .ok();
5501 }
5502 editor.update(cx, |editor, cx| {
5503 editor.refresh_document_highlights(cx);
5504 })?;
5505 }
5506 Ok(())
5507 }))
5508 }
5509
5510 pub fn show_word_completions(
5511 &mut self,
5512 _: &ShowWordCompletions,
5513 window: &mut Window,
5514 cx: &mut Context<Self>,
5515 ) {
5516 self.open_or_update_completions_menu(
5517 Some(CompletionsMenuSource::Words {
5518 ignore_threshold: true,
5519 }),
5520 None,
5521 window,
5522 cx,
5523 );
5524 }
5525
5526 pub fn show_completions(
5527 &mut self,
5528 options: &ShowCompletions,
5529 window: &mut Window,
5530 cx: &mut Context<Self>,
5531 ) {
5532 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5533 }
5534
5535 fn open_or_update_completions_menu(
5536 &mut self,
5537 requested_source: Option<CompletionsMenuSource>,
5538 trigger: Option<&str>,
5539 window: &mut Window,
5540 cx: &mut Context<Self>,
5541 ) {
5542 if self.pending_rename.is_some() {
5543 return;
5544 }
5545
5546 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5547
5548 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5549 // inserted and selected. To handle that case, the start of the selection is used so that
5550 // the menu starts with all choices.
5551 let position = self
5552 .selections
5553 .newest_anchor()
5554 .start
5555 .bias_right(&multibuffer_snapshot);
5556 if position.diff_base_anchor.is_some() {
5557 return;
5558 }
5559 let buffer_position = multibuffer_snapshot.anchor_before(position);
5560 let Some(buffer) = buffer_position
5561 .buffer_id
5562 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5563 else {
5564 return;
5565 };
5566 let buffer_snapshot = buffer.read(cx).snapshot();
5567
5568 let query: Option<Arc<String>> =
5569 Self::completion_query(&multibuffer_snapshot, buffer_position)
5570 .map(|query| query.into());
5571
5572 drop(multibuffer_snapshot);
5573
5574 // Hide the current completions menu when query is empty. Without this, cached
5575 // completions from before the trigger char may be reused (#32774).
5576 if query.is_none() {
5577 let menu_is_open = matches!(
5578 self.context_menu.borrow().as_ref(),
5579 Some(CodeContextMenu::Completions(_))
5580 );
5581 if menu_is_open {
5582 self.hide_context_menu(window, cx);
5583 }
5584 }
5585
5586 let mut ignore_word_threshold = false;
5587 let provider = match requested_source {
5588 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5589 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5590 ignore_word_threshold = ignore_threshold;
5591 None
5592 }
5593 Some(CompletionsMenuSource::SnippetChoices) => {
5594 log::error!("bug: SnippetChoices requested_source is not handled");
5595 None
5596 }
5597 };
5598
5599 let sort_completions = provider
5600 .as_ref()
5601 .is_some_and(|provider| provider.sort_completions());
5602
5603 let filter_completions = provider
5604 .as_ref()
5605 .is_none_or(|provider| provider.filter_completions());
5606
5607 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5608 if filter_completions {
5609 menu.filter(query.clone(), provider.clone(), window, cx);
5610 }
5611 // When `is_incomplete` is false, no need to re-query completions when the current query
5612 // is a suffix of the initial query.
5613 if !menu.is_incomplete {
5614 // If the new query is a suffix of the old query (typing more characters) and
5615 // the previous result was complete, the existing completions can be filtered.
5616 //
5617 // Note that this is always true for snippet completions.
5618 let query_matches = match (&menu.initial_query, &query) {
5619 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5620 (None, _) => true,
5621 _ => false,
5622 };
5623 if query_matches {
5624 let position_matches = if menu.initial_position == position {
5625 true
5626 } else {
5627 let snapshot = self.buffer.read(cx).read(cx);
5628 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5629 };
5630 if position_matches {
5631 return;
5632 }
5633 }
5634 }
5635 };
5636
5637 let trigger_kind = match trigger {
5638 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5639 CompletionTriggerKind::TRIGGER_CHARACTER
5640 }
5641 _ => CompletionTriggerKind::INVOKED,
5642 };
5643 let completion_context = CompletionContext {
5644 trigger_character: trigger.and_then(|trigger| {
5645 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5646 Some(String::from(trigger))
5647 } else {
5648 None
5649 }
5650 }),
5651 trigger_kind,
5652 };
5653
5654 let Anchor {
5655 excerpt_id: buffer_excerpt_id,
5656 text_anchor: buffer_position,
5657 ..
5658 } = buffer_position;
5659
5660 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5661 buffer_snapshot.surrounding_word(buffer_position, None)
5662 {
5663 let word_to_exclude = buffer_snapshot
5664 .text_for_range(word_range.clone())
5665 .collect::<String>();
5666 (
5667 buffer_snapshot.anchor_before(word_range.start)
5668 ..buffer_snapshot.anchor_after(buffer_position),
5669 Some(word_to_exclude),
5670 )
5671 } else {
5672 (buffer_position..buffer_position, None)
5673 };
5674
5675 let language = buffer_snapshot
5676 .language_at(buffer_position)
5677 .map(|language| language.name());
5678
5679 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5680 .completions
5681 .clone();
5682
5683 let show_completion_documentation = buffer_snapshot
5684 .settings_at(buffer_position, cx)
5685 .show_completion_documentation;
5686
5687 // The document can be large, so stay in reasonable bounds when searching for words,
5688 // otherwise completion pop-up might be slow to appear.
5689 const WORD_LOOKUP_ROWS: u32 = 5_000;
5690 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5691 let min_word_search = buffer_snapshot.clip_point(
5692 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5693 Bias::Left,
5694 );
5695 let max_word_search = buffer_snapshot.clip_point(
5696 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5697 Bias::Right,
5698 );
5699 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5700 ..buffer_snapshot.point_to_offset(max_word_search);
5701
5702 let skip_digits = query
5703 .as_ref()
5704 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5705
5706 let omit_word_completions = !self.word_completions_enabled
5707 || (!ignore_word_threshold
5708 && match &query {
5709 Some(query) => query.chars().count() < completion_settings.words_min_length,
5710 None => completion_settings.words_min_length != 0,
5711 });
5712
5713 let (mut words, provider_responses) = match &provider {
5714 Some(provider) => {
5715 let provider_responses = provider.completions(
5716 buffer_excerpt_id,
5717 &buffer,
5718 buffer_position,
5719 completion_context,
5720 window,
5721 cx,
5722 );
5723
5724 let words = match (omit_word_completions, completion_settings.words) {
5725 (true, _) | (_, WordsCompletionMode::Disabled) => {
5726 Task::ready(BTreeMap::default())
5727 }
5728 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5729 .background_spawn(async move {
5730 buffer_snapshot.words_in_range(WordsQuery {
5731 fuzzy_contents: None,
5732 range: word_search_range,
5733 skip_digits,
5734 })
5735 }),
5736 };
5737
5738 (words, provider_responses)
5739 }
5740 None => {
5741 let words = if omit_word_completions {
5742 Task::ready(BTreeMap::default())
5743 } else {
5744 cx.background_spawn(async move {
5745 buffer_snapshot.words_in_range(WordsQuery {
5746 fuzzy_contents: None,
5747 range: word_search_range,
5748 skip_digits,
5749 })
5750 })
5751 };
5752 (words, Task::ready(Ok(Vec::new())))
5753 }
5754 };
5755
5756 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5757
5758 let id = post_inc(&mut self.next_completion_id);
5759 let task = cx.spawn_in(window, async move |editor, cx| {
5760 let Ok(()) = editor.update(cx, |this, _| {
5761 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5762 }) else {
5763 return;
5764 };
5765
5766 // TODO: Ideally completions from different sources would be selectively re-queried, so
5767 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5768 let mut completions = Vec::new();
5769 let mut is_incomplete = false;
5770 let mut display_options: Option<CompletionDisplayOptions> = None;
5771 if let Some(provider_responses) = provider_responses.await.log_err()
5772 && !provider_responses.is_empty()
5773 {
5774 for response in provider_responses {
5775 completions.extend(response.completions);
5776 is_incomplete = is_incomplete || response.is_incomplete;
5777 match display_options.as_mut() {
5778 None => {
5779 display_options = Some(response.display_options);
5780 }
5781 Some(options) => options.merge(&response.display_options),
5782 }
5783 }
5784 if completion_settings.words == WordsCompletionMode::Fallback {
5785 words = Task::ready(BTreeMap::default());
5786 }
5787 }
5788 let display_options = display_options.unwrap_or_default();
5789
5790 let mut words = words.await;
5791 if let Some(word_to_exclude) = &word_to_exclude {
5792 words.remove(word_to_exclude);
5793 }
5794 for lsp_completion in &completions {
5795 words.remove(&lsp_completion.new_text);
5796 }
5797 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5798 replace_range: word_replace_range.clone(),
5799 new_text: word.clone(),
5800 label: CodeLabel::plain(word, None),
5801 icon_path: None,
5802 documentation: None,
5803 source: CompletionSource::BufferWord {
5804 word_range,
5805 resolved: false,
5806 },
5807 insert_text_mode: Some(InsertTextMode::AS_IS),
5808 confirm: None,
5809 }));
5810
5811 let menu = if completions.is_empty() {
5812 None
5813 } else {
5814 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5815 let languages = editor
5816 .workspace
5817 .as_ref()
5818 .and_then(|(workspace, _)| workspace.upgrade())
5819 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5820 let menu = CompletionsMenu::new(
5821 id,
5822 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5823 sort_completions,
5824 show_completion_documentation,
5825 position,
5826 query.clone(),
5827 is_incomplete,
5828 buffer.clone(),
5829 completions.into(),
5830 display_options,
5831 snippet_sort_order,
5832 languages,
5833 language,
5834 cx,
5835 );
5836
5837 let query = if filter_completions { query } else { None };
5838 let matches_task = if let Some(query) = query {
5839 menu.do_async_filtering(query, cx)
5840 } else {
5841 Task::ready(menu.unfiltered_matches())
5842 };
5843 (menu, matches_task)
5844 }) else {
5845 return;
5846 };
5847
5848 let matches = matches_task.await;
5849
5850 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5851 // Newer menu already set, so exit.
5852 if let Some(CodeContextMenu::Completions(prev_menu)) =
5853 editor.context_menu.borrow().as_ref()
5854 && prev_menu.id > id
5855 {
5856 return;
5857 };
5858
5859 // Only valid to take prev_menu because it the new menu is immediately set
5860 // below, or the menu is hidden.
5861 if let Some(CodeContextMenu::Completions(prev_menu)) =
5862 editor.context_menu.borrow_mut().take()
5863 {
5864 let position_matches =
5865 if prev_menu.initial_position == menu.initial_position {
5866 true
5867 } else {
5868 let snapshot = editor.buffer.read(cx).read(cx);
5869 prev_menu.initial_position.to_offset(&snapshot)
5870 == menu.initial_position.to_offset(&snapshot)
5871 };
5872 if position_matches {
5873 // Preserve markdown cache before `set_filter_results` because it will
5874 // try to populate the documentation cache.
5875 menu.preserve_markdown_cache(prev_menu);
5876 }
5877 };
5878
5879 menu.set_filter_results(matches, provider, window, cx);
5880 }) else {
5881 return;
5882 };
5883
5884 menu.visible().then_some(menu)
5885 };
5886
5887 editor
5888 .update_in(cx, |editor, window, cx| {
5889 if editor.focus_handle.is_focused(window)
5890 && let Some(menu) = menu
5891 {
5892 *editor.context_menu.borrow_mut() =
5893 Some(CodeContextMenu::Completions(menu));
5894
5895 crate::hover_popover::hide_hover(editor, cx);
5896 if editor.show_edit_predictions_in_menu() {
5897 editor.update_visible_edit_prediction(window, cx);
5898 } else {
5899 editor.discard_edit_prediction(false, cx);
5900 }
5901
5902 cx.notify();
5903 return;
5904 }
5905
5906 if editor.completion_tasks.len() <= 1 {
5907 // If there are no more completion tasks and the last menu was empty, we should hide it.
5908 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5909 // If it was already hidden and we don't show edit predictions in the menu,
5910 // we should also show the edit prediction when available.
5911 if was_hidden && editor.show_edit_predictions_in_menu() {
5912 editor.update_visible_edit_prediction(window, cx);
5913 }
5914 }
5915 })
5916 .ok();
5917 });
5918
5919 self.completion_tasks.push((id, task));
5920 }
5921
5922 #[cfg(feature = "test-support")]
5923 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5924 let menu = self.context_menu.borrow();
5925 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5926 let completions = menu.completions.borrow();
5927 Some(completions.to_vec())
5928 } else {
5929 None
5930 }
5931 }
5932
5933 pub fn with_completions_menu_matching_id<R>(
5934 &self,
5935 id: CompletionId,
5936 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5937 ) -> R {
5938 let mut context_menu = self.context_menu.borrow_mut();
5939 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5940 return f(None);
5941 };
5942 if completions_menu.id != id {
5943 return f(None);
5944 }
5945 f(Some(completions_menu))
5946 }
5947
5948 pub fn confirm_completion(
5949 &mut self,
5950 action: &ConfirmCompletion,
5951 window: &mut Window,
5952 cx: &mut Context<Self>,
5953 ) -> Option<Task<Result<()>>> {
5954 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5955 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5956 }
5957
5958 pub fn confirm_completion_insert(
5959 &mut self,
5960 _: &ConfirmCompletionInsert,
5961 window: &mut Window,
5962 cx: &mut Context<Self>,
5963 ) -> Option<Task<Result<()>>> {
5964 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5965 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5966 }
5967
5968 pub fn confirm_completion_replace(
5969 &mut self,
5970 _: &ConfirmCompletionReplace,
5971 window: &mut Window,
5972 cx: &mut Context<Self>,
5973 ) -> Option<Task<Result<()>>> {
5974 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5975 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5976 }
5977
5978 pub fn compose_completion(
5979 &mut self,
5980 action: &ComposeCompletion,
5981 window: &mut Window,
5982 cx: &mut Context<Self>,
5983 ) -> Option<Task<Result<()>>> {
5984 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5985 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5986 }
5987
5988 fn do_completion(
5989 &mut self,
5990 item_ix: Option<usize>,
5991 intent: CompletionIntent,
5992 window: &mut Window,
5993 cx: &mut Context<Editor>,
5994 ) -> Option<Task<Result<()>>> {
5995 use language::ToOffset as _;
5996
5997 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5998 else {
5999 return None;
6000 };
6001
6002 let candidate_id = {
6003 let entries = completions_menu.entries.borrow();
6004 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6005 if self.show_edit_predictions_in_menu() {
6006 self.discard_edit_prediction(true, cx);
6007 }
6008 mat.candidate_id
6009 };
6010
6011 let completion = completions_menu
6012 .completions
6013 .borrow()
6014 .get(candidate_id)?
6015 .clone();
6016 cx.stop_propagation();
6017
6018 let buffer_handle = completions_menu.buffer.clone();
6019
6020 let CompletionEdit {
6021 new_text,
6022 snippet,
6023 replace_range,
6024 } = process_completion_for_edit(
6025 &completion,
6026 intent,
6027 &buffer_handle,
6028 &completions_menu.initial_position.text_anchor,
6029 cx,
6030 );
6031
6032 let buffer = buffer_handle.read(cx);
6033 let snapshot = self.buffer.read(cx).snapshot(cx);
6034 let newest_anchor = self.selections.newest_anchor();
6035 let replace_range_multibuffer = {
6036 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6037 excerpt.map_range_from_buffer(replace_range.clone())
6038 };
6039 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6040 return None;
6041 }
6042
6043 let old_text = buffer
6044 .text_for_range(replace_range.clone())
6045 .collect::<String>();
6046 let lookbehind = newest_anchor
6047 .start
6048 .text_anchor
6049 .to_offset(buffer)
6050 .saturating_sub(replace_range.start);
6051 let lookahead = replace_range
6052 .end
6053 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6054 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6055 let suffix = &old_text[lookbehind.min(old_text.len())..];
6056
6057 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
6058 let mut ranges = Vec::new();
6059 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6060
6061 for selection in &selections {
6062 let range = if selection.id == newest_anchor.id {
6063 replace_range_multibuffer.clone()
6064 } else {
6065 let mut range = selection.range();
6066
6067 // if prefix is present, don't duplicate it
6068 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
6069 range.start = range.start.saturating_sub(lookbehind);
6070
6071 // if suffix is also present, mimic the newest cursor and replace it
6072 if selection.id != newest_anchor.id
6073 && snapshot.contains_str_at(range.end, suffix)
6074 {
6075 range.end += lookahead;
6076 }
6077 }
6078 range
6079 };
6080
6081 ranges.push(range.clone());
6082
6083 if !self.linked_edit_ranges.is_empty() {
6084 let start_anchor = snapshot.anchor_before(range.start);
6085 let end_anchor = snapshot.anchor_after(range.end);
6086 if let Some(ranges) = self
6087 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6088 {
6089 for (buffer, edits) in ranges {
6090 linked_edits
6091 .entry(buffer.clone())
6092 .or_default()
6093 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6094 }
6095 }
6096 }
6097 }
6098
6099 let common_prefix_len = old_text
6100 .chars()
6101 .zip(new_text.chars())
6102 .take_while(|(a, b)| a == b)
6103 .map(|(a, _)| a.len_utf8())
6104 .sum::<usize>();
6105
6106 cx.emit(EditorEvent::InputHandled {
6107 utf16_range_to_replace: None,
6108 text: new_text[common_prefix_len..].into(),
6109 });
6110
6111 self.transact(window, cx, |editor, window, cx| {
6112 if let Some(mut snippet) = snippet {
6113 snippet.text = new_text.to_string();
6114 editor
6115 .insert_snippet(&ranges, snippet, window, cx)
6116 .log_err();
6117 } else {
6118 editor.buffer.update(cx, |multi_buffer, cx| {
6119 let auto_indent = match completion.insert_text_mode {
6120 Some(InsertTextMode::AS_IS) => None,
6121 _ => editor.autoindent_mode.clone(),
6122 };
6123 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6124 multi_buffer.edit(edits, auto_indent, cx);
6125 });
6126 }
6127 for (buffer, edits) in linked_edits {
6128 buffer.update(cx, |buffer, cx| {
6129 let snapshot = buffer.snapshot();
6130 let edits = edits
6131 .into_iter()
6132 .map(|(range, text)| {
6133 use text::ToPoint as TP;
6134 let end_point = TP::to_point(&range.end, &snapshot);
6135 let start_point = TP::to_point(&range.start, &snapshot);
6136 (start_point..end_point, text)
6137 })
6138 .sorted_by_key(|(range, _)| range.start);
6139 buffer.edit(edits, None, cx);
6140 })
6141 }
6142
6143 editor.refresh_edit_prediction(true, false, window, cx);
6144 });
6145 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6146
6147 let show_new_completions_on_confirm = completion
6148 .confirm
6149 .as_ref()
6150 .is_some_and(|confirm| confirm(intent, window, cx));
6151 if show_new_completions_on_confirm {
6152 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6153 }
6154
6155 let provider = self.completion_provider.as_ref()?;
6156 drop(completion);
6157 let apply_edits = provider.apply_additional_edits_for_completion(
6158 buffer_handle,
6159 completions_menu.completions.clone(),
6160 candidate_id,
6161 true,
6162 cx,
6163 );
6164
6165 let editor_settings = EditorSettings::get_global(cx);
6166 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6167 // After the code completion is finished, users often want to know what signatures are needed.
6168 // so we should automatically call signature_help
6169 self.show_signature_help(&ShowSignatureHelp, window, cx);
6170 }
6171
6172 Some(cx.foreground_executor().spawn(async move {
6173 apply_edits.await?;
6174 Ok(())
6175 }))
6176 }
6177
6178 pub fn toggle_code_actions(
6179 &mut self,
6180 action: &ToggleCodeActions,
6181 window: &mut Window,
6182 cx: &mut Context<Self>,
6183 ) {
6184 let quick_launch = action.quick_launch;
6185 let mut context_menu = self.context_menu.borrow_mut();
6186 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6187 if code_actions.deployed_from == action.deployed_from {
6188 // Toggle if we're selecting the same one
6189 *context_menu = None;
6190 cx.notify();
6191 return;
6192 } else {
6193 // Otherwise, clear it and start a new one
6194 *context_menu = None;
6195 cx.notify();
6196 }
6197 }
6198 drop(context_menu);
6199 let snapshot = self.snapshot(window, cx);
6200 let deployed_from = action.deployed_from.clone();
6201 let action = action.clone();
6202 self.completion_tasks.clear();
6203 self.discard_edit_prediction(false, cx);
6204
6205 let multibuffer_point = match &action.deployed_from {
6206 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6207 DisplayPoint::new(*row, 0).to_point(&snapshot)
6208 }
6209 _ => self
6210 .selections
6211 .newest::<Point>(&snapshot.display_snapshot)
6212 .head(),
6213 };
6214 let Some((buffer, buffer_row)) = snapshot
6215 .buffer_snapshot()
6216 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6217 .and_then(|(buffer_snapshot, range)| {
6218 self.buffer()
6219 .read(cx)
6220 .buffer(buffer_snapshot.remote_id())
6221 .map(|buffer| (buffer, range.start.row))
6222 })
6223 else {
6224 return;
6225 };
6226 let buffer_id = buffer.read(cx).remote_id();
6227 let tasks = self
6228 .tasks
6229 .get(&(buffer_id, buffer_row))
6230 .map(|t| Arc::new(t.to_owned()));
6231
6232 if !self.focus_handle.is_focused(window) {
6233 return;
6234 }
6235 let project = self.project.clone();
6236
6237 let code_actions_task = match deployed_from {
6238 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6239 _ => self.code_actions(buffer_row, window, cx),
6240 };
6241
6242 let runnable_task = match deployed_from {
6243 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6244 _ => {
6245 let mut task_context_task = Task::ready(None);
6246 if let Some(tasks) = &tasks
6247 && let Some(project) = project
6248 {
6249 task_context_task =
6250 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6251 }
6252
6253 cx.spawn_in(window, {
6254 let buffer = buffer.clone();
6255 async move |editor, cx| {
6256 let task_context = task_context_task.await;
6257
6258 let resolved_tasks =
6259 tasks
6260 .zip(task_context.clone())
6261 .map(|(tasks, task_context)| ResolvedTasks {
6262 templates: tasks.resolve(&task_context).collect(),
6263 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6264 multibuffer_point.row,
6265 tasks.column,
6266 )),
6267 });
6268 let debug_scenarios = editor
6269 .update(cx, |editor, cx| {
6270 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6271 })?
6272 .await;
6273 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6274 }
6275 })
6276 }
6277 };
6278
6279 cx.spawn_in(window, async move |editor, cx| {
6280 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6281 let code_actions = code_actions_task.await;
6282 let spawn_straight_away = quick_launch
6283 && resolved_tasks
6284 .as_ref()
6285 .is_some_and(|tasks| tasks.templates.len() == 1)
6286 && code_actions
6287 .as_ref()
6288 .is_none_or(|actions| actions.is_empty())
6289 && debug_scenarios.is_empty();
6290
6291 editor.update_in(cx, |editor, window, cx| {
6292 crate::hover_popover::hide_hover(editor, cx);
6293 let actions = CodeActionContents::new(
6294 resolved_tasks,
6295 code_actions,
6296 debug_scenarios,
6297 task_context.unwrap_or_default(),
6298 );
6299
6300 // Don't show the menu if there are no actions available
6301 if actions.is_empty() {
6302 cx.notify();
6303 return Task::ready(Ok(()));
6304 }
6305
6306 *editor.context_menu.borrow_mut() =
6307 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6308 buffer,
6309 actions,
6310 selected_item: Default::default(),
6311 scroll_handle: UniformListScrollHandle::default(),
6312 deployed_from,
6313 }));
6314 cx.notify();
6315 if spawn_straight_away
6316 && let Some(task) = editor.confirm_code_action(
6317 &ConfirmCodeAction { item_ix: Some(0) },
6318 window,
6319 cx,
6320 )
6321 {
6322 return task;
6323 }
6324
6325 Task::ready(Ok(()))
6326 })
6327 })
6328 .detach_and_log_err(cx);
6329 }
6330
6331 fn debug_scenarios(
6332 &mut self,
6333 resolved_tasks: &Option<ResolvedTasks>,
6334 buffer: &Entity<Buffer>,
6335 cx: &mut App,
6336 ) -> Task<Vec<task::DebugScenario>> {
6337 maybe!({
6338 let project = self.project()?;
6339 let dap_store = project.read(cx).dap_store();
6340 let mut scenarios = vec![];
6341 let resolved_tasks = resolved_tasks.as_ref()?;
6342 let buffer = buffer.read(cx);
6343 let language = buffer.language()?;
6344 let file = buffer.file();
6345 let debug_adapter = language_settings(language.name().into(), file, cx)
6346 .debuggers
6347 .first()
6348 .map(SharedString::from)
6349 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6350
6351 dap_store.update(cx, |dap_store, cx| {
6352 for (_, task) in &resolved_tasks.templates {
6353 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6354 task.original_task().clone(),
6355 debug_adapter.clone().into(),
6356 task.display_label().to_owned().into(),
6357 cx,
6358 );
6359 scenarios.push(maybe_scenario);
6360 }
6361 });
6362 Some(cx.background_spawn(async move {
6363 futures::future::join_all(scenarios)
6364 .await
6365 .into_iter()
6366 .flatten()
6367 .collect::<Vec<_>>()
6368 }))
6369 })
6370 .unwrap_or_else(|| Task::ready(vec![]))
6371 }
6372
6373 fn code_actions(
6374 &mut self,
6375 buffer_row: u32,
6376 window: &mut Window,
6377 cx: &mut Context<Self>,
6378 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6379 let mut task = self.code_actions_task.take();
6380 cx.spawn_in(window, async move |editor, cx| {
6381 while let Some(prev_task) = task {
6382 prev_task.await.log_err();
6383 task = editor
6384 .update(cx, |this, _| this.code_actions_task.take())
6385 .ok()?;
6386 }
6387
6388 editor
6389 .update(cx, |editor, cx| {
6390 editor
6391 .available_code_actions
6392 .clone()
6393 .and_then(|(location, code_actions)| {
6394 let snapshot = location.buffer.read(cx).snapshot();
6395 let point_range = location.range.to_point(&snapshot);
6396 let point_range = point_range.start.row..=point_range.end.row;
6397 if point_range.contains(&buffer_row) {
6398 Some(code_actions)
6399 } else {
6400 None
6401 }
6402 })
6403 })
6404 .ok()
6405 .flatten()
6406 })
6407 }
6408
6409 pub fn confirm_code_action(
6410 &mut self,
6411 action: &ConfirmCodeAction,
6412 window: &mut Window,
6413 cx: &mut Context<Self>,
6414 ) -> Option<Task<Result<()>>> {
6415 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6416
6417 let actions_menu =
6418 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6419 menu
6420 } else {
6421 return None;
6422 };
6423
6424 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6425 let action = actions_menu.actions.get(action_ix)?;
6426 let title = action.label();
6427 let buffer = actions_menu.buffer;
6428 let workspace = self.workspace()?;
6429
6430 match action {
6431 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6432 workspace.update(cx, |workspace, cx| {
6433 workspace.schedule_resolved_task(
6434 task_source_kind,
6435 resolved_task,
6436 false,
6437 window,
6438 cx,
6439 );
6440
6441 Some(Task::ready(Ok(())))
6442 })
6443 }
6444 CodeActionsItem::CodeAction {
6445 excerpt_id,
6446 action,
6447 provider,
6448 } => {
6449 let apply_code_action =
6450 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6451 let workspace = workspace.downgrade();
6452 Some(cx.spawn_in(window, async move |editor, cx| {
6453 let project_transaction = apply_code_action.await?;
6454 Self::open_project_transaction(
6455 &editor,
6456 workspace,
6457 project_transaction,
6458 title,
6459 cx,
6460 )
6461 .await
6462 }))
6463 }
6464 CodeActionsItem::DebugScenario(scenario) => {
6465 let context = actions_menu.actions.context;
6466
6467 workspace.update(cx, |workspace, cx| {
6468 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6469 workspace.start_debug_session(
6470 scenario,
6471 context,
6472 Some(buffer),
6473 None,
6474 window,
6475 cx,
6476 );
6477 });
6478 Some(Task::ready(Ok(())))
6479 }
6480 }
6481 }
6482
6483 pub async fn open_project_transaction(
6484 editor: &WeakEntity<Editor>,
6485 workspace: WeakEntity<Workspace>,
6486 transaction: ProjectTransaction,
6487 title: String,
6488 cx: &mut AsyncWindowContext,
6489 ) -> Result<()> {
6490 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6491 cx.update(|_, cx| {
6492 entries.sort_unstable_by_key(|(buffer, _)| {
6493 buffer.read(cx).file().map(|f| f.path().clone())
6494 });
6495 })?;
6496 if entries.is_empty() {
6497 return Ok(());
6498 }
6499
6500 // If the project transaction's edits are all contained within this editor, then
6501 // avoid opening a new editor to display them.
6502
6503 if let [(buffer, transaction)] = &*entries {
6504 let excerpt = editor.update(cx, |editor, cx| {
6505 editor
6506 .buffer()
6507 .read(cx)
6508 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6509 })?;
6510 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6511 && excerpted_buffer == *buffer
6512 {
6513 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6514 let excerpt_range = excerpt_range.to_offset(buffer);
6515 buffer
6516 .edited_ranges_for_transaction::<usize>(transaction)
6517 .all(|range| {
6518 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6519 })
6520 })?;
6521
6522 if all_edits_within_excerpt {
6523 return Ok(());
6524 }
6525 }
6526 }
6527
6528 let mut ranges_to_highlight = Vec::new();
6529 let excerpt_buffer = cx.new(|cx| {
6530 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6531 for (buffer_handle, transaction) in &entries {
6532 let edited_ranges = buffer_handle
6533 .read(cx)
6534 .edited_ranges_for_transaction::<Point>(transaction)
6535 .collect::<Vec<_>>();
6536 let (ranges, _) = multibuffer.set_excerpts_for_path(
6537 PathKey::for_buffer(buffer_handle, cx),
6538 buffer_handle.clone(),
6539 edited_ranges,
6540 multibuffer_context_lines(cx),
6541 cx,
6542 );
6543
6544 ranges_to_highlight.extend(ranges);
6545 }
6546 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6547 multibuffer
6548 })?;
6549
6550 workspace.update_in(cx, |workspace, window, cx| {
6551 let project = workspace.project().clone();
6552 let editor =
6553 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6554 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6555 editor.update(cx, |editor, cx| {
6556 editor.highlight_background::<Self>(
6557 &ranges_to_highlight,
6558 |theme| theme.colors().editor_highlighted_line_background,
6559 cx,
6560 );
6561 });
6562 })?;
6563
6564 Ok(())
6565 }
6566
6567 pub fn clear_code_action_providers(&mut self) {
6568 self.code_action_providers.clear();
6569 self.available_code_actions.take();
6570 }
6571
6572 pub fn add_code_action_provider(
6573 &mut self,
6574 provider: Rc<dyn CodeActionProvider>,
6575 window: &mut Window,
6576 cx: &mut Context<Self>,
6577 ) {
6578 if self
6579 .code_action_providers
6580 .iter()
6581 .any(|existing_provider| existing_provider.id() == provider.id())
6582 {
6583 return;
6584 }
6585
6586 self.code_action_providers.push(provider);
6587 self.refresh_code_actions(window, cx);
6588 }
6589
6590 pub fn remove_code_action_provider(
6591 &mut self,
6592 id: Arc<str>,
6593 window: &mut Window,
6594 cx: &mut Context<Self>,
6595 ) {
6596 self.code_action_providers
6597 .retain(|provider| provider.id() != id);
6598 self.refresh_code_actions(window, cx);
6599 }
6600
6601 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6602 !self.code_action_providers.is_empty()
6603 && EditorSettings::get_global(cx).toolbar.code_actions
6604 }
6605
6606 pub fn has_available_code_actions(&self) -> bool {
6607 self.available_code_actions
6608 .as_ref()
6609 .is_some_and(|(_, actions)| !actions.is_empty())
6610 }
6611
6612 fn render_inline_code_actions(
6613 &self,
6614 icon_size: ui::IconSize,
6615 display_row: DisplayRow,
6616 is_active: bool,
6617 cx: &mut Context<Self>,
6618 ) -> AnyElement {
6619 let show_tooltip = !self.context_menu_visible();
6620 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6621 .icon_size(icon_size)
6622 .shape(ui::IconButtonShape::Square)
6623 .icon_color(ui::Color::Hidden)
6624 .toggle_state(is_active)
6625 .when(show_tooltip, |this| {
6626 this.tooltip({
6627 let focus_handle = self.focus_handle.clone();
6628 move |window, cx| {
6629 Tooltip::for_action_in(
6630 "Toggle Code Actions",
6631 &ToggleCodeActions {
6632 deployed_from: None,
6633 quick_launch: false,
6634 },
6635 &focus_handle,
6636 window,
6637 cx,
6638 )
6639 }
6640 })
6641 })
6642 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6643 window.focus(&editor.focus_handle(cx));
6644 editor.toggle_code_actions(
6645 &crate::actions::ToggleCodeActions {
6646 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6647 display_row,
6648 )),
6649 quick_launch: false,
6650 },
6651 window,
6652 cx,
6653 );
6654 }))
6655 .into_any_element()
6656 }
6657
6658 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6659 &self.context_menu
6660 }
6661
6662 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6663 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6664 cx.background_executor()
6665 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6666 .await;
6667
6668 let (start_buffer, start, _, end, newest_selection) = this
6669 .update(cx, |this, cx| {
6670 let newest_selection = this.selections.newest_anchor().clone();
6671 if newest_selection.head().diff_base_anchor.is_some() {
6672 return None;
6673 }
6674 let display_snapshot = this.display_snapshot(cx);
6675 let newest_selection_adjusted =
6676 this.selections.newest_adjusted(&display_snapshot);
6677 let buffer = this.buffer.read(cx);
6678
6679 let (start_buffer, start) =
6680 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6681 let (end_buffer, end) =
6682 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6683
6684 Some((start_buffer, start, end_buffer, end, newest_selection))
6685 })?
6686 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6687 .context(
6688 "Expected selection to lie in a single buffer when refreshing code actions",
6689 )?;
6690 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6691 let providers = this.code_action_providers.clone();
6692 let tasks = this
6693 .code_action_providers
6694 .iter()
6695 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6696 .collect::<Vec<_>>();
6697 (providers, tasks)
6698 })?;
6699
6700 let mut actions = Vec::new();
6701 for (provider, provider_actions) in
6702 providers.into_iter().zip(future::join_all(tasks).await)
6703 {
6704 if let Some(provider_actions) = provider_actions.log_err() {
6705 actions.extend(provider_actions.into_iter().map(|action| {
6706 AvailableCodeAction {
6707 excerpt_id: newest_selection.start.excerpt_id,
6708 action,
6709 provider: provider.clone(),
6710 }
6711 }));
6712 }
6713 }
6714
6715 this.update(cx, |this, cx| {
6716 this.available_code_actions = if actions.is_empty() {
6717 None
6718 } else {
6719 Some((
6720 Location {
6721 buffer: start_buffer,
6722 range: start..end,
6723 },
6724 actions.into(),
6725 ))
6726 };
6727 cx.notify();
6728 })
6729 }));
6730 }
6731
6732 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6733 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6734 self.show_git_blame_inline = false;
6735
6736 self.show_git_blame_inline_delay_task =
6737 Some(cx.spawn_in(window, async move |this, cx| {
6738 cx.background_executor().timer(delay).await;
6739
6740 this.update(cx, |this, cx| {
6741 this.show_git_blame_inline = true;
6742 cx.notify();
6743 })
6744 .log_err();
6745 }));
6746 }
6747 }
6748
6749 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6750 let snapshot = self.snapshot(window, cx);
6751 let cursor = self
6752 .selections
6753 .newest::<Point>(&snapshot.display_snapshot)
6754 .head();
6755 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6756 else {
6757 return;
6758 };
6759
6760 let Some(blame) = self.blame.as_ref() else {
6761 return;
6762 };
6763
6764 let row_info = RowInfo {
6765 buffer_id: Some(buffer.remote_id()),
6766 buffer_row: Some(point.row),
6767 ..Default::default()
6768 };
6769 let Some((buffer, blame_entry)) = blame
6770 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6771 .flatten()
6772 else {
6773 return;
6774 };
6775
6776 let anchor = self.selections.newest_anchor().head();
6777 let position = self.to_pixel_point(anchor, &snapshot, window);
6778 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6779 self.show_blame_popover(
6780 buffer,
6781 &blame_entry,
6782 position + last_bounds.origin,
6783 true,
6784 cx,
6785 );
6786 };
6787 }
6788
6789 fn show_blame_popover(
6790 &mut self,
6791 buffer: BufferId,
6792 blame_entry: &BlameEntry,
6793 position: gpui::Point<Pixels>,
6794 ignore_timeout: bool,
6795 cx: &mut Context<Self>,
6796 ) {
6797 if let Some(state) = &mut self.inline_blame_popover {
6798 state.hide_task.take();
6799 } else {
6800 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6801 let blame_entry = blame_entry.clone();
6802 let show_task = cx.spawn(async move |editor, cx| {
6803 if !ignore_timeout {
6804 cx.background_executor()
6805 .timer(std::time::Duration::from_millis(blame_popover_delay))
6806 .await;
6807 }
6808 editor
6809 .update(cx, |editor, cx| {
6810 editor.inline_blame_popover_show_task.take();
6811 let Some(blame) = editor.blame.as_ref() else {
6812 return;
6813 };
6814 let blame = blame.read(cx);
6815 let details = blame.details_for_entry(buffer, &blame_entry);
6816 let markdown = cx.new(|cx| {
6817 Markdown::new(
6818 details
6819 .as_ref()
6820 .map(|message| message.message.clone())
6821 .unwrap_or_default(),
6822 None,
6823 None,
6824 cx,
6825 )
6826 });
6827 editor.inline_blame_popover = Some(InlineBlamePopover {
6828 position,
6829 hide_task: None,
6830 popover_bounds: None,
6831 popover_state: InlineBlamePopoverState {
6832 scroll_handle: ScrollHandle::new(),
6833 commit_message: details,
6834 markdown,
6835 },
6836 keyboard_grace: ignore_timeout,
6837 });
6838 cx.notify();
6839 })
6840 .ok();
6841 });
6842 self.inline_blame_popover_show_task = Some(show_task);
6843 }
6844 }
6845
6846 fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6847 self.inline_blame_popover_show_task.take();
6848 if let Some(state) = &mut self.inline_blame_popover {
6849 let hide_task = cx.spawn(async move |editor, cx| {
6850 if !ignore_timeout {
6851 cx.background_executor()
6852 .timer(std::time::Duration::from_millis(100))
6853 .await;
6854 }
6855 editor
6856 .update(cx, |editor, cx| {
6857 editor.inline_blame_popover.take();
6858 cx.notify();
6859 })
6860 .ok();
6861 });
6862 state.hide_task = Some(hide_task);
6863 true
6864 } else {
6865 false
6866 }
6867 }
6868
6869 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6870 if self.pending_rename.is_some() {
6871 return None;
6872 }
6873
6874 let provider = self.semantics_provider.clone()?;
6875 let buffer = self.buffer.read(cx);
6876 let newest_selection = self.selections.newest_anchor().clone();
6877 let cursor_position = newest_selection.head();
6878 let (cursor_buffer, cursor_buffer_position) =
6879 buffer.text_anchor_for_position(cursor_position, cx)?;
6880 let (tail_buffer, tail_buffer_position) =
6881 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6882 if cursor_buffer != tail_buffer {
6883 return None;
6884 }
6885
6886 let snapshot = cursor_buffer.read(cx).snapshot();
6887 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6888 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6889 if start_word_range != end_word_range {
6890 self.document_highlights_task.take();
6891 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6892 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6893 return None;
6894 }
6895
6896 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6897 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6898 cx.background_executor()
6899 .timer(Duration::from_millis(debounce))
6900 .await;
6901
6902 let highlights = if let Some(highlights) = cx
6903 .update(|cx| {
6904 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6905 })
6906 .ok()
6907 .flatten()
6908 {
6909 highlights.await.log_err()
6910 } else {
6911 None
6912 };
6913
6914 if let Some(highlights) = highlights {
6915 this.update(cx, |this, cx| {
6916 if this.pending_rename.is_some() {
6917 return;
6918 }
6919
6920 let buffer = this.buffer.read(cx);
6921 if buffer
6922 .text_anchor_for_position(cursor_position, cx)
6923 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6924 {
6925 return;
6926 }
6927
6928 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6929 let mut write_ranges = Vec::new();
6930 let mut read_ranges = Vec::new();
6931 for highlight in highlights {
6932 let buffer_id = cursor_buffer.read(cx).remote_id();
6933 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6934 {
6935 let start = highlight
6936 .range
6937 .start
6938 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6939 let end = highlight
6940 .range
6941 .end
6942 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6943 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6944 continue;
6945 }
6946
6947 let range =
6948 Anchor::range_in_buffer(excerpt_id, buffer_id, *start..*end);
6949 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6950 write_ranges.push(range);
6951 } else {
6952 read_ranges.push(range);
6953 }
6954 }
6955 }
6956
6957 this.highlight_background::<DocumentHighlightRead>(
6958 &read_ranges,
6959 |theme| theme.colors().editor_document_highlight_read_background,
6960 cx,
6961 );
6962 this.highlight_background::<DocumentHighlightWrite>(
6963 &write_ranges,
6964 |theme| theme.colors().editor_document_highlight_write_background,
6965 cx,
6966 );
6967 cx.notify();
6968 })
6969 .log_err();
6970 }
6971 }));
6972 None
6973 }
6974
6975 fn prepare_highlight_query_from_selection(
6976 &mut self,
6977 cx: &mut Context<Editor>,
6978 ) -> Option<(String, Range<Anchor>)> {
6979 if matches!(self.mode, EditorMode::SingleLine) {
6980 return None;
6981 }
6982 if !EditorSettings::get_global(cx).selection_highlight {
6983 return None;
6984 }
6985 if self.selections.count() != 1 || self.selections.line_mode() {
6986 return None;
6987 }
6988 let selection = self.selections.newest_anchor();
6989 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6990 let selection_point_range = selection.start.to_point(&multi_buffer_snapshot)
6991 ..selection.end.to_point(&multi_buffer_snapshot);
6992 // If the selection spans multiple rows OR it is empty
6993 if selection_point_range.start.row != selection_point_range.end.row
6994 || selection_point_range.start.column == selection_point_range.end.column
6995 {
6996 return None;
6997 }
6998
6999 let query = multi_buffer_snapshot
7000 .text_for_range(selection.range())
7001 .collect::<String>();
7002 if query.trim().is_empty() {
7003 return None;
7004 }
7005 Some((query, selection.range()))
7006 }
7007
7008 fn update_selection_occurrence_highlights(
7009 &mut self,
7010 query_text: String,
7011 query_range: Range<Anchor>,
7012 multi_buffer_range_to_query: Range<Point>,
7013 use_debounce: bool,
7014 window: &mut Window,
7015 cx: &mut Context<Editor>,
7016 ) -> Task<()> {
7017 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7018 cx.spawn_in(window, async move |editor, cx| {
7019 if use_debounce {
7020 cx.background_executor()
7021 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7022 .await;
7023 }
7024 let match_task = cx.background_spawn(async move {
7025 let buffer_ranges = multi_buffer_snapshot
7026 .range_to_buffer_ranges(multi_buffer_range_to_query)
7027 .into_iter()
7028 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7029 let mut match_ranges = Vec::new();
7030 let Ok(regex) = project::search::SearchQuery::text(
7031 query_text.clone(),
7032 false,
7033 false,
7034 false,
7035 Default::default(),
7036 Default::default(),
7037 false,
7038 None,
7039 ) else {
7040 return Vec::default();
7041 };
7042 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7043 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7044 match_ranges.extend(
7045 regex
7046 .search(buffer_snapshot, Some(search_range.clone()))
7047 .await
7048 .into_iter()
7049 .filter_map(|match_range| {
7050 let match_start = buffer_snapshot
7051 .anchor_after(search_range.start + match_range.start);
7052 let match_end = buffer_snapshot
7053 .anchor_before(search_range.start + match_range.end);
7054 let match_anchor_range = Anchor::range_in_buffer(
7055 excerpt_id,
7056 buffer_snapshot.remote_id(),
7057 match_start..match_end,
7058 );
7059 (match_anchor_range != query_range).then_some(match_anchor_range)
7060 }),
7061 );
7062 }
7063 match_ranges
7064 });
7065 let match_ranges = match_task.await;
7066 editor
7067 .update_in(cx, |editor, _, cx| {
7068 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7069 if !match_ranges.is_empty() {
7070 editor.highlight_background::<SelectedTextHighlight>(
7071 &match_ranges,
7072 |theme| theme.colors().editor_document_highlight_bracket_background,
7073 cx,
7074 )
7075 }
7076 })
7077 .log_err();
7078 })
7079 }
7080
7081 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7082 struct NewlineFold;
7083 let type_id = std::any::TypeId::of::<NewlineFold>();
7084 if !self.mode.is_single_line() {
7085 return;
7086 }
7087 let snapshot = self.snapshot(window, cx);
7088 if snapshot.buffer_snapshot().max_point().row == 0 {
7089 return;
7090 }
7091 let task = cx.background_spawn(async move {
7092 let new_newlines = snapshot
7093 .buffer_chars_at(0)
7094 .filter_map(|(c, i)| {
7095 if c == '\n' {
7096 Some(
7097 snapshot.buffer_snapshot().anchor_after(i)
7098 ..snapshot.buffer_snapshot().anchor_before(i + 1),
7099 )
7100 } else {
7101 None
7102 }
7103 })
7104 .collect::<Vec<_>>();
7105 let existing_newlines = snapshot
7106 .folds_in_range(0..snapshot.buffer_snapshot().len())
7107 .filter_map(|fold| {
7108 if fold.placeholder.type_tag == Some(type_id) {
7109 Some(fold.range.start..fold.range.end)
7110 } else {
7111 None
7112 }
7113 })
7114 .collect::<Vec<_>>();
7115
7116 (new_newlines, existing_newlines)
7117 });
7118 self.folding_newlines = cx.spawn(async move |this, cx| {
7119 let (new_newlines, existing_newlines) = task.await;
7120 if new_newlines == existing_newlines {
7121 return;
7122 }
7123 let placeholder = FoldPlaceholder {
7124 render: Arc::new(move |_, _, cx| {
7125 div()
7126 .bg(cx.theme().status().hint_background)
7127 .border_b_1()
7128 .size_full()
7129 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7130 .border_color(cx.theme().status().hint)
7131 .child("\\n")
7132 .into_any()
7133 }),
7134 constrain_width: false,
7135 merge_adjacent: false,
7136 type_tag: Some(type_id),
7137 };
7138 let creases = new_newlines
7139 .into_iter()
7140 .map(|range| Crease::simple(range, placeholder.clone()))
7141 .collect();
7142 this.update(cx, |this, cx| {
7143 this.display_map.update(cx, |display_map, cx| {
7144 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7145 display_map.fold(creases, cx);
7146 });
7147 })
7148 .ok();
7149 });
7150 }
7151
7152 fn refresh_selected_text_highlights(
7153 &mut self,
7154 on_buffer_edit: bool,
7155 window: &mut Window,
7156 cx: &mut Context<Editor>,
7157 ) {
7158 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
7159 else {
7160 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7161 self.quick_selection_highlight_task.take();
7162 self.debounced_selection_highlight_task.take();
7163 return;
7164 };
7165 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7166 if on_buffer_edit
7167 || self
7168 .quick_selection_highlight_task
7169 .as_ref()
7170 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7171 {
7172 let multi_buffer_visible_start = self
7173 .scroll_manager
7174 .anchor()
7175 .anchor
7176 .to_point(&multi_buffer_snapshot);
7177 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7178 multi_buffer_visible_start
7179 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7180 Bias::Left,
7181 );
7182 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7183 self.quick_selection_highlight_task = Some((
7184 query_range.clone(),
7185 self.update_selection_occurrence_highlights(
7186 query_text.clone(),
7187 query_range.clone(),
7188 multi_buffer_visible_range,
7189 false,
7190 window,
7191 cx,
7192 ),
7193 ));
7194 }
7195 if on_buffer_edit
7196 || self
7197 .debounced_selection_highlight_task
7198 .as_ref()
7199 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7200 {
7201 let multi_buffer_start = multi_buffer_snapshot
7202 .anchor_before(0)
7203 .to_point(&multi_buffer_snapshot);
7204 let multi_buffer_end = multi_buffer_snapshot
7205 .anchor_after(multi_buffer_snapshot.len())
7206 .to_point(&multi_buffer_snapshot);
7207 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7208 self.debounced_selection_highlight_task = Some((
7209 query_range.clone(),
7210 self.update_selection_occurrence_highlights(
7211 query_text,
7212 query_range,
7213 multi_buffer_full_range,
7214 true,
7215 window,
7216 cx,
7217 ),
7218 ));
7219 }
7220 }
7221
7222 pub fn refresh_edit_prediction(
7223 &mut self,
7224 debounce: bool,
7225 user_requested: bool,
7226 window: &mut Window,
7227 cx: &mut Context<Self>,
7228 ) -> Option<()> {
7229 if DisableAiSettings::get_global(cx).disable_ai {
7230 return None;
7231 }
7232
7233 let provider = self.edit_prediction_provider()?;
7234 let cursor = self.selections.newest_anchor().head();
7235 let (buffer, cursor_buffer_position) =
7236 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7237
7238 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7239 self.discard_edit_prediction(false, cx);
7240 return None;
7241 }
7242
7243 self.update_visible_edit_prediction(window, cx);
7244
7245 if !user_requested
7246 && (!self.should_show_edit_predictions()
7247 || !self.is_focused(window)
7248 || buffer.read(cx).is_empty())
7249 {
7250 self.discard_edit_prediction(false, cx);
7251 return None;
7252 }
7253
7254 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7255 Some(())
7256 }
7257
7258 fn show_edit_predictions_in_menu(&self) -> bool {
7259 match self.edit_prediction_settings {
7260 EditPredictionSettings::Disabled => false,
7261 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7262 }
7263 }
7264
7265 pub fn edit_predictions_enabled(&self) -> bool {
7266 match self.edit_prediction_settings {
7267 EditPredictionSettings::Disabled => false,
7268 EditPredictionSettings::Enabled { .. } => true,
7269 }
7270 }
7271
7272 fn edit_prediction_requires_modifier(&self) -> bool {
7273 match self.edit_prediction_settings {
7274 EditPredictionSettings::Disabled => false,
7275 EditPredictionSettings::Enabled {
7276 preview_requires_modifier,
7277 ..
7278 } => preview_requires_modifier,
7279 }
7280 }
7281
7282 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7283 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7284 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7285 self.discard_edit_prediction(false, cx);
7286 } else {
7287 let selection = self.selections.newest_anchor();
7288 let cursor = selection.head();
7289
7290 if let Some((buffer, cursor_buffer_position)) =
7291 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7292 {
7293 self.edit_prediction_settings =
7294 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7295 }
7296 }
7297 }
7298
7299 fn edit_prediction_settings_at_position(
7300 &self,
7301 buffer: &Entity<Buffer>,
7302 buffer_position: language::Anchor,
7303 cx: &App,
7304 ) -> EditPredictionSettings {
7305 if !self.mode.is_full()
7306 || !self.show_edit_predictions_override.unwrap_or(true)
7307 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7308 {
7309 return EditPredictionSettings::Disabled;
7310 }
7311
7312 let buffer = buffer.read(cx);
7313
7314 let file = buffer.file();
7315
7316 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7317 return EditPredictionSettings::Disabled;
7318 };
7319
7320 let by_provider = matches!(
7321 self.menu_edit_predictions_policy,
7322 MenuEditPredictionsPolicy::ByProvider
7323 );
7324
7325 let show_in_menu = by_provider
7326 && self
7327 .edit_prediction_provider
7328 .as_ref()
7329 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7330
7331 let preview_requires_modifier =
7332 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7333
7334 EditPredictionSettings::Enabled {
7335 show_in_menu,
7336 preview_requires_modifier,
7337 }
7338 }
7339
7340 fn should_show_edit_predictions(&self) -> bool {
7341 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7342 }
7343
7344 pub fn edit_prediction_preview_is_active(&self) -> bool {
7345 matches!(
7346 self.edit_prediction_preview,
7347 EditPredictionPreview::Active { .. }
7348 )
7349 }
7350
7351 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7352 let cursor = self.selections.newest_anchor().head();
7353 if let Some((buffer, cursor_position)) =
7354 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7355 {
7356 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7357 } else {
7358 false
7359 }
7360 }
7361
7362 pub fn supports_minimap(&self, cx: &App) -> bool {
7363 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7364 }
7365
7366 fn edit_predictions_enabled_in_buffer(
7367 &self,
7368 buffer: &Entity<Buffer>,
7369 buffer_position: language::Anchor,
7370 cx: &App,
7371 ) -> bool {
7372 maybe!({
7373 if self.read_only(cx) {
7374 return Some(false);
7375 }
7376 let provider = self.edit_prediction_provider()?;
7377 if !provider.is_enabled(buffer, buffer_position, cx) {
7378 return Some(false);
7379 }
7380 let buffer = buffer.read(cx);
7381 let Some(file) = buffer.file() else {
7382 return Some(true);
7383 };
7384 let settings = all_language_settings(Some(file), cx);
7385 Some(settings.edit_predictions_enabled_for_file(file, cx))
7386 })
7387 .unwrap_or(false)
7388 }
7389
7390 fn cycle_edit_prediction(
7391 &mut self,
7392 direction: Direction,
7393 window: &mut Window,
7394 cx: &mut Context<Self>,
7395 ) -> Option<()> {
7396 let provider = self.edit_prediction_provider()?;
7397 let cursor = self.selections.newest_anchor().head();
7398 let (buffer, cursor_buffer_position) =
7399 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7400 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7401 return None;
7402 }
7403
7404 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7405 self.update_visible_edit_prediction(window, cx);
7406
7407 Some(())
7408 }
7409
7410 pub fn show_edit_prediction(
7411 &mut self,
7412 _: &ShowEditPrediction,
7413 window: &mut Window,
7414 cx: &mut Context<Self>,
7415 ) {
7416 if !self.has_active_edit_prediction() {
7417 self.refresh_edit_prediction(false, true, window, cx);
7418 return;
7419 }
7420
7421 self.update_visible_edit_prediction(window, cx);
7422 }
7423
7424 pub fn display_cursor_names(
7425 &mut self,
7426 _: &DisplayCursorNames,
7427 window: &mut Window,
7428 cx: &mut Context<Self>,
7429 ) {
7430 self.show_cursor_names(window, cx);
7431 }
7432
7433 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7434 self.show_cursor_names = true;
7435 cx.notify();
7436 cx.spawn_in(window, async move |this, cx| {
7437 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7438 this.update(cx, |this, cx| {
7439 this.show_cursor_names = false;
7440 cx.notify()
7441 })
7442 .ok()
7443 })
7444 .detach();
7445 }
7446
7447 pub fn next_edit_prediction(
7448 &mut self,
7449 _: &NextEditPrediction,
7450 window: &mut Window,
7451 cx: &mut Context<Self>,
7452 ) {
7453 if self.has_active_edit_prediction() {
7454 self.cycle_edit_prediction(Direction::Next, window, cx);
7455 } else {
7456 let is_copilot_disabled = self
7457 .refresh_edit_prediction(false, true, window, cx)
7458 .is_none();
7459 if is_copilot_disabled {
7460 cx.propagate();
7461 }
7462 }
7463 }
7464
7465 pub fn previous_edit_prediction(
7466 &mut self,
7467 _: &PreviousEditPrediction,
7468 window: &mut Window,
7469 cx: &mut Context<Self>,
7470 ) {
7471 if self.has_active_edit_prediction() {
7472 self.cycle_edit_prediction(Direction::Prev, window, cx);
7473 } else {
7474 let is_copilot_disabled = self
7475 .refresh_edit_prediction(false, true, window, cx)
7476 .is_none();
7477 if is_copilot_disabled {
7478 cx.propagate();
7479 }
7480 }
7481 }
7482
7483 pub fn accept_edit_prediction(
7484 &mut self,
7485 _: &AcceptEditPrediction,
7486 window: &mut Window,
7487 cx: &mut Context<Self>,
7488 ) {
7489 if self.show_edit_predictions_in_menu() {
7490 self.hide_context_menu(window, cx);
7491 }
7492
7493 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7494 return;
7495 };
7496
7497 match &active_edit_prediction.completion {
7498 EditPrediction::MoveWithin { target, .. } => {
7499 let target = *target;
7500
7501 if let Some(position_map) = &self.last_position_map {
7502 if position_map
7503 .visible_row_range
7504 .contains(&target.to_display_point(&position_map.snapshot).row())
7505 || !self.edit_prediction_requires_modifier()
7506 {
7507 self.unfold_ranges(&[target..target], true, false, cx);
7508 // Note that this is also done in vim's handler of the Tab action.
7509 self.change_selections(
7510 SelectionEffects::scroll(Autoscroll::newest()),
7511 window,
7512 cx,
7513 |selections| {
7514 selections.select_anchor_ranges([target..target]);
7515 },
7516 );
7517 self.clear_row_highlights::<EditPredictionPreview>();
7518
7519 self.edit_prediction_preview
7520 .set_previous_scroll_position(None);
7521 } else {
7522 self.edit_prediction_preview
7523 .set_previous_scroll_position(Some(
7524 position_map.snapshot.scroll_anchor,
7525 ));
7526
7527 self.highlight_rows::<EditPredictionPreview>(
7528 target..target,
7529 cx.theme().colors().editor_highlighted_line_background,
7530 RowHighlightOptions {
7531 autoscroll: true,
7532 ..Default::default()
7533 },
7534 cx,
7535 );
7536 self.request_autoscroll(Autoscroll::fit(), cx);
7537 }
7538 }
7539 }
7540 EditPrediction::MoveOutside { snapshot, target } => {
7541 if let Some(workspace) = self.workspace() {
7542 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7543 .detach_and_log_err(cx);
7544 }
7545 }
7546 EditPrediction::Edit { edits, .. } => {
7547 self.report_edit_prediction_event(
7548 active_edit_prediction.completion_id.clone(),
7549 true,
7550 cx,
7551 );
7552
7553 if let Some(provider) = self.edit_prediction_provider() {
7554 provider.accept(cx);
7555 }
7556
7557 // Store the transaction ID and selections before applying the edit
7558 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7559
7560 let snapshot = self.buffer.read(cx).snapshot(cx);
7561 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7562
7563 self.buffer.update(cx, |buffer, cx| {
7564 buffer.edit(edits.iter().cloned(), None, cx)
7565 });
7566
7567 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7568 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7569 });
7570
7571 let selections = self.selections.disjoint_anchors_arc();
7572 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7573 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7574 if has_new_transaction {
7575 self.selection_history
7576 .insert_transaction(transaction_id_now, selections);
7577 }
7578 }
7579
7580 self.update_visible_edit_prediction(window, cx);
7581 if self.active_edit_prediction.is_none() {
7582 self.refresh_edit_prediction(true, true, window, cx);
7583 }
7584
7585 cx.notify();
7586 }
7587 }
7588
7589 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7590 }
7591
7592 pub fn accept_partial_edit_prediction(
7593 &mut self,
7594 _: &AcceptPartialEditPrediction,
7595 window: &mut Window,
7596 cx: &mut Context<Self>,
7597 ) {
7598 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7599 return;
7600 };
7601 if self.selections.count() != 1 {
7602 return;
7603 }
7604
7605 match &active_edit_prediction.completion {
7606 EditPrediction::MoveWithin { target, .. } => {
7607 let target = *target;
7608 self.change_selections(
7609 SelectionEffects::scroll(Autoscroll::newest()),
7610 window,
7611 cx,
7612 |selections| {
7613 selections.select_anchor_ranges([target..target]);
7614 },
7615 );
7616 }
7617 EditPrediction::MoveOutside { snapshot, target } => {
7618 if let Some(workspace) = self.workspace() {
7619 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7620 .detach_and_log_err(cx);
7621 }
7622 }
7623 EditPrediction::Edit { edits, .. } => {
7624 self.report_edit_prediction_event(
7625 active_edit_prediction.completion_id.clone(),
7626 true,
7627 cx,
7628 );
7629
7630 // Find an insertion that starts at the cursor position.
7631 let snapshot = self.buffer.read(cx).snapshot(cx);
7632 let cursor_offset = self
7633 .selections
7634 .newest::<usize>(&self.display_snapshot(cx))
7635 .head();
7636 let insertion = edits.iter().find_map(|(range, text)| {
7637 let range = range.to_offset(&snapshot);
7638 if range.is_empty() && range.start == cursor_offset {
7639 Some(text)
7640 } else {
7641 None
7642 }
7643 });
7644
7645 if let Some(text) = insertion {
7646 let mut partial_completion = text
7647 .chars()
7648 .by_ref()
7649 .take_while(|c| c.is_alphabetic())
7650 .collect::<String>();
7651 if partial_completion.is_empty() {
7652 partial_completion = text
7653 .chars()
7654 .by_ref()
7655 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7656 .collect::<String>();
7657 }
7658
7659 cx.emit(EditorEvent::InputHandled {
7660 utf16_range_to_replace: None,
7661 text: partial_completion.clone().into(),
7662 });
7663
7664 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7665
7666 self.refresh_edit_prediction(true, true, window, cx);
7667 cx.notify();
7668 } else {
7669 self.accept_edit_prediction(&Default::default(), window, cx);
7670 }
7671 }
7672 }
7673 }
7674
7675 fn discard_edit_prediction(
7676 &mut self,
7677 should_report_edit_prediction_event: bool,
7678 cx: &mut Context<Self>,
7679 ) -> bool {
7680 if should_report_edit_prediction_event {
7681 let completion_id = self
7682 .active_edit_prediction
7683 .as_ref()
7684 .and_then(|active_completion| active_completion.completion_id.clone());
7685
7686 self.report_edit_prediction_event(completion_id, false, cx);
7687 }
7688
7689 if let Some(provider) = self.edit_prediction_provider() {
7690 provider.discard(cx);
7691 }
7692
7693 self.take_active_edit_prediction(cx)
7694 }
7695
7696 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7697 let Some(provider) = self.edit_prediction_provider() else {
7698 return;
7699 };
7700
7701 let Some((_, buffer, _)) = self
7702 .buffer
7703 .read(cx)
7704 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7705 else {
7706 return;
7707 };
7708
7709 let extension = buffer
7710 .read(cx)
7711 .file()
7712 .and_then(|file| Some(file.path().extension()?.to_string()));
7713
7714 let event_type = match accepted {
7715 true => "Edit Prediction Accepted",
7716 false => "Edit Prediction Discarded",
7717 };
7718 telemetry::event!(
7719 event_type,
7720 provider = provider.name(),
7721 prediction_id = id,
7722 suggestion_accepted = accepted,
7723 file_extension = extension,
7724 );
7725 }
7726
7727 fn open_editor_at_anchor(
7728 snapshot: &language::BufferSnapshot,
7729 target: language::Anchor,
7730 workspace: &Entity<Workspace>,
7731 window: &mut Window,
7732 cx: &mut App,
7733 ) -> Task<Result<()>> {
7734 workspace.update(cx, |workspace, cx| {
7735 let path = snapshot.file().map(|file| file.full_path(cx));
7736 let Some(path) =
7737 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7738 else {
7739 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7740 };
7741 let target = text::ToPoint::to_point(&target, snapshot);
7742 let item = workspace.open_path(path, None, true, window, cx);
7743 window.spawn(cx, async move |cx| {
7744 let Some(editor) = item.await?.downcast::<Editor>() else {
7745 return Ok(());
7746 };
7747 editor
7748 .update_in(cx, |editor, window, cx| {
7749 editor.go_to_singleton_buffer_point(target, window, cx);
7750 })
7751 .ok();
7752 anyhow::Ok(())
7753 })
7754 })
7755 }
7756
7757 pub fn has_active_edit_prediction(&self) -> bool {
7758 self.active_edit_prediction.is_some()
7759 }
7760
7761 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7762 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7763 return false;
7764 };
7765
7766 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7767 self.clear_highlights::<EditPredictionHighlight>(cx);
7768 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7769 true
7770 }
7771
7772 /// Returns true when we're displaying the edit prediction popover below the cursor
7773 /// like we are not previewing and the LSP autocomplete menu is visible
7774 /// or we are in `when_holding_modifier` mode.
7775 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7776 if self.edit_prediction_preview_is_active()
7777 || !self.show_edit_predictions_in_menu()
7778 || !self.edit_predictions_enabled()
7779 {
7780 return false;
7781 }
7782
7783 if self.has_visible_completions_menu() {
7784 return true;
7785 }
7786
7787 has_completion && self.edit_prediction_requires_modifier()
7788 }
7789
7790 fn handle_modifiers_changed(
7791 &mut self,
7792 modifiers: Modifiers,
7793 position_map: &PositionMap,
7794 window: &mut Window,
7795 cx: &mut Context<Self>,
7796 ) {
7797 if self.show_edit_predictions_in_menu() {
7798 self.update_edit_prediction_preview(&modifiers, window, cx);
7799 }
7800
7801 self.update_selection_mode(&modifiers, position_map, window, cx);
7802
7803 let mouse_position = window.mouse_position();
7804 if !position_map.text_hitbox.is_hovered(window) {
7805 return;
7806 }
7807
7808 self.update_hovered_link(
7809 position_map.point_for_position(mouse_position),
7810 &position_map.snapshot,
7811 modifiers,
7812 window,
7813 cx,
7814 )
7815 }
7816
7817 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7818 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7819 if invert {
7820 match multi_cursor_setting {
7821 MultiCursorModifier::Alt => modifiers.alt,
7822 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7823 }
7824 } else {
7825 match multi_cursor_setting {
7826 MultiCursorModifier::Alt => modifiers.secondary(),
7827 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7828 }
7829 }
7830 }
7831
7832 fn columnar_selection_mode(
7833 modifiers: &Modifiers,
7834 cx: &mut Context<Self>,
7835 ) -> Option<ColumnarMode> {
7836 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7837 if Self::multi_cursor_modifier(false, modifiers, cx) {
7838 Some(ColumnarMode::FromMouse)
7839 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7840 Some(ColumnarMode::FromSelection)
7841 } else {
7842 None
7843 }
7844 } else {
7845 None
7846 }
7847 }
7848
7849 fn update_selection_mode(
7850 &mut self,
7851 modifiers: &Modifiers,
7852 position_map: &PositionMap,
7853 window: &mut Window,
7854 cx: &mut Context<Self>,
7855 ) {
7856 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7857 return;
7858 };
7859 if self.selections.pending_anchor().is_none() {
7860 return;
7861 }
7862
7863 let mouse_position = window.mouse_position();
7864 let point_for_position = position_map.point_for_position(mouse_position);
7865 let position = point_for_position.previous_valid;
7866
7867 self.select(
7868 SelectPhase::BeginColumnar {
7869 position,
7870 reset: false,
7871 mode,
7872 goal_column: point_for_position.exact_unclipped.column(),
7873 },
7874 window,
7875 cx,
7876 );
7877 }
7878
7879 fn update_edit_prediction_preview(
7880 &mut self,
7881 modifiers: &Modifiers,
7882 window: &mut Window,
7883 cx: &mut Context<Self>,
7884 ) {
7885 let mut modifiers_held = false;
7886 if let Some(accept_keystroke) = self
7887 .accept_edit_prediction_keybind(false, window, cx)
7888 .keystroke()
7889 {
7890 modifiers_held = modifiers_held
7891 || (accept_keystroke.modifiers() == modifiers
7892 && accept_keystroke.modifiers().modified());
7893 };
7894 if let Some(accept_partial_keystroke) = self
7895 .accept_edit_prediction_keybind(true, window, cx)
7896 .keystroke()
7897 {
7898 modifiers_held = modifiers_held
7899 || (accept_partial_keystroke.modifiers() == modifiers
7900 && accept_partial_keystroke.modifiers().modified());
7901 }
7902
7903 if modifiers_held {
7904 if matches!(
7905 self.edit_prediction_preview,
7906 EditPredictionPreview::Inactive { .. }
7907 ) {
7908 self.edit_prediction_preview = EditPredictionPreview::Active {
7909 previous_scroll_position: None,
7910 since: Instant::now(),
7911 };
7912
7913 self.update_visible_edit_prediction(window, cx);
7914 cx.notify();
7915 }
7916 } else if let EditPredictionPreview::Active {
7917 previous_scroll_position,
7918 since,
7919 } = self.edit_prediction_preview
7920 {
7921 if let (Some(previous_scroll_position), Some(position_map)) =
7922 (previous_scroll_position, self.last_position_map.as_ref())
7923 {
7924 self.set_scroll_position(
7925 previous_scroll_position
7926 .scroll_position(&position_map.snapshot.display_snapshot),
7927 window,
7928 cx,
7929 );
7930 }
7931
7932 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7933 released_too_fast: since.elapsed() < Duration::from_millis(200),
7934 };
7935 self.clear_row_highlights::<EditPredictionPreview>();
7936 self.update_visible_edit_prediction(window, cx);
7937 cx.notify();
7938 }
7939 }
7940
7941 fn update_visible_edit_prediction(
7942 &mut self,
7943 _window: &mut Window,
7944 cx: &mut Context<Self>,
7945 ) -> Option<()> {
7946 if DisableAiSettings::get_global(cx).disable_ai {
7947 return None;
7948 }
7949
7950 if self.ime_transaction.is_some() {
7951 self.discard_edit_prediction(false, cx);
7952 return None;
7953 }
7954
7955 let selection = self.selections.newest_anchor();
7956 let cursor = selection.head();
7957 let multibuffer = self.buffer.read(cx).snapshot(cx);
7958 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7959 let excerpt_id = cursor.excerpt_id;
7960
7961 let show_in_menu = self.show_edit_predictions_in_menu();
7962 let completions_menu_has_precedence = !show_in_menu
7963 && (self.context_menu.borrow().is_some()
7964 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7965
7966 if completions_menu_has_precedence
7967 || !offset_selection.is_empty()
7968 || self
7969 .active_edit_prediction
7970 .as_ref()
7971 .is_some_and(|completion| {
7972 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7973 return false;
7974 };
7975 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7976 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7977 !invalidation_range.contains(&offset_selection.head())
7978 })
7979 {
7980 self.discard_edit_prediction(false, cx);
7981 return None;
7982 }
7983
7984 self.take_active_edit_prediction(cx);
7985 let Some(provider) = self.edit_prediction_provider() else {
7986 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7987 return None;
7988 };
7989
7990 let (buffer, cursor_buffer_position) =
7991 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7992
7993 self.edit_prediction_settings =
7994 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7995
7996 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7997
7998 if self.edit_prediction_indent_conflict {
7999 let cursor_point = cursor.to_point(&multibuffer);
8000
8001 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
8002
8003 if let Some((_, indent)) = indents.iter().next()
8004 && indent.len == cursor_point.column
8005 {
8006 self.edit_prediction_indent_conflict = false;
8007 }
8008 }
8009
8010 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8011
8012 let (completion_id, edits, edit_preview) = match edit_prediction {
8013 edit_prediction::EditPrediction::Local {
8014 id,
8015 edits,
8016 edit_preview,
8017 } => (id, edits, edit_preview),
8018 edit_prediction::EditPrediction::Jump {
8019 id,
8020 snapshot,
8021 target,
8022 } => {
8023 self.stale_edit_prediction_in_menu = None;
8024 self.active_edit_prediction = Some(EditPredictionState {
8025 inlay_ids: vec![],
8026 completion: EditPrediction::MoveOutside { snapshot, target },
8027 completion_id: id,
8028 invalidation_range: None,
8029 });
8030 cx.notify();
8031 return Some(());
8032 }
8033 };
8034
8035 let edits = edits
8036 .into_iter()
8037 .flat_map(|(range, new_text)| {
8038 Some((
8039 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8040 new_text,
8041 ))
8042 })
8043 .collect::<Vec<_>>();
8044 if edits.is_empty() {
8045 return None;
8046 }
8047
8048 let first_edit_start = edits.first().unwrap().0.start;
8049 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8050 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8051
8052 let last_edit_end = edits.last().unwrap().0.end;
8053 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8054 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8055
8056 let cursor_row = cursor.to_point(&multibuffer).row;
8057
8058 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8059
8060 let mut inlay_ids = Vec::new();
8061 let invalidation_row_range;
8062 let move_invalidation_row_range = if cursor_row < edit_start_row {
8063 Some(cursor_row..edit_end_row)
8064 } else if cursor_row > edit_end_row {
8065 Some(edit_start_row..cursor_row)
8066 } else {
8067 None
8068 };
8069 let supports_jump = self
8070 .edit_prediction_provider
8071 .as_ref()
8072 .map(|provider| provider.provider.supports_jump_to_edit())
8073 .unwrap_or(true);
8074
8075 let is_move = supports_jump
8076 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8077 let completion = if is_move {
8078 invalidation_row_range =
8079 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8080 let target = first_edit_start;
8081 EditPrediction::MoveWithin { target, snapshot }
8082 } else {
8083 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8084 && !self.edit_predictions_hidden_for_vim_mode;
8085
8086 if show_completions_in_buffer {
8087 if edits
8088 .iter()
8089 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8090 {
8091 let mut inlays = Vec::new();
8092 for (range, new_text) in &edits {
8093 let inlay = Inlay::edit_prediction(
8094 post_inc(&mut self.next_inlay_id),
8095 range.start,
8096 new_text.as_str(),
8097 );
8098 inlay_ids.push(inlay.id);
8099 inlays.push(inlay);
8100 }
8101
8102 self.splice_inlays(&[], inlays, cx);
8103 } else {
8104 let background_color = cx.theme().status().deleted_background;
8105 self.highlight_text::<EditPredictionHighlight>(
8106 edits.iter().map(|(range, _)| range.clone()).collect(),
8107 HighlightStyle {
8108 background_color: Some(background_color),
8109 ..Default::default()
8110 },
8111 cx,
8112 );
8113 }
8114 }
8115
8116 invalidation_row_range = edit_start_row..edit_end_row;
8117
8118 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8119 if provider.show_tab_accept_marker() {
8120 EditDisplayMode::TabAccept
8121 } else {
8122 EditDisplayMode::Inline
8123 }
8124 } else {
8125 EditDisplayMode::DiffPopover
8126 };
8127
8128 EditPrediction::Edit {
8129 edits,
8130 edit_preview,
8131 display_mode,
8132 snapshot,
8133 }
8134 };
8135
8136 let invalidation_range = multibuffer
8137 .anchor_before(Point::new(invalidation_row_range.start, 0))
8138 ..multibuffer.anchor_after(Point::new(
8139 invalidation_row_range.end,
8140 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8141 ));
8142
8143 self.stale_edit_prediction_in_menu = None;
8144 self.active_edit_prediction = Some(EditPredictionState {
8145 inlay_ids,
8146 completion,
8147 completion_id,
8148 invalidation_range: Some(invalidation_range),
8149 });
8150
8151 cx.notify();
8152
8153 Some(())
8154 }
8155
8156 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8157 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8158 }
8159
8160 fn clear_tasks(&mut self) {
8161 self.tasks.clear()
8162 }
8163
8164 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8165 if self.tasks.insert(key, value).is_some() {
8166 // This case should hopefully be rare, but just in case...
8167 log::error!(
8168 "multiple different run targets found on a single line, only the last target will be rendered"
8169 )
8170 }
8171 }
8172
8173 /// Get all display points of breakpoints that will be rendered within editor
8174 ///
8175 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8176 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8177 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8178 fn active_breakpoints(
8179 &self,
8180 range: Range<DisplayRow>,
8181 window: &mut Window,
8182 cx: &mut Context<Self>,
8183 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8184 let mut breakpoint_display_points = HashMap::default();
8185
8186 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8187 return breakpoint_display_points;
8188 };
8189
8190 let snapshot = self.snapshot(window, cx);
8191
8192 let multi_buffer_snapshot = snapshot.display_snapshot.buffer_snapshot();
8193 let Some(project) = self.project() else {
8194 return breakpoint_display_points;
8195 };
8196
8197 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8198 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8199
8200 for (buffer_snapshot, range, excerpt_id) in
8201 multi_buffer_snapshot.range_to_buffer_ranges(range)
8202 {
8203 let Some(buffer) = project
8204 .read(cx)
8205 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8206 else {
8207 continue;
8208 };
8209 let breakpoints = breakpoint_store.read(cx).breakpoints(
8210 &buffer,
8211 Some(
8212 buffer_snapshot.anchor_before(range.start)
8213 ..buffer_snapshot.anchor_after(range.end),
8214 ),
8215 buffer_snapshot,
8216 cx,
8217 );
8218 for (breakpoint, state) in breakpoints {
8219 let multi_buffer_anchor =
8220 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8221 let position = multi_buffer_anchor
8222 .to_point(multi_buffer_snapshot)
8223 .to_display_point(&snapshot);
8224
8225 breakpoint_display_points.insert(
8226 position.row(),
8227 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8228 );
8229 }
8230 }
8231
8232 breakpoint_display_points
8233 }
8234
8235 fn breakpoint_context_menu(
8236 &self,
8237 anchor: Anchor,
8238 window: &mut Window,
8239 cx: &mut Context<Self>,
8240 ) -> Entity<ui::ContextMenu> {
8241 let weak_editor = cx.weak_entity();
8242 let focus_handle = self.focus_handle(cx);
8243
8244 let row = self
8245 .buffer
8246 .read(cx)
8247 .snapshot(cx)
8248 .summary_for_anchor::<Point>(&anchor)
8249 .row;
8250
8251 let breakpoint = self
8252 .breakpoint_at_row(row, window, cx)
8253 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8254
8255 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8256 "Edit Log Breakpoint"
8257 } else {
8258 "Set Log Breakpoint"
8259 };
8260
8261 let condition_breakpoint_msg = if breakpoint
8262 .as_ref()
8263 .is_some_and(|bp| bp.1.condition.is_some())
8264 {
8265 "Edit Condition Breakpoint"
8266 } else {
8267 "Set Condition Breakpoint"
8268 };
8269
8270 let hit_condition_breakpoint_msg = if breakpoint
8271 .as_ref()
8272 .is_some_and(|bp| bp.1.hit_condition.is_some())
8273 {
8274 "Edit Hit Condition Breakpoint"
8275 } else {
8276 "Set Hit Condition Breakpoint"
8277 };
8278
8279 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8280 "Unset Breakpoint"
8281 } else {
8282 "Set Breakpoint"
8283 };
8284
8285 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8286
8287 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8288 BreakpointState::Enabled => Some("Disable"),
8289 BreakpointState::Disabled => Some("Enable"),
8290 });
8291
8292 let (anchor, breakpoint) =
8293 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8294
8295 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8296 menu.on_blur_subscription(Subscription::new(|| {}))
8297 .context(focus_handle)
8298 .when(run_to_cursor, |this| {
8299 let weak_editor = weak_editor.clone();
8300 this.entry("Run to cursor", None, move |window, cx| {
8301 weak_editor
8302 .update(cx, |editor, cx| {
8303 editor.change_selections(
8304 SelectionEffects::no_scroll(),
8305 window,
8306 cx,
8307 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8308 );
8309 })
8310 .ok();
8311
8312 window.dispatch_action(Box::new(RunToCursor), cx);
8313 })
8314 .separator()
8315 })
8316 .when_some(toggle_state_msg, |this, msg| {
8317 this.entry(msg, None, {
8318 let weak_editor = weak_editor.clone();
8319 let breakpoint = breakpoint.clone();
8320 move |_window, cx| {
8321 weak_editor
8322 .update(cx, |this, cx| {
8323 this.edit_breakpoint_at_anchor(
8324 anchor,
8325 breakpoint.as_ref().clone(),
8326 BreakpointEditAction::InvertState,
8327 cx,
8328 );
8329 })
8330 .log_err();
8331 }
8332 })
8333 })
8334 .entry(set_breakpoint_msg, None, {
8335 let weak_editor = weak_editor.clone();
8336 let breakpoint = breakpoint.clone();
8337 move |_window, cx| {
8338 weak_editor
8339 .update(cx, |this, cx| {
8340 this.edit_breakpoint_at_anchor(
8341 anchor,
8342 breakpoint.as_ref().clone(),
8343 BreakpointEditAction::Toggle,
8344 cx,
8345 );
8346 })
8347 .log_err();
8348 }
8349 })
8350 .entry(log_breakpoint_msg, None, {
8351 let breakpoint = breakpoint.clone();
8352 let weak_editor = weak_editor.clone();
8353 move |window, cx| {
8354 weak_editor
8355 .update(cx, |this, cx| {
8356 this.add_edit_breakpoint_block(
8357 anchor,
8358 breakpoint.as_ref(),
8359 BreakpointPromptEditAction::Log,
8360 window,
8361 cx,
8362 );
8363 })
8364 .log_err();
8365 }
8366 })
8367 .entry(condition_breakpoint_msg, None, {
8368 let breakpoint = breakpoint.clone();
8369 let weak_editor = weak_editor.clone();
8370 move |window, cx| {
8371 weak_editor
8372 .update(cx, |this, cx| {
8373 this.add_edit_breakpoint_block(
8374 anchor,
8375 breakpoint.as_ref(),
8376 BreakpointPromptEditAction::Condition,
8377 window,
8378 cx,
8379 );
8380 })
8381 .log_err();
8382 }
8383 })
8384 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8385 weak_editor
8386 .update(cx, |this, cx| {
8387 this.add_edit_breakpoint_block(
8388 anchor,
8389 breakpoint.as_ref(),
8390 BreakpointPromptEditAction::HitCondition,
8391 window,
8392 cx,
8393 );
8394 })
8395 .log_err();
8396 })
8397 })
8398 }
8399
8400 fn render_breakpoint(
8401 &self,
8402 position: Anchor,
8403 row: DisplayRow,
8404 breakpoint: &Breakpoint,
8405 state: Option<BreakpointSessionState>,
8406 cx: &mut Context<Self>,
8407 ) -> IconButton {
8408 let is_rejected = state.is_some_and(|s| !s.verified);
8409 // Is it a breakpoint that shows up when hovering over gutter?
8410 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8411 (false, false),
8412 |PhantomBreakpointIndicator {
8413 is_active,
8414 display_row,
8415 collides_with_existing_breakpoint,
8416 }| {
8417 (
8418 is_active && display_row == row,
8419 collides_with_existing_breakpoint,
8420 )
8421 },
8422 );
8423
8424 let (color, icon) = {
8425 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8426 (false, false) => ui::IconName::DebugBreakpoint,
8427 (true, false) => ui::IconName::DebugLogBreakpoint,
8428 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8429 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8430 };
8431
8432 let color = if is_phantom {
8433 Color::Hint
8434 } else if is_rejected {
8435 Color::Disabled
8436 } else {
8437 Color::Debugger
8438 };
8439
8440 (color, icon)
8441 };
8442
8443 let breakpoint = Arc::from(breakpoint.clone());
8444
8445 let alt_as_text = gpui::Keystroke {
8446 modifiers: Modifiers::secondary_key(),
8447 ..Default::default()
8448 };
8449 let primary_action_text = if breakpoint.is_disabled() {
8450 "Enable breakpoint"
8451 } else if is_phantom && !collides_with_existing {
8452 "Set breakpoint"
8453 } else {
8454 "Unset breakpoint"
8455 };
8456 let focus_handle = self.focus_handle.clone();
8457
8458 let meta = if is_rejected {
8459 SharedString::from("No executable code is associated with this line.")
8460 } else if collides_with_existing && !breakpoint.is_disabled() {
8461 SharedString::from(format!(
8462 "{alt_as_text}-click to disable,\nright-click for more options."
8463 ))
8464 } else {
8465 SharedString::from("Right-click for more options.")
8466 };
8467 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8468 .icon_size(IconSize::XSmall)
8469 .size(ui::ButtonSize::None)
8470 .when(is_rejected, |this| {
8471 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8472 })
8473 .icon_color(color)
8474 .style(ButtonStyle::Transparent)
8475 .on_click(cx.listener({
8476 move |editor, event: &ClickEvent, window, cx| {
8477 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8478 BreakpointEditAction::InvertState
8479 } else {
8480 BreakpointEditAction::Toggle
8481 };
8482
8483 window.focus(&editor.focus_handle(cx));
8484 editor.edit_breakpoint_at_anchor(
8485 position,
8486 breakpoint.as_ref().clone(),
8487 edit_action,
8488 cx,
8489 );
8490 }
8491 }))
8492 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8493 editor.set_breakpoint_context_menu(
8494 row,
8495 Some(position),
8496 event.position(),
8497 window,
8498 cx,
8499 );
8500 }))
8501 .tooltip(move |window, cx| {
8502 Tooltip::with_meta_in(
8503 primary_action_text,
8504 Some(&ToggleBreakpoint),
8505 meta.clone(),
8506 &focus_handle,
8507 window,
8508 cx,
8509 )
8510 })
8511 }
8512
8513 fn build_tasks_context(
8514 project: &Entity<Project>,
8515 buffer: &Entity<Buffer>,
8516 buffer_row: u32,
8517 tasks: &Arc<RunnableTasks>,
8518 cx: &mut Context<Self>,
8519 ) -> Task<Option<task::TaskContext>> {
8520 let position = Point::new(buffer_row, tasks.column);
8521 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8522 let location = Location {
8523 buffer: buffer.clone(),
8524 range: range_start..range_start,
8525 };
8526 // Fill in the environmental variables from the tree-sitter captures
8527 let mut captured_task_variables = TaskVariables::default();
8528 for (capture_name, value) in tasks.extra_variables.clone() {
8529 captured_task_variables.insert(
8530 task::VariableName::Custom(capture_name.into()),
8531 value.clone(),
8532 );
8533 }
8534 project.update(cx, |project, cx| {
8535 project.task_store().update(cx, |task_store, cx| {
8536 task_store.task_context_for_location(captured_task_variables, location, cx)
8537 })
8538 })
8539 }
8540
8541 pub fn spawn_nearest_task(
8542 &mut self,
8543 action: &SpawnNearestTask,
8544 window: &mut Window,
8545 cx: &mut Context<Self>,
8546 ) {
8547 let Some((workspace, _)) = self.workspace.clone() else {
8548 return;
8549 };
8550 let Some(project) = self.project.clone() else {
8551 return;
8552 };
8553
8554 // Try to find a closest, enclosing node using tree-sitter that has a task
8555 let Some((buffer, buffer_row, tasks)) = self
8556 .find_enclosing_node_task(cx)
8557 // Or find the task that's closest in row-distance.
8558 .or_else(|| self.find_closest_task(cx))
8559 else {
8560 return;
8561 };
8562
8563 let reveal_strategy = action.reveal;
8564 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8565 cx.spawn_in(window, async move |_, cx| {
8566 let context = task_context.await?;
8567 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8568
8569 let resolved = &mut resolved_task.resolved;
8570 resolved.reveal = reveal_strategy;
8571
8572 workspace
8573 .update_in(cx, |workspace, window, cx| {
8574 workspace.schedule_resolved_task(
8575 task_source_kind,
8576 resolved_task,
8577 false,
8578 window,
8579 cx,
8580 );
8581 })
8582 .ok()
8583 })
8584 .detach();
8585 }
8586
8587 fn find_closest_task(
8588 &mut self,
8589 cx: &mut Context<Self>,
8590 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8591 let cursor_row = self
8592 .selections
8593 .newest_adjusted(&self.display_snapshot(cx))
8594 .head()
8595 .row;
8596
8597 let ((buffer_id, row), tasks) = self
8598 .tasks
8599 .iter()
8600 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8601
8602 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8603 let tasks = Arc::new(tasks.to_owned());
8604 Some((buffer, *row, tasks))
8605 }
8606
8607 fn find_enclosing_node_task(
8608 &mut self,
8609 cx: &mut Context<Self>,
8610 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8611 let snapshot = self.buffer.read(cx).snapshot(cx);
8612 let offset = self
8613 .selections
8614 .newest::<usize>(&self.display_snapshot(cx))
8615 .head();
8616 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8617 let buffer_id = excerpt.buffer().remote_id();
8618
8619 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8620 let mut cursor = layer.node().walk();
8621
8622 while cursor.goto_first_child_for_byte(offset).is_some() {
8623 if cursor.node().end_byte() == offset {
8624 cursor.goto_next_sibling();
8625 }
8626 }
8627
8628 // Ascend to the smallest ancestor that contains the range and has a task.
8629 loop {
8630 let node = cursor.node();
8631 let node_range = node.byte_range();
8632 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8633
8634 // Check if this node contains our offset
8635 if node_range.start <= offset && node_range.end >= offset {
8636 // If it contains offset, check for task
8637 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8638 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8639 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8640 }
8641 }
8642
8643 if !cursor.goto_parent() {
8644 break;
8645 }
8646 }
8647 None
8648 }
8649
8650 fn render_run_indicator(
8651 &self,
8652 _style: &EditorStyle,
8653 is_active: bool,
8654 row: DisplayRow,
8655 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8656 cx: &mut Context<Self>,
8657 ) -> IconButton {
8658 let color = Color::Muted;
8659 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8660
8661 IconButton::new(
8662 ("run_indicator", row.0 as usize),
8663 ui::IconName::PlayOutlined,
8664 )
8665 .shape(ui::IconButtonShape::Square)
8666 .icon_size(IconSize::XSmall)
8667 .icon_color(color)
8668 .toggle_state(is_active)
8669 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8670 let quick_launch = match e {
8671 ClickEvent::Keyboard(_) => true,
8672 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8673 };
8674
8675 window.focus(&editor.focus_handle(cx));
8676 editor.toggle_code_actions(
8677 &ToggleCodeActions {
8678 deployed_from: Some(CodeActionSource::RunMenu(row)),
8679 quick_launch,
8680 },
8681 window,
8682 cx,
8683 );
8684 }))
8685 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8686 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8687 }))
8688 }
8689
8690 pub fn context_menu_visible(&self) -> bool {
8691 !self.edit_prediction_preview_is_active()
8692 && self
8693 .context_menu
8694 .borrow()
8695 .as_ref()
8696 .is_some_and(|menu| menu.visible())
8697 }
8698
8699 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8700 self.context_menu
8701 .borrow()
8702 .as_ref()
8703 .map(|menu| menu.origin())
8704 }
8705
8706 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8707 self.context_menu_options = Some(options);
8708 }
8709
8710 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8711 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8712
8713 fn render_edit_prediction_popover(
8714 &mut self,
8715 text_bounds: &Bounds<Pixels>,
8716 content_origin: gpui::Point<Pixels>,
8717 right_margin: Pixels,
8718 editor_snapshot: &EditorSnapshot,
8719 visible_row_range: Range<DisplayRow>,
8720 scroll_top: ScrollOffset,
8721 scroll_bottom: ScrollOffset,
8722 line_layouts: &[LineWithInvisibles],
8723 line_height: Pixels,
8724 scroll_position: gpui::Point<ScrollOffset>,
8725 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8726 newest_selection_head: Option<DisplayPoint>,
8727 editor_width: Pixels,
8728 style: &EditorStyle,
8729 window: &mut Window,
8730 cx: &mut App,
8731 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8732 if self.mode().is_minimap() {
8733 return None;
8734 }
8735 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8736
8737 if self.edit_prediction_visible_in_cursor_popover(true) {
8738 return None;
8739 }
8740
8741 match &active_edit_prediction.completion {
8742 EditPrediction::MoveWithin { target, .. } => {
8743 let target_display_point = target.to_display_point(editor_snapshot);
8744
8745 if self.edit_prediction_requires_modifier() {
8746 if !self.edit_prediction_preview_is_active() {
8747 return None;
8748 }
8749
8750 self.render_edit_prediction_modifier_jump_popover(
8751 text_bounds,
8752 content_origin,
8753 visible_row_range,
8754 line_layouts,
8755 line_height,
8756 scroll_pixel_position,
8757 newest_selection_head,
8758 target_display_point,
8759 window,
8760 cx,
8761 )
8762 } else {
8763 self.render_edit_prediction_eager_jump_popover(
8764 text_bounds,
8765 content_origin,
8766 editor_snapshot,
8767 visible_row_range,
8768 scroll_top,
8769 scroll_bottom,
8770 line_height,
8771 scroll_pixel_position,
8772 target_display_point,
8773 editor_width,
8774 window,
8775 cx,
8776 )
8777 }
8778 }
8779 EditPrediction::Edit {
8780 display_mode: EditDisplayMode::Inline,
8781 ..
8782 } => None,
8783 EditPrediction::Edit {
8784 display_mode: EditDisplayMode::TabAccept,
8785 edits,
8786 ..
8787 } => {
8788 let range = &edits.first()?.0;
8789 let target_display_point = range.end.to_display_point(editor_snapshot);
8790
8791 self.render_edit_prediction_end_of_line_popover(
8792 "Accept",
8793 editor_snapshot,
8794 visible_row_range,
8795 target_display_point,
8796 line_height,
8797 scroll_pixel_position,
8798 content_origin,
8799 editor_width,
8800 window,
8801 cx,
8802 )
8803 }
8804 EditPrediction::Edit {
8805 edits,
8806 edit_preview,
8807 display_mode: EditDisplayMode::DiffPopover,
8808 snapshot,
8809 } => self.render_edit_prediction_diff_popover(
8810 text_bounds,
8811 content_origin,
8812 right_margin,
8813 editor_snapshot,
8814 visible_row_range,
8815 line_layouts,
8816 line_height,
8817 scroll_position,
8818 scroll_pixel_position,
8819 newest_selection_head,
8820 editor_width,
8821 style,
8822 edits,
8823 edit_preview,
8824 snapshot,
8825 window,
8826 cx,
8827 ),
8828 EditPrediction::MoveOutside { snapshot, .. } => {
8829 let file_name = snapshot
8830 .file()
8831 .map(|file| file.file_name(cx))
8832 .unwrap_or("untitled");
8833 let mut element = self
8834 .render_edit_prediction_line_popover(
8835 format!("Jump to {file_name}"),
8836 Some(IconName::ZedPredict),
8837 window,
8838 cx,
8839 )
8840 .into_any();
8841
8842 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8843 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8844 let origin_y = text_bounds.size.height - size.height - px(30.);
8845 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8846 element.prepaint_at(origin, window, cx);
8847
8848 Some((element, origin))
8849 }
8850 }
8851 }
8852
8853 fn render_edit_prediction_modifier_jump_popover(
8854 &mut self,
8855 text_bounds: &Bounds<Pixels>,
8856 content_origin: gpui::Point<Pixels>,
8857 visible_row_range: Range<DisplayRow>,
8858 line_layouts: &[LineWithInvisibles],
8859 line_height: Pixels,
8860 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8861 newest_selection_head: Option<DisplayPoint>,
8862 target_display_point: DisplayPoint,
8863 window: &mut Window,
8864 cx: &mut App,
8865 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8866 let scrolled_content_origin =
8867 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8868
8869 const SCROLL_PADDING_Y: Pixels = px(12.);
8870
8871 if target_display_point.row() < visible_row_range.start {
8872 return self.render_edit_prediction_scroll_popover(
8873 |_| SCROLL_PADDING_Y,
8874 IconName::ArrowUp,
8875 visible_row_range,
8876 line_layouts,
8877 newest_selection_head,
8878 scrolled_content_origin,
8879 window,
8880 cx,
8881 );
8882 } else if target_display_point.row() >= visible_row_range.end {
8883 return self.render_edit_prediction_scroll_popover(
8884 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8885 IconName::ArrowDown,
8886 visible_row_range,
8887 line_layouts,
8888 newest_selection_head,
8889 scrolled_content_origin,
8890 window,
8891 cx,
8892 );
8893 }
8894
8895 const POLE_WIDTH: Pixels = px(2.);
8896
8897 let line_layout =
8898 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8899 let target_column = target_display_point.column() as usize;
8900
8901 let target_x = line_layout.x_for_index(target_column);
8902 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8903 - scroll_pixel_position.y;
8904
8905 let flag_on_right = target_x < text_bounds.size.width / 2.;
8906
8907 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8908 border_color.l += 0.001;
8909
8910 let mut element = v_flex()
8911 .items_end()
8912 .when(flag_on_right, |el| el.items_start())
8913 .child(if flag_on_right {
8914 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8915 .rounded_bl(px(0.))
8916 .rounded_tl(px(0.))
8917 .border_l_2()
8918 .border_color(border_color)
8919 } else {
8920 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8921 .rounded_br(px(0.))
8922 .rounded_tr(px(0.))
8923 .border_r_2()
8924 .border_color(border_color)
8925 })
8926 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8927 .into_any();
8928
8929 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8930
8931 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8932 - point(
8933 if flag_on_right {
8934 POLE_WIDTH
8935 } else {
8936 size.width - POLE_WIDTH
8937 },
8938 size.height - line_height,
8939 );
8940
8941 origin.x = origin.x.max(content_origin.x);
8942
8943 element.prepaint_at(origin, window, cx);
8944
8945 Some((element, origin))
8946 }
8947
8948 fn render_edit_prediction_scroll_popover(
8949 &mut self,
8950 to_y: impl Fn(Size<Pixels>) -> Pixels,
8951 scroll_icon: IconName,
8952 visible_row_range: Range<DisplayRow>,
8953 line_layouts: &[LineWithInvisibles],
8954 newest_selection_head: Option<DisplayPoint>,
8955 scrolled_content_origin: gpui::Point<Pixels>,
8956 window: &mut Window,
8957 cx: &mut App,
8958 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8959 let mut element = self
8960 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8961 .into_any();
8962
8963 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8964
8965 let cursor = newest_selection_head?;
8966 let cursor_row_layout =
8967 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8968 let cursor_column = cursor.column() as usize;
8969
8970 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8971
8972 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8973
8974 element.prepaint_at(origin, window, cx);
8975 Some((element, origin))
8976 }
8977
8978 fn render_edit_prediction_eager_jump_popover(
8979 &mut self,
8980 text_bounds: &Bounds<Pixels>,
8981 content_origin: gpui::Point<Pixels>,
8982 editor_snapshot: &EditorSnapshot,
8983 visible_row_range: Range<DisplayRow>,
8984 scroll_top: ScrollOffset,
8985 scroll_bottom: ScrollOffset,
8986 line_height: Pixels,
8987 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8988 target_display_point: DisplayPoint,
8989 editor_width: Pixels,
8990 window: &mut Window,
8991 cx: &mut App,
8992 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8993 if target_display_point.row().as_f64() < scroll_top {
8994 let mut element = self
8995 .render_edit_prediction_line_popover(
8996 "Jump to Edit",
8997 Some(IconName::ArrowUp),
8998 window,
8999 cx,
9000 )
9001 .into_any();
9002
9003 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9004 let offset = point(
9005 (text_bounds.size.width - size.width) / 2.,
9006 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9007 );
9008
9009 let origin = text_bounds.origin + offset;
9010 element.prepaint_at(origin, window, cx);
9011 Some((element, origin))
9012 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9013 let mut element = self
9014 .render_edit_prediction_line_popover(
9015 "Jump to Edit",
9016 Some(IconName::ArrowDown),
9017 window,
9018 cx,
9019 )
9020 .into_any();
9021
9022 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9023 let offset = point(
9024 (text_bounds.size.width - size.width) / 2.,
9025 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9026 );
9027
9028 let origin = text_bounds.origin + offset;
9029 element.prepaint_at(origin, window, cx);
9030 Some((element, origin))
9031 } else {
9032 self.render_edit_prediction_end_of_line_popover(
9033 "Jump to Edit",
9034 editor_snapshot,
9035 visible_row_range,
9036 target_display_point,
9037 line_height,
9038 scroll_pixel_position,
9039 content_origin,
9040 editor_width,
9041 window,
9042 cx,
9043 )
9044 }
9045 }
9046
9047 fn render_edit_prediction_end_of_line_popover(
9048 self: &mut Editor,
9049 label: &'static str,
9050 editor_snapshot: &EditorSnapshot,
9051 visible_row_range: Range<DisplayRow>,
9052 target_display_point: DisplayPoint,
9053 line_height: Pixels,
9054 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9055 content_origin: gpui::Point<Pixels>,
9056 editor_width: Pixels,
9057 window: &mut Window,
9058 cx: &mut App,
9059 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9060 let target_line_end = DisplayPoint::new(
9061 target_display_point.row(),
9062 editor_snapshot.line_len(target_display_point.row()),
9063 );
9064
9065 let mut element = self
9066 .render_edit_prediction_line_popover(label, None, window, cx)
9067 .into_any();
9068
9069 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9070
9071 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
9072
9073 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9074 let mut origin = start_point
9075 + line_origin
9076 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9077 origin.x = origin.x.max(content_origin.x);
9078
9079 let max_x = content_origin.x + editor_width - size.width;
9080
9081 if origin.x > max_x {
9082 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9083
9084 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9085 origin.y += offset;
9086 IconName::ArrowUp
9087 } else {
9088 origin.y -= offset;
9089 IconName::ArrowDown
9090 };
9091
9092 element = self
9093 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9094 .into_any();
9095
9096 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9097
9098 origin.x = content_origin.x + editor_width - size.width - px(2.);
9099 }
9100
9101 element.prepaint_at(origin, window, cx);
9102 Some((element, origin))
9103 }
9104
9105 fn render_edit_prediction_diff_popover(
9106 self: &Editor,
9107 text_bounds: &Bounds<Pixels>,
9108 content_origin: gpui::Point<Pixels>,
9109 right_margin: Pixels,
9110 editor_snapshot: &EditorSnapshot,
9111 visible_row_range: Range<DisplayRow>,
9112 line_layouts: &[LineWithInvisibles],
9113 line_height: Pixels,
9114 scroll_position: gpui::Point<ScrollOffset>,
9115 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9116 newest_selection_head: Option<DisplayPoint>,
9117 editor_width: Pixels,
9118 style: &EditorStyle,
9119 edits: &Vec<(Range<Anchor>, String)>,
9120 edit_preview: &Option<language::EditPreview>,
9121 snapshot: &language::BufferSnapshot,
9122 window: &mut Window,
9123 cx: &mut App,
9124 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9125 let edit_start = edits
9126 .first()
9127 .unwrap()
9128 .0
9129 .start
9130 .to_display_point(editor_snapshot);
9131 let edit_end = edits
9132 .last()
9133 .unwrap()
9134 .0
9135 .end
9136 .to_display_point(editor_snapshot);
9137
9138 let is_visible = visible_row_range.contains(&edit_start.row())
9139 || visible_row_range.contains(&edit_end.row());
9140 if !is_visible {
9141 return None;
9142 }
9143
9144 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9145 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9146 } else {
9147 // Fallback for providers without edit_preview
9148 crate::edit_prediction_fallback_text(edits, cx)
9149 };
9150
9151 let styled_text = highlighted_edits.to_styled_text(&style.text);
9152 let line_count = highlighted_edits.text.lines().count();
9153
9154 const BORDER_WIDTH: Pixels = px(1.);
9155
9156 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9157 let has_keybind = keybind.is_some();
9158
9159 let mut element = h_flex()
9160 .items_start()
9161 .child(
9162 h_flex()
9163 .bg(cx.theme().colors().editor_background)
9164 .border(BORDER_WIDTH)
9165 .shadow_xs()
9166 .border_color(cx.theme().colors().border)
9167 .rounded_l_lg()
9168 .when(line_count > 1, |el| el.rounded_br_lg())
9169 .pr_1()
9170 .child(styled_text),
9171 )
9172 .child(
9173 h_flex()
9174 .h(line_height + BORDER_WIDTH * 2.)
9175 .px_1p5()
9176 .gap_1()
9177 // Workaround: For some reason, there's a gap if we don't do this
9178 .ml(-BORDER_WIDTH)
9179 .shadow(vec![gpui::BoxShadow {
9180 color: gpui::black().opacity(0.05),
9181 offset: point(px(1.), px(1.)),
9182 blur_radius: px(2.),
9183 spread_radius: px(0.),
9184 }])
9185 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9186 .border(BORDER_WIDTH)
9187 .border_color(cx.theme().colors().border)
9188 .rounded_r_lg()
9189 .id("edit_prediction_diff_popover_keybind")
9190 .when(!has_keybind, |el| {
9191 let status_colors = cx.theme().status();
9192
9193 el.bg(status_colors.error_background)
9194 .border_color(status_colors.error.opacity(0.6))
9195 .child(Icon::new(IconName::Info).color(Color::Error))
9196 .cursor_default()
9197 .hoverable_tooltip(move |_window, cx| {
9198 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9199 })
9200 })
9201 .children(keybind),
9202 )
9203 .into_any();
9204
9205 let longest_row =
9206 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9207 let longest_line_width = if visible_row_range.contains(&longest_row) {
9208 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9209 } else {
9210 layout_line(
9211 longest_row,
9212 editor_snapshot,
9213 style,
9214 editor_width,
9215 |_| false,
9216 window,
9217 cx,
9218 )
9219 .width
9220 };
9221
9222 let viewport_bounds =
9223 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9224 right: -right_margin,
9225 ..Default::default()
9226 });
9227
9228 let x_after_longest = Pixels::from(
9229 ScrollPixelOffset::from(
9230 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9231 ) - scroll_pixel_position.x,
9232 );
9233
9234 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9235
9236 // Fully visible if it can be displayed within the window (allow overlapping other
9237 // panes). However, this is only allowed if the popover starts within text_bounds.
9238 let can_position_to_the_right = x_after_longest < text_bounds.right()
9239 && x_after_longest + element_bounds.width < viewport_bounds.right();
9240
9241 let mut origin = if can_position_to_the_right {
9242 point(
9243 x_after_longest,
9244 text_bounds.origin.y
9245 + Pixels::from(
9246 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9247 - scroll_pixel_position.y,
9248 ),
9249 )
9250 } else {
9251 let cursor_row = newest_selection_head.map(|head| head.row());
9252 let above_edit = edit_start
9253 .row()
9254 .0
9255 .checked_sub(line_count as u32)
9256 .map(DisplayRow);
9257 let below_edit = Some(edit_end.row() + 1);
9258 let above_cursor =
9259 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9260 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9261
9262 // Place the edit popover adjacent to the edit if there is a location
9263 // available that is onscreen and does not obscure the cursor. Otherwise,
9264 // place it adjacent to the cursor.
9265 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9266 .into_iter()
9267 .flatten()
9268 .find(|&start_row| {
9269 let end_row = start_row + line_count as u32;
9270 visible_row_range.contains(&start_row)
9271 && visible_row_range.contains(&end_row)
9272 && cursor_row
9273 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9274 })?;
9275
9276 content_origin
9277 + point(
9278 Pixels::from(-scroll_pixel_position.x),
9279 Pixels::from(
9280 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9281 ),
9282 )
9283 };
9284
9285 origin.x -= BORDER_WIDTH;
9286
9287 window.defer_draw(element, origin, 1);
9288
9289 // Do not return an element, since it will already be drawn due to defer_draw.
9290 None
9291 }
9292
9293 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9294 px(30.)
9295 }
9296
9297 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9298 if self.read_only(cx) {
9299 cx.theme().players().read_only()
9300 } else {
9301 self.style.as_ref().unwrap().local_player
9302 }
9303 }
9304
9305 fn render_edit_prediction_accept_keybind(
9306 &self,
9307 window: &mut Window,
9308 cx: &mut App,
9309 ) -> Option<AnyElement> {
9310 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9311 let accept_keystroke = accept_binding.keystroke()?;
9312
9313 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9314
9315 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9316 Color::Accent
9317 } else {
9318 Color::Muted
9319 };
9320
9321 h_flex()
9322 .px_0p5()
9323 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9324 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9325 .text_size(TextSize::XSmall.rems(cx))
9326 .child(h_flex().children(ui::render_modifiers(
9327 accept_keystroke.modifiers(),
9328 PlatformStyle::platform(),
9329 Some(modifiers_color),
9330 Some(IconSize::XSmall.rems().into()),
9331 true,
9332 )))
9333 .when(is_platform_style_mac, |parent| {
9334 parent.child(accept_keystroke.key().to_string())
9335 })
9336 .when(!is_platform_style_mac, |parent| {
9337 parent.child(
9338 Key::new(
9339 util::capitalize(accept_keystroke.key()),
9340 Some(Color::Default),
9341 )
9342 .size(Some(IconSize::XSmall.rems().into())),
9343 )
9344 })
9345 .into_any()
9346 .into()
9347 }
9348
9349 fn render_edit_prediction_line_popover(
9350 &self,
9351 label: impl Into<SharedString>,
9352 icon: Option<IconName>,
9353 window: &mut Window,
9354 cx: &mut App,
9355 ) -> Stateful<Div> {
9356 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9357
9358 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9359 let has_keybind = keybind.is_some();
9360
9361 h_flex()
9362 .id("ep-line-popover")
9363 .py_0p5()
9364 .pl_1()
9365 .pr(padding_right)
9366 .gap_1()
9367 .rounded_md()
9368 .border_1()
9369 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9370 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9371 .shadow_xs()
9372 .when(!has_keybind, |el| {
9373 let status_colors = cx.theme().status();
9374
9375 el.bg(status_colors.error_background)
9376 .border_color(status_colors.error.opacity(0.6))
9377 .pl_2()
9378 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9379 .cursor_default()
9380 .hoverable_tooltip(move |_window, cx| {
9381 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9382 })
9383 })
9384 .children(keybind)
9385 .child(
9386 Label::new(label)
9387 .size(LabelSize::Small)
9388 .when(!has_keybind, |el| {
9389 el.color(cx.theme().status().error.into()).strikethrough()
9390 }),
9391 )
9392 .when(!has_keybind, |el| {
9393 el.child(
9394 h_flex().ml_1().child(
9395 Icon::new(IconName::Info)
9396 .size(IconSize::Small)
9397 .color(cx.theme().status().error.into()),
9398 ),
9399 )
9400 })
9401 .when_some(icon, |element, icon| {
9402 element.child(
9403 div()
9404 .mt(px(1.5))
9405 .child(Icon::new(icon).size(IconSize::Small)),
9406 )
9407 })
9408 }
9409
9410 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9411 let accent_color = cx.theme().colors().text_accent;
9412 let editor_bg_color = cx.theme().colors().editor_background;
9413 editor_bg_color.blend(accent_color.opacity(0.1))
9414 }
9415
9416 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9417 let accent_color = cx.theme().colors().text_accent;
9418 let editor_bg_color = cx.theme().colors().editor_background;
9419 editor_bg_color.blend(accent_color.opacity(0.6))
9420 }
9421 fn get_prediction_provider_icon_name(
9422 provider: &Option<RegisteredEditPredictionProvider>,
9423 ) -> IconName {
9424 match provider {
9425 Some(provider) => match provider.provider.name() {
9426 "copilot" => IconName::Copilot,
9427 "supermaven" => IconName::Supermaven,
9428 _ => IconName::ZedPredict,
9429 },
9430 None => IconName::ZedPredict,
9431 }
9432 }
9433
9434 fn render_edit_prediction_cursor_popover(
9435 &self,
9436 min_width: Pixels,
9437 max_width: Pixels,
9438 cursor_point: Point,
9439 style: &EditorStyle,
9440 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9441 _window: &Window,
9442 cx: &mut Context<Editor>,
9443 ) -> Option<AnyElement> {
9444 let provider = self.edit_prediction_provider.as_ref()?;
9445 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9446
9447 let is_refreshing = provider.provider.is_refreshing(cx);
9448
9449 fn pending_completion_container(icon: IconName) -> Div {
9450 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9451 }
9452
9453 let completion = match &self.active_edit_prediction {
9454 Some(prediction) => {
9455 if !self.has_visible_completions_menu() {
9456 const RADIUS: Pixels = px(6.);
9457 const BORDER_WIDTH: Pixels = px(1.);
9458
9459 return Some(
9460 h_flex()
9461 .elevation_2(cx)
9462 .border(BORDER_WIDTH)
9463 .border_color(cx.theme().colors().border)
9464 .when(accept_keystroke.is_none(), |el| {
9465 el.border_color(cx.theme().status().error)
9466 })
9467 .rounded(RADIUS)
9468 .rounded_tl(px(0.))
9469 .overflow_hidden()
9470 .child(div().px_1p5().child(match &prediction.completion {
9471 EditPrediction::MoveWithin { target, snapshot } => {
9472 use text::ToPoint as _;
9473 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9474 {
9475 Icon::new(IconName::ZedPredictDown)
9476 } else {
9477 Icon::new(IconName::ZedPredictUp)
9478 }
9479 }
9480 EditPrediction::MoveOutside { .. } => {
9481 // TODO [zeta2] custom icon for external jump?
9482 Icon::new(provider_icon)
9483 }
9484 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9485 }))
9486 .child(
9487 h_flex()
9488 .gap_1()
9489 .py_1()
9490 .px_2()
9491 .rounded_r(RADIUS - BORDER_WIDTH)
9492 .border_l_1()
9493 .border_color(cx.theme().colors().border)
9494 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9495 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9496 el.child(
9497 Label::new("Hold")
9498 .size(LabelSize::Small)
9499 .when(accept_keystroke.is_none(), |el| {
9500 el.strikethrough()
9501 })
9502 .line_height_style(LineHeightStyle::UiLabel),
9503 )
9504 })
9505 .id("edit_prediction_cursor_popover_keybind")
9506 .when(accept_keystroke.is_none(), |el| {
9507 let status_colors = cx.theme().status();
9508
9509 el.bg(status_colors.error_background)
9510 .border_color(status_colors.error.opacity(0.6))
9511 .child(Icon::new(IconName::Info).color(Color::Error))
9512 .cursor_default()
9513 .hoverable_tooltip(move |_window, cx| {
9514 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9515 .into()
9516 })
9517 })
9518 .when_some(
9519 accept_keystroke.as_ref(),
9520 |el, accept_keystroke| {
9521 el.child(h_flex().children(ui::render_modifiers(
9522 accept_keystroke.modifiers(),
9523 PlatformStyle::platform(),
9524 Some(Color::Default),
9525 Some(IconSize::XSmall.rems().into()),
9526 false,
9527 )))
9528 },
9529 ),
9530 )
9531 .into_any(),
9532 );
9533 }
9534
9535 self.render_edit_prediction_cursor_popover_preview(
9536 prediction,
9537 cursor_point,
9538 style,
9539 cx,
9540 )?
9541 }
9542
9543 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9544 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9545 stale_completion,
9546 cursor_point,
9547 style,
9548 cx,
9549 )?,
9550
9551 None => pending_completion_container(provider_icon)
9552 .child(Label::new("...").size(LabelSize::Small)),
9553 },
9554
9555 None => pending_completion_container(provider_icon)
9556 .child(Label::new("...").size(LabelSize::Small)),
9557 };
9558
9559 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9560 completion
9561 .with_animation(
9562 "loading-completion",
9563 Animation::new(Duration::from_secs(2))
9564 .repeat()
9565 .with_easing(pulsating_between(0.4, 0.8)),
9566 |label, delta| label.opacity(delta),
9567 )
9568 .into_any_element()
9569 } else {
9570 completion.into_any_element()
9571 };
9572
9573 let has_completion = self.active_edit_prediction.is_some();
9574
9575 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9576 Some(
9577 h_flex()
9578 .min_w(min_width)
9579 .max_w(max_width)
9580 .flex_1()
9581 .elevation_2(cx)
9582 .border_color(cx.theme().colors().border)
9583 .child(
9584 div()
9585 .flex_1()
9586 .py_1()
9587 .px_2()
9588 .overflow_hidden()
9589 .child(completion),
9590 )
9591 .when_some(accept_keystroke, |el, accept_keystroke| {
9592 if !accept_keystroke.modifiers().modified() {
9593 return el;
9594 }
9595
9596 el.child(
9597 h_flex()
9598 .h_full()
9599 .border_l_1()
9600 .rounded_r_lg()
9601 .border_color(cx.theme().colors().border)
9602 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9603 .gap_1()
9604 .py_1()
9605 .px_2()
9606 .child(
9607 h_flex()
9608 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9609 .when(is_platform_style_mac, |parent| parent.gap_1())
9610 .child(h_flex().children(ui::render_modifiers(
9611 accept_keystroke.modifiers(),
9612 PlatformStyle::platform(),
9613 Some(if !has_completion {
9614 Color::Muted
9615 } else {
9616 Color::Default
9617 }),
9618 None,
9619 false,
9620 ))),
9621 )
9622 .child(Label::new("Preview").into_any_element())
9623 .opacity(if has_completion { 1.0 } else { 0.4 }),
9624 )
9625 })
9626 .into_any(),
9627 )
9628 }
9629
9630 fn render_edit_prediction_cursor_popover_preview(
9631 &self,
9632 completion: &EditPredictionState,
9633 cursor_point: Point,
9634 style: &EditorStyle,
9635 cx: &mut Context<Editor>,
9636 ) -> Option<Div> {
9637 use text::ToPoint as _;
9638
9639 fn render_relative_row_jump(
9640 prefix: impl Into<String>,
9641 current_row: u32,
9642 target_row: u32,
9643 ) -> Div {
9644 let (row_diff, arrow) = if target_row < current_row {
9645 (current_row - target_row, IconName::ArrowUp)
9646 } else {
9647 (target_row - current_row, IconName::ArrowDown)
9648 };
9649
9650 h_flex()
9651 .child(
9652 Label::new(format!("{}{}", prefix.into(), row_diff))
9653 .color(Color::Muted)
9654 .size(LabelSize::Small),
9655 )
9656 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9657 }
9658
9659 let supports_jump = self
9660 .edit_prediction_provider
9661 .as_ref()
9662 .map(|provider| provider.provider.supports_jump_to_edit())
9663 .unwrap_or(true);
9664
9665 match &completion.completion {
9666 EditPrediction::MoveWithin {
9667 target, snapshot, ..
9668 } => {
9669 if !supports_jump {
9670 return None;
9671 }
9672
9673 Some(
9674 h_flex()
9675 .px_2()
9676 .gap_2()
9677 .flex_1()
9678 .child(
9679 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9680 Icon::new(IconName::ZedPredictDown)
9681 } else {
9682 Icon::new(IconName::ZedPredictUp)
9683 },
9684 )
9685 .child(Label::new("Jump to Edit")),
9686 )
9687 }
9688 EditPrediction::MoveOutside { snapshot, .. } => {
9689 let file_name = snapshot
9690 .file()
9691 .map(|file| file.file_name(cx))
9692 .unwrap_or("untitled");
9693 Some(
9694 h_flex()
9695 .px_2()
9696 .gap_2()
9697 .flex_1()
9698 .child(Icon::new(IconName::ZedPredict))
9699 .child(Label::new(format!("Jump to {file_name}"))),
9700 )
9701 }
9702 EditPrediction::Edit {
9703 edits,
9704 edit_preview,
9705 snapshot,
9706 display_mode: _,
9707 } => {
9708 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9709
9710 let (highlighted_edits, has_more_lines) =
9711 if let Some(edit_preview) = edit_preview.as_ref() {
9712 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9713 .first_line_preview()
9714 } else {
9715 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9716 };
9717
9718 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9719 .with_default_highlights(&style.text, highlighted_edits.highlights);
9720
9721 let preview = h_flex()
9722 .gap_1()
9723 .min_w_16()
9724 .child(styled_text)
9725 .when(has_more_lines, |parent| parent.child("…"));
9726
9727 let left = if supports_jump && first_edit_row != cursor_point.row {
9728 render_relative_row_jump("", cursor_point.row, first_edit_row)
9729 .into_any_element()
9730 } else {
9731 let icon_name =
9732 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9733 Icon::new(icon_name).into_any_element()
9734 };
9735
9736 Some(
9737 h_flex()
9738 .h_full()
9739 .flex_1()
9740 .gap_2()
9741 .pr_1()
9742 .overflow_x_hidden()
9743 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9744 .child(left)
9745 .child(preview),
9746 )
9747 }
9748 }
9749 }
9750
9751 pub fn render_context_menu(
9752 &self,
9753 style: &EditorStyle,
9754 max_height_in_lines: u32,
9755 window: &mut Window,
9756 cx: &mut Context<Editor>,
9757 ) -> Option<AnyElement> {
9758 let menu = self.context_menu.borrow();
9759 let menu = menu.as_ref()?;
9760 if !menu.visible() {
9761 return None;
9762 };
9763 Some(menu.render(style, max_height_in_lines, window, cx))
9764 }
9765
9766 fn render_context_menu_aside(
9767 &mut self,
9768 max_size: Size<Pixels>,
9769 window: &mut Window,
9770 cx: &mut Context<Editor>,
9771 ) -> Option<AnyElement> {
9772 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9773 if menu.visible() {
9774 menu.render_aside(max_size, window, cx)
9775 } else {
9776 None
9777 }
9778 })
9779 }
9780
9781 fn hide_context_menu(
9782 &mut self,
9783 window: &mut Window,
9784 cx: &mut Context<Self>,
9785 ) -> Option<CodeContextMenu> {
9786 cx.notify();
9787 self.completion_tasks.clear();
9788 let context_menu = self.context_menu.borrow_mut().take();
9789 self.stale_edit_prediction_in_menu.take();
9790 self.update_visible_edit_prediction(window, cx);
9791 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9792 && let Some(completion_provider) = &self.completion_provider
9793 {
9794 completion_provider.selection_changed(None, window, cx);
9795 }
9796 context_menu
9797 }
9798
9799 fn show_snippet_choices(
9800 &mut self,
9801 choices: &Vec<String>,
9802 selection: Range<Anchor>,
9803 cx: &mut Context<Self>,
9804 ) {
9805 let Some((_, buffer, _)) = self
9806 .buffer()
9807 .read(cx)
9808 .excerpt_containing(selection.start, cx)
9809 else {
9810 return;
9811 };
9812 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9813 else {
9814 return;
9815 };
9816 if buffer != end_buffer {
9817 log::error!("expected anchor range to have matching buffer IDs");
9818 return;
9819 }
9820
9821 let id = post_inc(&mut self.next_completion_id);
9822 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9823 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9824 CompletionsMenu::new_snippet_choices(
9825 id,
9826 true,
9827 choices,
9828 selection,
9829 buffer,
9830 snippet_sort_order,
9831 ),
9832 ));
9833 }
9834
9835 pub fn insert_snippet(
9836 &mut self,
9837 insertion_ranges: &[Range<usize>],
9838 snippet: Snippet,
9839 window: &mut Window,
9840 cx: &mut Context<Self>,
9841 ) -> Result<()> {
9842 struct Tabstop<T> {
9843 is_end_tabstop: bool,
9844 ranges: Vec<Range<T>>,
9845 choices: Option<Vec<String>>,
9846 }
9847
9848 let tabstops = self.buffer.update(cx, |buffer, cx| {
9849 let snippet_text: Arc<str> = snippet.text.clone().into();
9850 let edits = insertion_ranges
9851 .iter()
9852 .cloned()
9853 .map(|range| (range, snippet_text.clone()));
9854 let autoindent_mode = AutoindentMode::Block {
9855 original_indent_columns: Vec::new(),
9856 };
9857 buffer.edit(edits, Some(autoindent_mode), cx);
9858
9859 let snapshot = &*buffer.read(cx);
9860 let snippet = &snippet;
9861 snippet
9862 .tabstops
9863 .iter()
9864 .map(|tabstop| {
9865 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9866 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9867 });
9868 let mut tabstop_ranges = tabstop
9869 .ranges
9870 .iter()
9871 .flat_map(|tabstop_range| {
9872 let mut delta = 0_isize;
9873 insertion_ranges.iter().map(move |insertion_range| {
9874 let insertion_start = insertion_range.start as isize + delta;
9875 delta +=
9876 snippet.text.len() as isize - insertion_range.len() as isize;
9877
9878 let start = ((insertion_start + tabstop_range.start) as usize)
9879 .min(snapshot.len());
9880 let end = ((insertion_start + tabstop_range.end) as usize)
9881 .min(snapshot.len());
9882 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9883 })
9884 })
9885 .collect::<Vec<_>>();
9886 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9887
9888 Tabstop {
9889 is_end_tabstop,
9890 ranges: tabstop_ranges,
9891 choices: tabstop.choices.clone(),
9892 }
9893 })
9894 .collect::<Vec<_>>()
9895 });
9896 if let Some(tabstop) = tabstops.first() {
9897 self.change_selections(Default::default(), window, cx, |s| {
9898 // Reverse order so that the first range is the newest created selection.
9899 // Completions will use it and autoscroll will prioritize it.
9900 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9901 });
9902
9903 if let Some(choices) = &tabstop.choices
9904 && let Some(selection) = tabstop.ranges.first()
9905 {
9906 self.show_snippet_choices(choices, selection.clone(), cx)
9907 }
9908
9909 // If we're already at the last tabstop and it's at the end of the snippet,
9910 // we're done, we don't need to keep the state around.
9911 if !tabstop.is_end_tabstop {
9912 let choices = tabstops
9913 .iter()
9914 .map(|tabstop| tabstop.choices.clone())
9915 .collect();
9916
9917 let ranges = tabstops
9918 .into_iter()
9919 .map(|tabstop| tabstop.ranges)
9920 .collect::<Vec<_>>();
9921
9922 self.snippet_stack.push(SnippetState {
9923 active_index: 0,
9924 ranges,
9925 choices,
9926 });
9927 }
9928
9929 // Check whether the just-entered snippet ends with an auto-closable bracket.
9930 if self.autoclose_regions.is_empty() {
9931 let snapshot = self.buffer.read(cx).snapshot(cx);
9932 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
9933 let selection_head = selection.head();
9934 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9935 continue;
9936 };
9937
9938 let mut bracket_pair = None;
9939 let max_lookup_length = scope
9940 .brackets()
9941 .map(|(pair, _)| {
9942 pair.start
9943 .as_str()
9944 .chars()
9945 .count()
9946 .max(pair.end.as_str().chars().count())
9947 })
9948 .max();
9949 if let Some(max_lookup_length) = max_lookup_length {
9950 let next_text = snapshot
9951 .chars_at(selection_head)
9952 .take(max_lookup_length)
9953 .collect::<String>();
9954 let prev_text = snapshot
9955 .reversed_chars_at(selection_head)
9956 .take(max_lookup_length)
9957 .collect::<String>();
9958
9959 for (pair, enabled) in scope.brackets() {
9960 if enabled
9961 && pair.close
9962 && prev_text.starts_with(pair.start.as_str())
9963 && next_text.starts_with(pair.end.as_str())
9964 {
9965 bracket_pair = Some(pair.clone());
9966 break;
9967 }
9968 }
9969 }
9970
9971 if let Some(pair) = bracket_pair {
9972 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9973 let autoclose_enabled =
9974 self.use_autoclose && snapshot_settings.use_autoclose;
9975 if autoclose_enabled {
9976 let start = snapshot.anchor_after(selection_head);
9977 let end = snapshot.anchor_after(selection_head);
9978 self.autoclose_regions.push(AutocloseRegion {
9979 selection_id: selection.id,
9980 range: start..end,
9981 pair,
9982 });
9983 }
9984 }
9985 }
9986 }
9987 }
9988 Ok(())
9989 }
9990
9991 pub fn move_to_next_snippet_tabstop(
9992 &mut self,
9993 window: &mut Window,
9994 cx: &mut Context<Self>,
9995 ) -> bool {
9996 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9997 }
9998
9999 pub fn move_to_prev_snippet_tabstop(
10000 &mut self,
10001 window: &mut Window,
10002 cx: &mut Context<Self>,
10003 ) -> bool {
10004 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10005 }
10006
10007 pub fn move_to_snippet_tabstop(
10008 &mut self,
10009 bias: Bias,
10010 window: &mut Window,
10011 cx: &mut Context<Self>,
10012 ) -> bool {
10013 if let Some(mut snippet) = self.snippet_stack.pop() {
10014 match bias {
10015 Bias::Left => {
10016 if snippet.active_index > 0 {
10017 snippet.active_index -= 1;
10018 } else {
10019 self.snippet_stack.push(snippet);
10020 return false;
10021 }
10022 }
10023 Bias::Right => {
10024 if snippet.active_index + 1 < snippet.ranges.len() {
10025 snippet.active_index += 1;
10026 } else {
10027 self.snippet_stack.push(snippet);
10028 return false;
10029 }
10030 }
10031 }
10032 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10033 self.change_selections(Default::default(), window, cx, |s| {
10034 // Reverse order so that the first range is the newest created selection.
10035 // Completions will use it and autoscroll will prioritize it.
10036 s.select_ranges(current_ranges.iter().rev().cloned())
10037 });
10038
10039 if let Some(choices) = &snippet.choices[snippet.active_index]
10040 && let Some(selection) = current_ranges.first()
10041 {
10042 self.show_snippet_choices(choices, selection.clone(), cx);
10043 }
10044
10045 // If snippet state is not at the last tabstop, push it back on the stack
10046 if snippet.active_index + 1 < snippet.ranges.len() {
10047 self.snippet_stack.push(snippet);
10048 }
10049 return true;
10050 }
10051 }
10052
10053 false
10054 }
10055
10056 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10057 self.transact(window, cx, |this, window, cx| {
10058 this.select_all(&SelectAll, window, cx);
10059 this.insert("", window, cx);
10060 });
10061 }
10062
10063 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10064 if self.read_only(cx) {
10065 return;
10066 }
10067 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10068 self.transact(window, cx, |this, window, cx| {
10069 this.select_autoclose_pair(window, cx);
10070
10071 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10072
10073 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10074 if !this.linked_edit_ranges.is_empty() {
10075 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10076 let snapshot = this.buffer.read(cx).snapshot(cx);
10077
10078 for selection in selections.iter() {
10079 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10080 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10081 if selection_start.buffer_id != selection_end.buffer_id {
10082 continue;
10083 }
10084 if let Some(ranges) =
10085 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10086 {
10087 for (buffer, entries) in ranges {
10088 linked_ranges.entry(buffer).or_default().extend(entries);
10089 }
10090 }
10091 }
10092 }
10093
10094 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10095 for selection in &mut selections {
10096 if selection.is_empty() {
10097 let old_head = selection.head();
10098 let mut new_head =
10099 movement::left(&display_map, old_head.to_display_point(&display_map))
10100 .to_point(&display_map);
10101 if let Some((buffer, line_buffer_range)) = display_map
10102 .buffer_snapshot()
10103 .buffer_line_for_row(MultiBufferRow(old_head.row))
10104 {
10105 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10106 let indent_len = match indent_size.kind {
10107 IndentKind::Space => {
10108 buffer.settings_at(line_buffer_range.start, cx).tab_size
10109 }
10110 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10111 };
10112 if old_head.column <= indent_size.len && old_head.column > 0 {
10113 let indent_len = indent_len.get();
10114 new_head = cmp::min(
10115 new_head,
10116 MultiBufferPoint::new(
10117 old_head.row,
10118 ((old_head.column - 1) / indent_len) * indent_len,
10119 ),
10120 );
10121 }
10122 }
10123
10124 selection.set_head(new_head, SelectionGoal::None);
10125 }
10126 }
10127
10128 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10129 this.insert("", window, cx);
10130 let empty_str: Arc<str> = Arc::from("");
10131 for (buffer, edits) in linked_ranges {
10132 let snapshot = buffer.read(cx).snapshot();
10133 use text::ToPoint as TP;
10134
10135 let edits = edits
10136 .into_iter()
10137 .map(|range| {
10138 let end_point = TP::to_point(&range.end, &snapshot);
10139 let mut start_point = TP::to_point(&range.start, &snapshot);
10140
10141 if end_point == start_point {
10142 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10143 .saturating_sub(1);
10144 start_point =
10145 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10146 };
10147
10148 (start_point..end_point, empty_str.clone())
10149 })
10150 .sorted_by_key(|(range, _)| range.start)
10151 .collect::<Vec<_>>();
10152 buffer.update(cx, |this, cx| {
10153 this.edit(edits, None, cx);
10154 })
10155 }
10156 this.refresh_edit_prediction(true, false, window, cx);
10157 refresh_linked_ranges(this, window, cx);
10158 });
10159 }
10160
10161 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10162 if self.read_only(cx) {
10163 return;
10164 }
10165 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10166 self.transact(window, cx, |this, window, cx| {
10167 this.change_selections(Default::default(), window, cx, |s| {
10168 s.move_with(|map, selection| {
10169 if selection.is_empty() {
10170 let cursor = movement::right(map, selection.head());
10171 selection.end = cursor;
10172 selection.reversed = true;
10173 selection.goal = SelectionGoal::None;
10174 }
10175 })
10176 });
10177 this.insert("", window, cx);
10178 this.refresh_edit_prediction(true, false, window, cx);
10179 });
10180 }
10181
10182 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10183 if self.mode.is_single_line() {
10184 cx.propagate();
10185 return;
10186 }
10187
10188 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10189 if self.move_to_prev_snippet_tabstop(window, cx) {
10190 return;
10191 }
10192 self.outdent(&Outdent, window, cx);
10193 }
10194
10195 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10196 if self.mode.is_single_line() {
10197 cx.propagate();
10198 return;
10199 }
10200
10201 if self.move_to_next_snippet_tabstop(window, cx) {
10202 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10203 return;
10204 }
10205 if self.read_only(cx) {
10206 return;
10207 }
10208 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10209 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10210 let buffer = self.buffer.read(cx);
10211 let snapshot = buffer.snapshot(cx);
10212 let rows_iter = selections.iter().map(|s| s.head().row);
10213 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10214
10215 let has_some_cursor_in_whitespace = selections
10216 .iter()
10217 .filter(|selection| selection.is_empty())
10218 .any(|selection| {
10219 let cursor = selection.head();
10220 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10221 cursor.column < current_indent.len
10222 });
10223
10224 let mut edits = Vec::new();
10225 let mut prev_edited_row = 0;
10226 let mut row_delta = 0;
10227 for selection in &mut selections {
10228 if selection.start.row != prev_edited_row {
10229 row_delta = 0;
10230 }
10231 prev_edited_row = selection.end.row;
10232
10233 // If the selection is non-empty, then increase the indentation of the selected lines.
10234 if !selection.is_empty() {
10235 row_delta =
10236 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10237 continue;
10238 }
10239
10240 let cursor = selection.head();
10241 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10242 if let Some(suggested_indent) =
10243 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10244 {
10245 // Don't do anything if already at suggested indent
10246 // and there is any other cursor which is not
10247 if has_some_cursor_in_whitespace
10248 && cursor.column == current_indent.len
10249 && current_indent.len == suggested_indent.len
10250 {
10251 continue;
10252 }
10253
10254 // Adjust line and move cursor to suggested indent
10255 // if cursor is not at suggested indent
10256 if cursor.column < suggested_indent.len
10257 && cursor.column <= current_indent.len
10258 && current_indent.len <= suggested_indent.len
10259 {
10260 selection.start = Point::new(cursor.row, suggested_indent.len);
10261 selection.end = selection.start;
10262 if row_delta == 0 {
10263 edits.extend(Buffer::edit_for_indent_size_adjustment(
10264 cursor.row,
10265 current_indent,
10266 suggested_indent,
10267 ));
10268 row_delta = suggested_indent.len - current_indent.len;
10269 }
10270 continue;
10271 }
10272
10273 // If current indent is more than suggested indent
10274 // only move cursor to current indent and skip indent
10275 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10276 selection.start = Point::new(cursor.row, current_indent.len);
10277 selection.end = selection.start;
10278 continue;
10279 }
10280 }
10281
10282 // Otherwise, insert a hard or soft tab.
10283 let settings = buffer.language_settings_at(cursor, cx);
10284 let tab_size = if settings.hard_tabs {
10285 IndentSize::tab()
10286 } else {
10287 let tab_size = settings.tab_size.get();
10288 let indent_remainder = snapshot
10289 .text_for_range(Point::new(cursor.row, 0)..cursor)
10290 .flat_map(str::chars)
10291 .fold(row_delta % tab_size, |counter: u32, c| {
10292 if c == '\t' {
10293 0
10294 } else {
10295 (counter + 1) % tab_size
10296 }
10297 });
10298
10299 let chars_to_next_tab_stop = tab_size - indent_remainder;
10300 IndentSize::spaces(chars_to_next_tab_stop)
10301 };
10302 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10303 selection.end = selection.start;
10304 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10305 row_delta += tab_size.len;
10306 }
10307
10308 self.transact(window, cx, |this, window, cx| {
10309 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10310 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10311 this.refresh_edit_prediction(true, false, window, cx);
10312 });
10313 }
10314
10315 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10316 if self.read_only(cx) {
10317 return;
10318 }
10319 if self.mode.is_single_line() {
10320 cx.propagate();
10321 return;
10322 }
10323
10324 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10325 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10326 let mut prev_edited_row = 0;
10327 let mut row_delta = 0;
10328 let mut edits = Vec::new();
10329 let buffer = self.buffer.read(cx);
10330 let snapshot = buffer.snapshot(cx);
10331 for selection in &mut selections {
10332 if selection.start.row != prev_edited_row {
10333 row_delta = 0;
10334 }
10335 prev_edited_row = selection.end.row;
10336
10337 row_delta =
10338 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10339 }
10340
10341 self.transact(window, cx, |this, window, cx| {
10342 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10343 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10344 });
10345 }
10346
10347 fn indent_selection(
10348 buffer: &MultiBuffer,
10349 snapshot: &MultiBufferSnapshot,
10350 selection: &mut Selection<Point>,
10351 edits: &mut Vec<(Range<Point>, String)>,
10352 delta_for_start_row: u32,
10353 cx: &App,
10354 ) -> u32 {
10355 let settings = buffer.language_settings_at(selection.start, cx);
10356 let tab_size = settings.tab_size.get();
10357 let indent_kind = if settings.hard_tabs {
10358 IndentKind::Tab
10359 } else {
10360 IndentKind::Space
10361 };
10362 let mut start_row = selection.start.row;
10363 let mut end_row = selection.end.row + 1;
10364
10365 // If a selection ends at the beginning of a line, don't indent
10366 // that last line.
10367 if selection.end.column == 0 && selection.end.row > selection.start.row {
10368 end_row -= 1;
10369 }
10370
10371 // Avoid re-indenting a row that has already been indented by a
10372 // previous selection, but still update this selection's column
10373 // to reflect that indentation.
10374 if delta_for_start_row > 0 {
10375 start_row += 1;
10376 selection.start.column += delta_for_start_row;
10377 if selection.end.row == selection.start.row {
10378 selection.end.column += delta_for_start_row;
10379 }
10380 }
10381
10382 let mut delta_for_end_row = 0;
10383 let has_multiple_rows = start_row + 1 != end_row;
10384 for row in start_row..end_row {
10385 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10386 let indent_delta = match (current_indent.kind, indent_kind) {
10387 (IndentKind::Space, IndentKind::Space) => {
10388 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10389 IndentSize::spaces(columns_to_next_tab_stop)
10390 }
10391 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10392 (_, IndentKind::Tab) => IndentSize::tab(),
10393 };
10394
10395 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10396 0
10397 } else {
10398 selection.start.column
10399 };
10400 let row_start = Point::new(row, start);
10401 edits.push((
10402 row_start..row_start,
10403 indent_delta.chars().collect::<String>(),
10404 ));
10405
10406 // Update this selection's endpoints to reflect the indentation.
10407 if row == selection.start.row {
10408 selection.start.column += indent_delta.len;
10409 }
10410 if row == selection.end.row {
10411 selection.end.column += indent_delta.len;
10412 delta_for_end_row = indent_delta.len;
10413 }
10414 }
10415
10416 if selection.start.row == selection.end.row {
10417 delta_for_start_row + delta_for_end_row
10418 } else {
10419 delta_for_end_row
10420 }
10421 }
10422
10423 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10424 if self.read_only(cx) {
10425 return;
10426 }
10427 if self.mode.is_single_line() {
10428 cx.propagate();
10429 return;
10430 }
10431
10432 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10433 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10434 let selections = self.selections.all::<Point>(&display_map);
10435 let mut deletion_ranges = Vec::new();
10436 let mut last_outdent = None;
10437 {
10438 let buffer = self.buffer.read(cx);
10439 let snapshot = buffer.snapshot(cx);
10440 for selection in &selections {
10441 let settings = buffer.language_settings_at(selection.start, cx);
10442 let tab_size = settings.tab_size.get();
10443 let mut rows = selection.spanned_rows(false, &display_map);
10444
10445 // Avoid re-outdenting a row that has already been outdented by a
10446 // previous selection.
10447 if let Some(last_row) = last_outdent
10448 && last_row == rows.start
10449 {
10450 rows.start = rows.start.next_row();
10451 }
10452 let has_multiple_rows = rows.len() > 1;
10453 for row in rows.iter_rows() {
10454 let indent_size = snapshot.indent_size_for_line(row);
10455 if indent_size.len > 0 {
10456 let deletion_len = match indent_size.kind {
10457 IndentKind::Space => {
10458 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10459 if columns_to_prev_tab_stop == 0 {
10460 tab_size
10461 } else {
10462 columns_to_prev_tab_stop
10463 }
10464 }
10465 IndentKind::Tab => 1,
10466 };
10467 let start = if has_multiple_rows
10468 || deletion_len > selection.start.column
10469 || indent_size.len < selection.start.column
10470 {
10471 0
10472 } else {
10473 selection.start.column - deletion_len
10474 };
10475 deletion_ranges.push(
10476 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10477 );
10478 last_outdent = Some(row);
10479 }
10480 }
10481 }
10482 }
10483
10484 self.transact(window, cx, |this, window, cx| {
10485 this.buffer.update(cx, |buffer, cx| {
10486 let empty_str: Arc<str> = Arc::default();
10487 buffer.edit(
10488 deletion_ranges
10489 .into_iter()
10490 .map(|range| (range, empty_str.clone())),
10491 None,
10492 cx,
10493 );
10494 });
10495 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10496 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10497 });
10498 }
10499
10500 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10501 if self.read_only(cx) {
10502 return;
10503 }
10504 if self.mode.is_single_line() {
10505 cx.propagate();
10506 return;
10507 }
10508
10509 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10510 let selections = self
10511 .selections
10512 .all::<usize>(&self.display_snapshot(cx))
10513 .into_iter()
10514 .map(|s| s.range());
10515
10516 self.transact(window, cx, |this, window, cx| {
10517 this.buffer.update(cx, |buffer, cx| {
10518 buffer.autoindent_ranges(selections, cx);
10519 });
10520 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10521 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10522 });
10523 }
10524
10525 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10526 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10527 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10528 let selections = self.selections.all::<Point>(&display_map);
10529
10530 let mut new_cursors = Vec::new();
10531 let mut edit_ranges = Vec::new();
10532 let mut selections = selections.iter().peekable();
10533 while let Some(selection) = selections.next() {
10534 let mut rows = selection.spanned_rows(false, &display_map);
10535
10536 // Accumulate contiguous regions of rows that we want to delete.
10537 while let Some(next_selection) = selections.peek() {
10538 let next_rows = next_selection.spanned_rows(false, &display_map);
10539 if next_rows.start <= rows.end {
10540 rows.end = next_rows.end;
10541 selections.next().unwrap();
10542 } else {
10543 break;
10544 }
10545 }
10546
10547 let buffer = display_map.buffer_snapshot();
10548 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10549 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10550 // If there's a line after the range, delete the \n from the end of the row range
10551 (
10552 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10553 rows.end,
10554 )
10555 } else {
10556 // If there isn't a line after the range, delete the \n from the line before the
10557 // start of the row range
10558 edit_start = edit_start.saturating_sub(1);
10559 (buffer.len(), rows.start.previous_row())
10560 };
10561
10562 let text_layout_details = self.text_layout_details(window);
10563 let x = display_map.x_for_display_point(
10564 selection.head().to_display_point(&display_map),
10565 &text_layout_details,
10566 );
10567 let row = Point::new(target_row.0, 0)
10568 .to_display_point(&display_map)
10569 .row();
10570 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10571
10572 new_cursors.push((
10573 selection.id,
10574 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10575 SelectionGoal::None,
10576 ));
10577 edit_ranges.push(edit_start..edit_end);
10578 }
10579
10580 self.transact(window, cx, |this, window, cx| {
10581 let buffer = this.buffer.update(cx, |buffer, cx| {
10582 let empty_str: Arc<str> = Arc::default();
10583 buffer.edit(
10584 edit_ranges
10585 .into_iter()
10586 .map(|range| (range, empty_str.clone())),
10587 None,
10588 cx,
10589 );
10590 buffer.snapshot(cx)
10591 });
10592 let new_selections = new_cursors
10593 .into_iter()
10594 .map(|(id, cursor, goal)| {
10595 let cursor = cursor.to_point(&buffer);
10596 Selection {
10597 id,
10598 start: cursor,
10599 end: cursor,
10600 reversed: false,
10601 goal,
10602 }
10603 })
10604 .collect();
10605
10606 this.change_selections(Default::default(), window, cx, |s| {
10607 s.select(new_selections);
10608 });
10609 });
10610 }
10611
10612 pub fn join_lines_impl(
10613 &mut self,
10614 insert_whitespace: bool,
10615 window: &mut Window,
10616 cx: &mut Context<Self>,
10617 ) {
10618 if self.read_only(cx) {
10619 return;
10620 }
10621 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10622 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10623 let start = MultiBufferRow(selection.start.row);
10624 // Treat single line selections as if they include the next line. Otherwise this action
10625 // would do nothing for single line selections individual cursors.
10626 let end = if selection.start.row == selection.end.row {
10627 MultiBufferRow(selection.start.row + 1)
10628 } else {
10629 MultiBufferRow(selection.end.row)
10630 };
10631
10632 if let Some(last_row_range) = row_ranges.last_mut()
10633 && start <= last_row_range.end
10634 {
10635 last_row_range.end = end;
10636 continue;
10637 }
10638 row_ranges.push(start..end);
10639 }
10640
10641 let snapshot = self.buffer.read(cx).snapshot(cx);
10642 let mut cursor_positions = Vec::new();
10643 for row_range in &row_ranges {
10644 let anchor = snapshot.anchor_before(Point::new(
10645 row_range.end.previous_row().0,
10646 snapshot.line_len(row_range.end.previous_row()),
10647 ));
10648 cursor_positions.push(anchor..anchor);
10649 }
10650
10651 self.transact(window, cx, |this, window, cx| {
10652 for row_range in row_ranges.into_iter().rev() {
10653 for row in row_range.iter_rows().rev() {
10654 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10655 let next_line_row = row.next_row();
10656 let indent = snapshot.indent_size_for_line(next_line_row);
10657 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10658
10659 let replace =
10660 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10661 " "
10662 } else {
10663 ""
10664 };
10665
10666 this.buffer.update(cx, |buffer, cx| {
10667 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10668 });
10669 }
10670 }
10671
10672 this.change_selections(Default::default(), window, cx, |s| {
10673 s.select_anchor_ranges(cursor_positions)
10674 });
10675 });
10676 }
10677
10678 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10679 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10680 self.join_lines_impl(true, window, cx);
10681 }
10682
10683 pub fn sort_lines_case_sensitive(
10684 &mut self,
10685 _: &SortLinesCaseSensitive,
10686 window: &mut Window,
10687 cx: &mut Context<Self>,
10688 ) {
10689 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10690 }
10691
10692 pub fn sort_lines_by_length(
10693 &mut self,
10694 _: &SortLinesByLength,
10695 window: &mut Window,
10696 cx: &mut Context<Self>,
10697 ) {
10698 self.manipulate_immutable_lines(window, cx, |lines| {
10699 lines.sort_by_key(|&line| line.chars().count())
10700 })
10701 }
10702
10703 pub fn sort_lines_case_insensitive(
10704 &mut self,
10705 _: &SortLinesCaseInsensitive,
10706 window: &mut Window,
10707 cx: &mut Context<Self>,
10708 ) {
10709 self.manipulate_immutable_lines(window, cx, |lines| {
10710 lines.sort_by_key(|line| line.to_lowercase())
10711 })
10712 }
10713
10714 pub fn unique_lines_case_insensitive(
10715 &mut self,
10716 _: &UniqueLinesCaseInsensitive,
10717 window: &mut Window,
10718 cx: &mut Context<Self>,
10719 ) {
10720 self.manipulate_immutable_lines(window, cx, |lines| {
10721 let mut seen = HashSet::default();
10722 lines.retain(|line| seen.insert(line.to_lowercase()));
10723 })
10724 }
10725
10726 pub fn unique_lines_case_sensitive(
10727 &mut self,
10728 _: &UniqueLinesCaseSensitive,
10729 window: &mut Window,
10730 cx: &mut Context<Self>,
10731 ) {
10732 self.manipulate_immutable_lines(window, cx, |lines| {
10733 let mut seen = HashSet::default();
10734 lines.retain(|line| seen.insert(*line));
10735 })
10736 }
10737
10738 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10739 let snapshot = self.buffer.read(cx).snapshot(cx);
10740 for selection in self.selections.disjoint_anchors_arc().iter() {
10741 if snapshot
10742 .language_at(selection.start)
10743 .and_then(|lang| lang.config().wrap_characters.as_ref())
10744 .is_some()
10745 {
10746 return true;
10747 }
10748 }
10749 false
10750 }
10751
10752 fn wrap_selections_in_tag(
10753 &mut self,
10754 _: &WrapSelectionsInTag,
10755 window: &mut Window,
10756 cx: &mut Context<Self>,
10757 ) {
10758 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10759
10760 let snapshot = self.buffer.read(cx).snapshot(cx);
10761
10762 let mut edits = Vec::new();
10763 let mut boundaries = Vec::new();
10764
10765 for selection in self
10766 .selections
10767 .all::<Point>(&self.display_snapshot(cx))
10768 .iter()
10769 {
10770 let Some(wrap_config) = snapshot
10771 .language_at(selection.start)
10772 .and_then(|lang| lang.config().wrap_characters.clone())
10773 else {
10774 continue;
10775 };
10776
10777 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10778 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10779
10780 let start_before = snapshot.anchor_before(selection.start);
10781 let end_after = snapshot.anchor_after(selection.end);
10782
10783 edits.push((start_before..start_before, open_tag));
10784 edits.push((end_after..end_after, close_tag));
10785
10786 boundaries.push((
10787 start_before,
10788 end_after,
10789 wrap_config.start_prefix.len(),
10790 wrap_config.end_suffix.len(),
10791 ));
10792 }
10793
10794 if edits.is_empty() {
10795 return;
10796 }
10797
10798 self.transact(window, cx, |this, window, cx| {
10799 let buffer = this.buffer.update(cx, |buffer, cx| {
10800 buffer.edit(edits, None, cx);
10801 buffer.snapshot(cx)
10802 });
10803
10804 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10805 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10806 boundaries.into_iter()
10807 {
10808 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10809 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10810 new_selections.push(open_offset..open_offset);
10811 new_selections.push(close_offset..close_offset);
10812 }
10813
10814 this.change_selections(Default::default(), window, cx, |s| {
10815 s.select_ranges(new_selections);
10816 });
10817
10818 this.request_autoscroll(Autoscroll::fit(), cx);
10819 });
10820 }
10821
10822 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10823 let Some(project) = self.project.clone() else {
10824 return;
10825 };
10826 self.reload(project, window, cx)
10827 .detach_and_notify_err(window, cx);
10828 }
10829
10830 pub fn restore_file(
10831 &mut self,
10832 _: &::git::RestoreFile,
10833 window: &mut Window,
10834 cx: &mut Context<Self>,
10835 ) {
10836 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10837 let mut buffer_ids = HashSet::default();
10838 let snapshot = self.buffer().read(cx).snapshot(cx);
10839 for selection in self.selections.all::<usize>(&self.display_snapshot(cx)) {
10840 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10841 }
10842
10843 let buffer = self.buffer().read(cx);
10844 let ranges = buffer_ids
10845 .into_iter()
10846 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10847 .collect::<Vec<_>>();
10848
10849 self.restore_hunks_in_ranges(ranges, window, cx);
10850 }
10851
10852 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10853 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10854 let selections = self
10855 .selections
10856 .all(&self.display_snapshot(cx))
10857 .into_iter()
10858 .map(|s| s.range())
10859 .collect();
10860 self.restore_hunks_in_ranges(selections, window, cx);
10861 }
10862
10863 pub fn restore_hunks_in_ranges(
10864 &mut self,
10865 ranges: Vec<Range<Point>>,
10866 window: &mut Window,
10867 cx: &mut Context<Editor>,
10868 ) {
10869 let mut revert_changes = HashMap::default();
10870 let chunk_by = self
10871 .snapshot(window, cx)
10872 .hunks_for_ranges(ranges)
10873 .into_iter()
10874 .chunk_by(|hunk| hunk.buffer_id);
10875 for (buffer_id, hunks) in &chunk_by {
10876 let hunks = hunks.collect::<Vec<_>>();
10877 for hunk in &hunks {
10878 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10879 }
10880 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10881 }
10882 drop(chunk_by);
10883 if !revert_changes.is_empty() {
10884 self.transact(window, cx, |editor, window, cx| {
10885 editor.restore(revert_changes, window, cx);
10886 });
10887 }
10888 }
10889
10890 pub fn open_active_item_in_terminal(
10891 &mut self,
10892 _: &OpenInTerminal,
10893 window: &mut Window,
10894 cx: &mut Context<Self>,
10895 ) {
10896 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10897 let project_path = buffer.read(cx).project_path(cx)?;
10898 let project = self.project()?.read(cx);
10899 let entry = project.entry_for_path(&project_path, cx)?;
10900 let parent = match &entry.canonical_path {
10901 Some(canonical_path) => canonical_path.to_path_buf(),
10902 None => project.absolute_path(&project_path, cx)?,
10903 }
10904 .parent()?
10905 .to_path_buf();
10906 Some(parent)
10907 }) {
10908 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10909 }
10910 }
10911
10912 fn set_breakpoint_context_menu(
10913 &mut self,
10914 display_row: DisplayRow,
10915 position: Option<Anchor>,
10916 clicked_point: gpui::Point<Pixels>,
10917 window: &mut Window,
10918 cx: &mut Context<Self>,
10919 ) {
10920 let source = self
10921 .buffer
10922 .read(cx)
10923 .snapshot(cx)
10924 .anchor_before(Point::new(display_row.0, 0u32));
10925
10926 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10927
10928 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10929 self,
10930 source,
10931 clicked_point,
10932 context_menu,
10933 window,
10934 cx,
10935 );
10936 }
10937
10938 fn add_edit_breakpoint_block(
10939 &mut self,
10940 anchor: Anchor,
10941 breakpoint: &Breakpoint,
10942 edit_action: BreakpointPromptEditAction,
10943 window: &mut Window,
10944 cx: &mut Context<Self>,
10945 ) {
10946 let weak_editor = cx.weak_entity();
10947 let bp_prompt = cx.new(|cx| {
10948 BreakpointPromptEditor::new(
10949 weak_editor,
10950 anchor,
10951 breakpoint.clone(),
10952 edit_action,
10953 window,
10954 cx,
10955 )
10956 });
10957
10958 let height = bp_prompt.update(cx, |this, cx| {
10959 this.prompt
10960 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10961 });
10962 let cloned_prompt = bp_prompt.clone();
10963 let blocks = vec![BlockProperties {
10964 style: BlockStyle::Sticky,
10965 placement: BlockPlacement::Above(anchor),
10966 height: Some(height),
10967 render: Arc::new(move |cx| {
10968 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10969 cloned_prompt.clone().into_any_element()
10970 }),
10971 priority: 0,
10972 }];
10973
10974 let focus_handle = bp_prompt.focus_handle(cx);
10975 window.focus(&focus_handle);
10976
10977 let block_ids = self.insert_blocks(blocks, None, cx);
10978 bp_prompt.update(cx, |prompt, _| {
10979 prompt.add_block_ids(block_ids);
10980 });
10981 }
10982
10983 pub(crate) fn breakpoint_at_row(
10984 &self,
10985 row: u32,
10986 window: &mut Window,
10987 cx: &mut Context<Self>,
10988 ) -> Option<(Anchor, Breakpoint)> {
10989 let snapshot = self.snapshot(window, cx);
10990 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
10991
10992 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10993 }
10994
10995 pub(crate) fn breakpoint_at_anchor(
10996 &self,
10997 breakpoint_position: Anchor,
10998 snapshot: &EditorSnapshot,
10999 cx: &mut Context<Self>,
11000 ) -> Option<(Anchor, Breakpoint)> {
11001 let buffer = self
11002 .buffer
11003 .read(cx)
11004 .buffer_for_anchor(breakpoint_position, cx)?;
11005
11006 let enclosing_excerpt = breakpoint_position.excerpt_id;
11007 let buffer_snapshot = buffer.read(cx).snapshot();
11008
11009 let row = buffer_snapshot
11010 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11011 .row;
11012
11013 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11014 let anchor_end = snapshot
11015 .buffer_snapshot()
11016 .anchor_after(Point::new(row, line_len));
11017
11018 self.breakpoint_store
11019 .as_ref()?
11020 .read_with(cx, |breakpoint_store, cx| {
11021 breakpoint_store
11022 .breakpoints(
11023 &buffer,
11024 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11025 &buffer_snapshot,
11026 cx,
11027 )
11028 .next()
11029 .and_then(|(bp, _)| {
11030 let breakpoint_row = buffer_snapshot
11031 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11032 .row;
11033
11034 if breakpoint_row == row {
11035 snapshot
11036 .buffer_snapshot()
11037 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11038 .map(|position| (position, bp.bp.clone()))
11039 } else {
11040 None
11041 }
11042 })
11043 })
11044 }
11045
11046 pub fn edit_log_breakpoint(
11047 &mut self,
11048 _: &EditLogBreakpoint,
11049 window: &mut Window,
11050 cx: &mut Context<Self>,
11051 ) {
11052 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11053 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11054 message: None,
11055 state: BreakpointState::Enabled,
11056 condition: None,
11057 hit_condition: None,
11058 });
11059
11060 self.add_edit_breakpoint_block(
11061 anchor,
11062 &breakpoint,
11063 BreakpointPromptEditAction::Log,
11064 window,
11065 cx,
11066 );
11067 }
11068 }
11069
11070 fn breakpoints_at_cursors(
11071 &self,
11072 window: &mut Window,
11073 cx: &mut Context<Self>,
11074 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11075 let snapshot = self.snapshot(window, cx);
11076 let cursors = self
11077 .selections
11078 .disjoint_anchors_arc()
11079 .iter()
11080 .map(|selection| {
11081 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11082
11083 let breakpoint_position = self
11084 .breakpoint_at_row(cursor_position.row, window, cx)
11085 .map(|bp| bp.0)
11086 .unwrap_or_else(|| {
11087 snapshot
11088 .display_snapshot
11089 .buffer_snapshot()
11090 .anchor_after(Point::new(cursor_position.row, 0))
11091 });
11092
11093 let breakpoint = self
11094 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11095 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11096
11097 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11098 })
11099 // 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.
11100 .collect::<HashMap<Anchor, _>>();
11101
11102 cursors.into_iter().collect()
11103 }
11104
11105 pub fn enable_breakpoint(
11106 &mut self,
11107 _: &crate::actions::EnableBreakpoint,
11108 window: &mut Window,
11109 cx: &mut Context<Self>,
11110 ) {
11111 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11112 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11113 continue;
11114 };
11115 self.edit_breakpoint_at_anchor(
11116 anchor,
11117 breakpoint,
11118 BreakpointEditAction::InvertState,
11119 cx,
11120 );
11121 }
11122 }
11123
11124 pub fn disable_breakpoint(
11125 &mut self,
11126 _: &crate::actions::DisableBreakpoint,
11127 window: &mut Window,
11128 cx: &mut Context<Self>,
11129 ) {
11130 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11131 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11132 continue;
11133 };
11134 self.edit_breakpoint_at_anchor(
11135 anchor,
11136 breakpoint,
11137 BreakpointEditAction::InvertState,
11138 cx,
11139 );
11140 }
11141 }
11142
11143 pub fn toggle_breakpoint(
11144 &mut self,
11145 _: &crate::actions::ToggleBreakpoint,
11146 window: &mut Window,
11147 cx: &mut Context<Self>,
11148 ) {
11149 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11150 if let Some(breakpoint) = breakpoint {
11151 self.edit_breakpoint_at_anchor(
11152 anchor,
11153 breakpoint,
11154 BreakpointEditAction::Toggle,
11155 cx,
11156 );
11157 } else {
11158 self.edit_breakpoint_at_anchor(
11159 anchor,
11160 Breakpoint::new_standard(),
11161 BreakpointEditAction::Toggle,
11162 cx,
11163 );
11164 }
11165 }
11166 }
11167
11168 pub fn edit_breakpoint_at_anchor(
11169 &mut self,
11170 breakpoint_position: Anchor,
11171 breakpoint: Breakpoint,
11172 edit_action: BreakpointEditAction,
11173 cx: &mut Context<Self>,
11174 ) {
11175 let Some(breakpoint_store) = &self.breakpoint_store else {
11176 return;
11177 };
11178
11179 let Some(buffer) = self
11180 .buffer
11181 .read(cx)
11182 .buffer_for_anchor(breakpoint_position, cx)
11183 else {
11184 return;
11185 };
11186
11187 breakpoint_store.update(cx, |breakpoint_store, cx| {
11188 breakpoint_store.toggle_breakpoint(
11189 buffer,
11190 BreakpointWithPosition {
11191 position: breakpoint_position.text_anchor,
11192 bp: breakpoint,
11193 },
11194 edit_action,
11195 cx,
11196 );
11197 });
11198
11199 cx.notify();
11200 }
11201
11202 #[cfg(any(test, feature = "test-support"))]
11203 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11204 self.breakpoint_store.clone()
11205 }
11206
11207 pub fn prepare_restore_change(
11208 &self,
11209 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11210 hunk: &MultiBufferDiffHunk,
11211 cx: &mut App,
11212 ) -> Option<()> {
11213 if hunk.is_created_file() {
11214 return None;
11215 }
11216 let buffer = self.buffer.read(cx);
11217 let diff = buffer.diff_for(hunk.buffer_id)?;
11218 let buffer = buffer.buffer(hunk.buffer_id)?;
11219 let buffer = buffer.read(cx);
11220 let original_text = diff
11221 .read(cx)
11222 .base_text()
11223 .as_rope()
11224 .slice(hunk.diff_base_byte_range.clone());
11225 let buffer_snapshot = buffer.snapshot();
11226 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11227 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11228 probe
11229 .0
11230 .start
11231 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11232 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11233 }) {
11234 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11235 Some(())
11236 } else {
11237 None
11238 }
11239 }
11240
11241 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11242 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11243 }
11244
11245 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11246 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11247 }
11248
11249 fn manipulate_lines<M>(
11250 &mut self,
11251 window: &mut Window,
11252 cx: &mut Context<Self>,
11253 mut manipulate: M,
11254 ) where
11255 M: FnMut(&str) -> LineManipulationResult,
11256 {
11257 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11258
11259 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11260 let buffer = self.buffer.read(cx).snapshot(cx);
11261
11262 let mut edits = Vec::new();
11263
11264 let selections = self.selections.all::<Point>(&display_map);
11265 let mut selections = selections.iter().peekable();
11266 let mut contiguous_row_selections = Vec::new();
11267 let mut new_selections = Vec::new();
11268 let mut added_lines = 0;
11269 let mut removed_lines = 0;
11270
11271 while let Some(selection) = selections.next() {
11272 let (start_row, end_row) = consume_contiguous_rows(
11273 &mut contiguous_row_selections,
11274 selection,
11275 &display_map,
11276 &mut selections,
11277 );
11278
11279 let start_point = Point::new(start_row.0, 0);
11280 let end_point = Point::new(
11281 end_row.previous_row().0,
11282 buffer.line_len(end_row.previous_row()),
11283 );
11284 let text = buffer
11285 .text_for_range(start_point..end_point)
11286 .collect::<String>();
11287
11288 let LineManipulationResult {
11289 new_text,
11290 line_count_before,
11291 line_count_after,
11292 } = manipulate(&text);
11293
11294 edits.push((start_point..end_point, new_text));
11295
11296 // Selections must change based on added and removed line count
11297 let start_row =
11298 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11299 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11300 new_selections.push(Selection {
11301 id: selection.id,
11302 start: start_row,
11303 end: end_row,
11304 goal: SelectionGoal::None,
11305 reversed: selection.reversed,
11306 });
11307
11308 if line_count_after > line_count_before {
11309 added_lines += line_count_after - line_count_before;
11310 } else if line_count_before > line_count_after {
11311 removed_lines += line_count_before - line_count_after;
11312 }
11313 }
11314
11315 self.transact(window, cx, |this, window, cx| {
11316 let buffer = this.buffer.update(cx, |buffer, cx| {
11317 buffer.edit(edits, None, cx);
11318 buffer.snapshot(cx)
11319 });
11320
11321 // Recalculate offsets on newly edited buffer
11322 let new_selections = new_selections
11323 .iter()
11324 .map(|s| {
11325 let start_point = Point::new(s.start.0, 0);
11326 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11327 Selection {
11328 id: s.id,
11329 start: buffer.point_to_offset(start_point),
11330 end: buffer.point_to_offset(end_point),
11331 goal: s.goal,
11332 reversed: s.reversed,
11333 }
11334 })
11335 .collect();
11336
11337 this.change_selections(Default::default(), window, cx, |s| {
11338 s.select(new_selections);
11339 });
11340
11341 this.request_autoscroll(Autoscroll::fit(), cx);
11342 });
11343 }
11344
11345 fn manipulate_immutable_lines<Fn>(
11346 &mut self,
11347 window: &mut Window,
11348 cx: &mut Context<Self>,
11349 mut callback: Fn,
11350 ) where
11351 Fn: FnMut(&mut Vec<&str>),
11352 {
11353 self.manipulate_lines(window, cx, |text| {
11354 let mut lines: Vec<&str> = text.split('\n').collect();
11355 let line_count_before = lines.len();
11356
11357 callback(&mut lines);
11358
11359 LineManipulationResult {
11360 new_text: lines.join("\n"),
11361 line_count_before,
11362 line_count_after: lines.len(),
11363 }
11364 });
11365 }
11366
11367 fn manipulate_mutable_lines<Fn>(
11368 &mut self,
11369 window: &mut Window,
11370 cx: &mut Context<Self>,
11371 mut callback: Fn,
11372 ) where
11373 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11374 {
11375 self.manipulate_lines(window, cx, |text| {
11376 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11377 let line_count_before = lines.len();
11378
11379 callback(&mut lines);
11380
11381 LineManipulationResult {
11382 new_text: lines.join("\n"),
11383 line_count_before,
11384 line_count_after: lines.len(),
11385 }
11386 });
11387 }
11388
11389 pub fn convert_indentation_to_spaces(
11390 &mut self,
11391 _: &ConvertIndentationToSpaces,
11392 window: &mut Window,
11393 cx: &mut Context<Self>,
11394 ) {
11395 let settings = self.buffer.read(cx).language_settings(cx);
11396 let tab_size = settings.tab_size.get() as usize;
11397
11398 self.manipulate_mutable_lines(window, cx, |lines| {
11399 // Allocates a reasonably sized scratch buffer once for the whole loop
11400 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11401 // Avoids recomputing spaces that could be inserted many times
11402 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11403 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11404 .collect();
11405
11406 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11407 let mut chars = line.as_ref().chars();
11408 let mut col = 0;
11409 let mut changed = false;
11410
11411 for ch in chars.by_ref() {
11412 match ch {
11413 ' ' => {
11414 reindented_line.push(' ');
11415 col += 1;
11416 }
11417 '\t' => {
11418 // \t are converted to spaces depending on the current column
11419 let spaces_len = tab_size - (col % tab_size);
11420 reindented_line.extend(&space_cache[spaces_len - 1]);
11421 col += spaces_len;
11422 changed = true;
11423 }
11424 _ => {
11425 // If we dont append before break, the character is consumed
11426 reindented_line.push(ch);
11427 break;
11428 }
11429 }
11430 }
11431
11432 if !changed {
11433 reindented_line.clear();
11434 continue;
11435 }
11436 // Append the rest of the line and replace old reference with new one
11437 reindented_line.extend(chars);
11438 *line = Cow::Owned(reindented_line.clone());
11439 reindented_line.clear();
11440 }
11441 });
11442 }
11443
11444 pub fn convert_indentation_to_tabs(
11445 &mut self,
11446 _: &ConvertIndentationToTabs,
11447 window: &mut Window,
11448 cx: &mut Context<Self>,
11449 ) {
11450 let settings = self.buffer.read(cx).language_settings(cx);
11451 let tab_size = settings.tab_size.get() as usize;
11452
11453 self.manipulate_mutable_lines(window, cx, |lines| {
11454 // Allocates a reasonably sized buffer once for the whole loop
11455 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11456 // Avoids recomputing spaces that could be inserted many times
11457 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11458 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11459 .collect();
11460
11461 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11462 let mut chars = line.chars();
11463 let mut spaces_count = 0;
11464 let mut first_non_indent_char = None;
11465 let mut changed = false;
11466
11467 for ch in chars.by_ref() {
11468 match ch {
11469 ' ' => {
11470 // Keep track of spaces. Append \t when we reach tab_size
11471 spaces_count += 1;
11472 changed = true;
11473 if spaces_count == tab_size {
11474 reindented_line.push('\t');
11475 spaces_count = 0;
11476 }
11477 }
11478 '\t' => {
11479 reindented_line.push('\t');
11480 spaces_count = 0;
11481 }
11482 _ => {
11483 // Dont append it yet, we might have remaining spaces
11484 first_non_indent_char = Some(ch);
11485 break;
11486 }
11487 }
11488 }
11489
11490 if !changed {
11491 reindented_line.clear();
11492 continue;
11493 }
11494 // Remaining spaces that didn't make a full tab stop
11495 if spaces_count > 0 {
11496 reindented_line.extend(&space_cache[spaces_count - 1]);
11497 }
11498 // If we consume an extra character that was not indentation, add it back
11499 if let Some(extra_char) = first_non_indent_char {
11500 reindented_line.push(extra_char);
11501 }
11502 // Append the rest of the line and replace old reference with new one
11503 reindented_line.extend(chars);
11504 *line = Cow::Owned(reindented_line.clone());
11505 reindented_line.clear();
11506 }
11507 });
11508 }
11509
11510 pub fn convert_to_upper_case(
11511 &mut self,
11512 _: &ConvertToUpperCase,
11513 window: &mut Window,
11514 cx: &mut Context<Self>,
11515 ) {
11516 self.manipulate_text(window, cx, |text| text.to_uppercase())
11517 }
11518
11519 pub fn convert_to_lower_case(
11520 &mut self,
11521 _: &ConvertToLowerCase,
11522 window: &mut Window,
11523 cx: &mut Context<Self>,
11524 ) {
11525 self.manipulate_text(window, cx, |text| text.to_lowercase())
11526 }
11527
11528 pub fn convert_to_title_case(
11529 &mut self,
11530 _: &ConvertToTitleCase,
11531 window: &mut Window,
11532 cx: &mut Context<Self>,
11533 ) {
11534 self.manipulate_text(window, cx, |text| {
11535 text.split('\n')
11536 .map(|line| line.to_case(Case::Title))
11537 .join("\n")
11538 })
11539 }
11540
11541 pub fn convert_to_snake_case(
11542 &mut self,
11543 _: &ConvertToSnakeCase,
11544 window: &mut Window,
11545 cx: &mut Context<Self>,
11546 ) {
11547 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11548 }
11549
11550 pub fn convert_to_kebab_case(
11551 &mut self,
11552 _: &ConvertToKebabCase,
11553 window: &mut Window,
11554 cx: &mut Context<Self>,
11555 ) {
11556 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11557 }
11558
11559 pub fn convert_to_upper_camel_case(
11560 &mut self,
11561 _: &ConvertToUpperCamelCase,
11562 window: &mut Window,
11563 cx: &mut Context<Self>,
11564 ) {
11565 self.manipulate_text(window, cx, |text| {
11566 text.split('\n')
11567 .map(|line| line.to_case(Case::UpperCamel))
11568 .join("\n")
11569 })
11570 }
11571
11572 pub fn convert_to_lower_camel_case(
11573 &mut self,
11574 _: &ConvertToLowerCamelCase,
11575 window: &mut Window,
11576 cx: &mut Context<Self>,
11577 ) {
11578 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11579 }
11580
11581 pub fn convert_to_opposite_case(
11582 &mut self,
11583 _: &ConvertToOppositeCase,
11584 window: &mut Window,
11585 cx: &mut Context<Self>,
11586 ) {
11587 self.manipulate_text(window, cx, |text| {
11588 text.chars()
11589 .fold(String::with_capacity(text.len()), |mut t, c| {
11590 if c.is_uppercase() {
11591 t.extend(c.to_lowercase());
11592 } else {
11593 t.extend(c.to_uppercase());
11594 }
11595 t
11596 })
11597 })
11598 }
11599
11600 pub fn convert_to_sentence_case(
11601 &mut self,
11602 _: &ConvertToSentenceCase,
11603 window: &mut Window,
11604 cx: &mut Context<Self>,
11605 ) {
11606 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11607 }
11608
11609 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11610 self.manipulate_text(window, cx, |text| {
11611 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11612 if has_upper_case_characters {
11613 text.to_lowercase()
11614 } else {
11615 text.to_uppercase()
11616 }
11617 })
11618 }
11619
11620 pub fn convert_to_rot13(
11621 &mut self,
11622 _: &ConvertToRot13,
11623 window: &mut Window,
11624 cx: &mut Context<Self>,
11625 ) {
11626 self.manipulate_text(window, cx, |text| {
11627 text.chars()
11628 .map(|c| match c {
11629 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11630 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11631 _ => c,
11632 })
11633 .collect()
11634 })
11635 }
11636
11637 pub fn convert_to_rot47(
11638 &mut self,
11639 _: &ConvertToRot47,
11640 window: &mut Window,
11641 cx: &mut Context<Self>,
11642 ) {
11643 self.manipulate_text(window, cx, |text| {
11644 text.chars()
11645 .map(|c| {
11646 let code_point = c as u32;
11647 if code_point >= 33 && code_point <= 126 {
11648 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11649 }
11650 c
11651 })
11652 .collect()
11653 })
11654 }
11655
11656 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11657 where
11658 Fn: FnMut(&str) -> String,
11659 {
11660 let buffer = self.buffer.read(cx).snapshot(cx);
11661
11662 let mut new_selections = Vec::new();
11663 let mut edits = Vec::new();
11664 let mut selection_adjustment = 0i32;
11665
11666 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11667 let selection_is_empty = selection.is_empty();
11668
11669 let (start, end) = if selection_is_empty {
11670 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11671 (word_range.start, word_range.end)
11672 } else {
11673 (
11674 buffer.point_to_offset(selection.start),
11675 buffer.point_to_offset(selection.end),
11676 )
11677 };
11678
11679 let text = buffer.text_for_range(start..end).collect::<String>();
11680 let old_length = text.len() as i32;
11681 let text = callback(&text);
11682
11683 new_selections.push(Selection {
11684 start: (start as i32 - selection_adjustment) as usize,
11685 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11686 goal: SelectionGoal::None,
11687 id: selection.id,
11688 reversed: selection.reversed,
11689 });
11690
11691 selection_adjustment += old_length - text.len() as i32;
11692
11693 edits.push((start..end, text));
11694 }
11695
11696 self.transact(window, cx, |this, window, cx| {
11697 this.buffer.update(cx, |buffer, cx| {
11698 buffer.edit(edits, None, cx);
11699 });
11700
11701 this.change_selections(Default::default(), window, cx, |s| {
11702 s.select(new_selections);
11703 });
11704
11705 this.request_autoscroll(Autoscroll::fit(), cx);
11706 });
11707 }
11708
11709 pub fn move_selection_on_drop(
11710 &mut self,
11711 selection: &Selection<Anchor>,
11712 target: DisplayPoint,
11713 is_cut: bool,
11714 window: &mut Window,
11715 cx: &mut Context<Self>,
11716 ) {
11717 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11718 let buffer = display_map.buffer_snapshot();
11719 let mut edits = Vec::new();
11720 let insert_point = display_map
11721 .clip_point(target, Bias::Left)
11722 .to_point(&display_map);
11723 let text = buffer
11724 .text_for_range(selection.start..selection.end)
11725 .collect::<String>();
11726 if is_cut {
11727 edits.push(((selection.start..selection.end), String::new()));
11728 }
11729 let insert_anchor = buffer.anchor_before(insert_point);
11730 edits.push(((insert_anchor..insert_anchor), text));
11731 let last_edit_start = insert_anchor.bias_left(buffer);
11732 let last_edit_end = insert_anchor.bias_right(buffer);
11733 self.transact(window, cx, |this, window, cx| {
11734 this.buffer.update(cx, |buffer, cx| {
11735 buffer.edit(edits, None, cx);
11736 });
11737 this.change_selections(Default::default(), window, cx, |s| {
11738 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11739 });
11740 });
11741 }
11742
11743 pub fn clear_selection_drag_state(&mut self) {
11744 self.selection_drag_state = SelectionDragState::None;
11745 }
11746
11747 pub fn duplicate(
11748 &mut self,
11749 upwards: bool,
11750 whole_lines: bool,
11751 window: &mut Window,
11752 cx: &mut Context<Self>,
11753 ) {
11754 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11755
11756 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11757 let buffer = display_map.buffer_snapshot();
11758 let selections = self.selections.all::<Point>(&display_map);
11759
11760 let mut edits = Vec::new();
11761 let mut selections_iter = selections.iter().peekable();
11762 while let Some(selection) = selections_iter.next() {
11763 let mut rows = selection.spanned_rows(false, &display_map);
11764 // duplicate line-wise
11765 if whole_lines || selection.start == selection.end {
11766 // Avoid duplicating the same lines twice.
11767 while let Some(next_selection) = selections_iter.peek() {
11768 let next_rows = next_selection.spanned_rows(false, &display_map);
11769 if next_rows.start < rows.end {
11770 rows.end = next_rows.end;
11771 selections_iter.next().unwrap();
11772 } else {
11773 break;
11774 }
11775 }
11776
11777 // Copy the text from the selected row region and splice it either at the start
11778 // or end of the region.
11779 let start = Point::new(rows.start.0, 0);
11780 let end = Point::new(
11781 rows.end.previous_row().0,
11782 buffer.line_len(rows.end.previous_row()),
11783 );
11784
11785 let mut text = buffer.text_for_range(start..end).collect::<String>();
11786
11787 let insert_location = if upwards {
11788 // When duplicating upward, we need to insert before the current line.
11789 // If we're on the last line and it doesn't end with a newline,
11790 // we need to add a newline before the duplicated content.
11791 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11792 && buffer.max_point().column > 0
11793 && !text.ends_with('\n');
11794
11795 if needs_leading_newline {
11796 text.insert(0, '\n');
11797 end
11798 } else {
11799 text.push('\n');
11800 Point::new(rows.end.0, 0)
11801 }
11802 } else {
11803 text.push('\n');
11804 start
11805 };
11806 edits.push((insert_location..insert_location, text));
11807 } else {
11808 // duplicate character-wise
11809 let start = selection.start;
11810 let end = selection.end;
11811 let text = buffer.text_for_range(start..end).collect::<String>();
11812 edits.push((selection.end..selection.end, text));
11813 }
11814 }
11815
11816 self.transact(window, cx, |this, _, cx| {
11817 this.buffer.update(cx, |buffer, cx| {
11818 buffer.edit(edits, None, cx);
11819 });
11820
11821 this.request_autoscroll(Autoscroll::fit(), cx);
11822 });
11823 }
11824
11825 pub fn duplicate_line_up(
11826 &mut self,
11827 _: &DuplicateLineUp,
11828 window: &mut Window,
11829 cx: &mut Context<Self>,
11830 ) {
11831 self.duplicate(true, true, window, cx);
11832 }
11833
11834 pub fn duplicate_line_down(
11835 &mut self,
11836 _: &DuplicateLineDown,
11837 window: &mut Window,
11838 cx: &mut Context<Self>,
11839 ) {
11840 self.duplicate(false, true, window, cx);
11841 }
11842
11843 pub fn duplicate_selection(
11844 &mut self,
11845 _: &DuplicateSelection,
11846 window: &mut Window,
11847 cx: &mut Context<Self>,
11848 ) {
11849 self.duplicate(false, false, window, cx);
11850 }
11851
11852 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11853 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11854 if self.mode.is_single_line() {
11855 cx.propagate();
11856 return;
11857 }
11858
11859 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11860 let buffer = self.buffer.read(cx).snapshot(cx);
11861
11862 let mut edits = Vec::new();
11863 let mut unfold_ranges = Vec::new();
11864 let mut refold_creases = Vec::new();
11865
11866 let selections = self.selections.all::<Point>(&display_map);
11867 let mut selections = selections.iter().peekable();
11868 let mut contiguous_row_selections = Vec::new();
11869 let mut new_selections = Vec::new();
11870
11871 while let Some(selection) = selections.next() {
11872 // Find all the selections that span a contiguous row range
11873 let (start_row, end_row) = consume_contiguous_rows(
11874 &mut contiguous_row_selections,
11875 selection,
11876 &display_map,
11877 &mut selections,
11878 );
11879
11880 // Move the text spanned by the row range to be before the line preceding the row range
11881 if start_row.0 > 0 {
11882 let range_to_move = Point::new(
11883 start_row.previous_row().0,
11884 buffer.line_len(start_row.previous_row()),
11885 )
11886 ..Point::new(
11887 end_row.previous_row().0,
11888 buffer.line_len(end_row.previous_row()),
11889 );
11890 let insertion_point = display_map
11891 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11892 .0;
11893
11894 // Don't move lines across excerpts
11895 if buffer
11896 .excerpt_containing(insertion_point..range_to_move.end)
11897 .is_some()
11898 {
11899 let text = buffer
11900 .text_for_range(range_to_move.clone())
11901 .flat_map(|s| s.chars())
11902 .skip(1)
11903 .chain(['\n'])
11904 .collect::<String>();
11905
11906 edits.push((
11907 buffer.anchor_after(range_to_move.start)
11908 ..buffer.anchor_before(range_to_move.end),
11909 String::new(),
11910 ));
11911 let insertion_anchor = buffer.anchor_after(insertion_point);
11912 edits.push((insertion_anchor..insertion_anchor, text));
11913
11914 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11915
11916 // Move selections up
11917 new_selections.extend(contiguous_row_selections.drain(..).map(
11918 |mut selection| {
11919 selection.start.row -= row_delta;
11920 selection.end.row -= row_delta;
11921 selection
11922 },
11923 ));
11924
11925 // Move folds up
11926 unfold_ranges.push(range_to_move.clone());
11927 for fold in display_map.folds_in_range(
11928 buffer.anchor_before(range_to_move.start)
11929 ..buffer.anchor_after(range_to_move.end),
11930 ) {
11931 let mut start = fold.range.start.to_point(&buffer);
11932 let mut end = fold.range.end.to_point(&buffer);
11933 start.row -= row_delta;
11934 end.row -= row_delta;
11935 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11936 }
11937 }
11938 }
11939
11940 // If we didn't move line(s), preserve the existing selections
11941 new_selections.append(&mut contiguous_row_selections);
11942 }
11943
11944 self.transact(window, cx, |this, window, cx| {
11945 this.unfold_ranges(&unfold_ranges, true, true, cx);
11946 this.buffer.update(cx, |buffer, cx| {
11947 for (range, text) in edits {
11948 buffer.edit([(range, text)], None, cx);
11949 }
11950 });
11951 this.fold_creases(refold_creases, true, window, cx);
11952 this.change_selections(Default::default(), window, cx, |s| {
11953 s.select(new_selections);
11954 })
11955 });
11956 }
11957
11958 pub fn move_line_down(
11959 &mut self,
11960 _: &MoveLineDown,
11961 window: &mut Window,
11962 cx: &mut Context<Self>,
11963 ) {
11964 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11965 if self.mode.is_single_line() {
11966 cx.propagate();
11967 return;
11968 }
11969
11970 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11971 let buffer = self.buffer.read(cx).snapshot(cx);
11972
11973 let mut edits = Vec::new();
11974 let mut unfold_ranges = Vec::new();
11975 let mut refold_creases = Vec::new();
11976
11977 let selections = self.selections.all::<Point>(&display_map);
11978 let mut selections = selections.iter().peekable();
11979 let mut contiguous_row_selections = Vec::new();
11980 let mut new_selections = Vec::new();
11981
11982 while let Some(selection) = selections.next() {
11983 // Find all the selections that span a contiguous row range
11984 let (start_row, end_row) = consume_contiguous_rows(
11985 &mut contiguous_row_selections,
11986 selection,
11987 &display_map,
11988 &mut selections,
11989 );
11990
11991 // Move the text spanned by the row range to be after the last line of the row range
11992 if end_row.0 <= buffer.max_point().row {
11993 let range_to_move =
11994 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11995 let insertion_point = display_map
11996 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11997 .0;
11998
11999 // Don't move lines across excerpt boundaries
12000 if buffer
12001 .excerpt_containing(range_to_move.start..insertion_point)
12002 .is_some()
12003 {
12004 let mut text = String::from("\n");
12005 text.extend(buffer.text_for_range(range_to_move.clone()));
12006 text.pop(); // Drop trailing newline
12007 edits.push((
12008 buffer.anchor_after(range_to_move.start)
12009 ..buffer.anchor_before(range_to_move.end),
12010 String::new(),
12011 ));
12012 let insertion_anchor = buffer.anchor_after(insertion_point);
12013 edits.push((insertion_anchor..insertion_anchor, text));
12014
12015 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12016
12017 // Move selections down
12018 new_selections.extend(contiguous_row_selections.drain(..).map(
12019 |mut selection| {
12020 selection.start.row += row_delta;
12021 selection.end.row += row_delta;
12022 selection
12023 },
12024 ));
12025
12026 // Move folds down
12027 unfold_ranges.push(range_to_move.clone());
12028 for fold in display_map.folds_in_range(
12029 buffer.anchor_before(range_to_move.start)
12030 ..buffer.anchor_after(range_to_move.end),
12031 ) {
12032 let mut start = fold.range.start.to_point(&buffer);
12033 let mut end = fold.range.end.to_point(&buffer);
12034 start.row += row_delta;
12035 end.row += row_delta;
12036 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12037 }
12038 }
12039 }
12040
12041 // If we didn't move line(s), preserve the existing selections
12042 new_selections.append(&mut contiguous_row_selections);
12043 }
12044
12045 self.transact(window, cx, |this, window, cx| {
12046 this.unfold_ranges(&unfold_ranges, true, true, cx);
12047 this.buffer.update(cx, |buffer, cx| {
12048 for (range, text) in edits {
12049 buffer.edit([(range, text)], None, cx);
12050 }
12051 });
12052 this.fold_creases(refold_creases, true, window, cx);
12053 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12054 });
12055 }
12056
12057 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12058 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12059 let text_layout_details = &self.text_layout_details(window);
12060 self.transact(window, cx, |this, window, cx| {
12061 let edits = this.change_selections(Default::default(), window, cx, |s| {
12062 let mut edits: Vec<(Range<usize>, String)> = Default::default();
12063 s.move_with(|display_map, selection| {
12064 if !selection.is_empty() {
12065 return;
12066 }
12067
12068 let mut head = selection.head();
12069 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12070 if head.column() == display_map.line_len(head.row()) {
12071 transpose_offset = display_map
12072 .buffer_snapshot()
12073 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12074 }
12075
12076 if transpose_offset == 0 {
12077 return;
12078 }
12079
12080 *head.column_mut() += 1;
12081 head = display_map.clip_point(head, Bias::Right);
12082 let goal = SelectionGoal::HorizontalPosition(
12083 display_map
12084 .x_for_display_point(head, text_layout_details)
12085 .into(),
12086 );
12087 selection.collapse_to(head, goal);
12088
12089 let transpose_start = display_map
12090 .buffer_snapshot()
12091 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12092 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12093 let transpose_end = display_map
12094 .buffer_snapshot()
12095 .clip_offset(transpose_offset + 1, Bias::Right);
12096 if let Some(ch) = display_map
12097 .buffer_snapshot()
12098 .chars_at(transpose_start)
12099 .next()
12100 {
12101 edits.push((transpose_start..transpose_offset, String::new()));
12102 edits.push((transpose_end..transpose_end, ch.to_string()));
12103 }
12104 }
12105 });
12106 edits
12107 });
12108 this.buffer
12109 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12110 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12111 this.change_selections(Default::default(), window, cx, |s| {
12112 s.select(selections);
12113 });
12114 });
12115 }
12116
12117 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12118 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12119 if self.mode.is_single_line() {
12120 cx.propagate();
12121 return;
12122 }
12123
12124 self.rewrap_impl(RewrapOptions::default(), cx)
12125 }
12126
12127 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12128 let buffer = self.buffer.read(cx).snapshot(cx);
12129 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12130
12131 #[derive(Clone, Debug, PartialEq)]
12132 enum CommentFormat {
12133 /// single line comment, with prefix for line
12134 Line(String),
12135 /// single line within a block comment, with prefix for line
12136 BlockLine(String),
12137 /// a single line of a block comment that includes the initial delimiter
12138 BlockCommentWithStart(BlockCommentConfig),
12139 /// a single line of a block comment that includes the ending delimiter
12140 BlockCommentWithEnd(BlockCommentConfig),
12141 }
12142
12143 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12144 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12145 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12146 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12147 .peekable();
12148
12149 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12150 row
12151 } else {
12152 return Vec::new();
12153 };
12154
12155 let language_settings = buffer.language_settings_at(selection.head(), cx);
12156 let language_scope = buffer.language_scope_at(selection.head());
12157
12158 let indent_and_prefix_for_row =
12159 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12160 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12161 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12162 &language_scope
12163 {
12164 let indent_end = Point::new(row, indent.len);
12165 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12166 let line_text_after_indent = buffer
12167 .text_for_range(indent_end..line_end)
12168 .collect::<String>();
12169
12170 let is_within_comment_override = buffer
12171 .language_scope_at(indent_end)
12172 .is_some_and(|scope| scope.override_name() == Some("comment"));
12173 let comment_delimiters = if is_within_comment_override {
12174 // we are within a comment syntax node, but we don't
12175 // yet know what kind of comment: block, doc or line
12176 match (
12177 language_scope.documentation_comment(),
12178 language_scope.block_comment(),
12179 ) {
12180 (Some(config), _) | (_, Some(config))
12181 if buffer.contains_str_at(indent_end, &config.start) =>
12182 {
12183 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12184 }
12185 (Some(config), _) | (_, Some(config))
12186 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12187 {
12188 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12189 }
12190 (Some(config), _) | (_, Some(config))
12191 if buffer.contains_str_at(indent_end, &config.prefix) =>
12192 {
12193 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12194 }
12195 (_, _) => language_scope
12196 .line_comment_prefixes()
12197 .iter()
12198 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12199 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12200 }
12201 } else {
12202 // we not in an overridden comment node, but we may
12203 // be within a non-overridden line comment node
12204 language_scope
12205 .line_comment_prefixes()
12206 .iter()
12207 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12208 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12209 };
12210
12211 let rewrap_prefix = language_scope
12212 .rewrap_prefixes()
12213 .iter()
12214 .find_map(|prefix_regex| {
12215 prefix_regex.find(&line_text_after_indent).map(|mat| {
12216 if mat.start() == 0 {
12217 Some(mat.as_str().to_string())
12218 } else {
12219 None
12220 }
12221 })
12222 })
12223 .flatten();
12224 (comment_delimiters, rewrap_prefix)
12225 } else {
12226 (None, None)
12227 };
12228 (indent, comment_prefix, rewrap_prefix)
12229 };
12230
12231 let mut ranges = Vec::new();
12232 let from_empty_selection = selection.is_empty();
12233
12234 let mut current_range_start = first_row;
12235 let mut prev_row = first_row;
12236 let (
12237 mut current_range_indent,
12238 mut current_range_comment_delimiters,
12239 mut current_range_rewrap_prefix,
12240 ) = indent_and_prefix_for_row(first_row);
12241
12242 for row in non_blank_rows_iter.skip(1) {
12243 let has_paragraph_break = row > prev_row + 1;
12244
12245 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12246 indent_and_prefix_for_row(row);
12247
12248 let has_indent_change = row_indent != current_range_indent;
12249 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12250
12251 let has_boundary_change = has_comment_change
12252 || row_rewrap_prefix.is_some()
12253 || (has_indent_change && current_range_comment_delimiters.is_some());
12254
12255 if has_paragraph_break || has_boundary_change {
12256 ranges.push((
12257 language_settings.clone(),
12258 Point::new(current_range_start, 0)
12259 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12260 current_range_indent,
12261 current_range_comment_delimiters.clone(),
12262 current_range_rewrap_prefix.clone(),
12263 from_empty_selection,
12264 ));
12265 current_range_start = row;
12266 current_range_indent = row_indent;
12267 current_range_comment_delimiters = row_comment_delimiters;
12268 current_range_rewrap_prefix = row_rewrap_prefix;
12269 }
12270 prev_row = row;
12271 }
12272
12273 ranges.push((
12274 language_settings.clone(),
12275 Point::new(current_range_start, 0)
12276 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12277 current_range_indent,
12278 current_range_comment_delimiters,
12279 current_range_rewrap_prefix,
12280 from_empty_selection,
12281 ));
12282
12283 ranges
12284 });
12285
12286 let mut edits = Vec::new();
12287 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12288
12289 for (
12290 language_settings,
12291 wrap_range,
12292 mut indent_size,
12293 comment_prefix,
12294 rewrap_prefix,
12295 from_empty_selection,
12296 ) in wrap_ranges
12297 {
12298 let mut start_row = wrap_range.start.row;
12299 let mut end_row = wrap_range.end.row;
12300
12301 // Skip selections that overlap with a range that has already been rewrapped.
12302 let selection_range = start_row..end_row;
12303 if rewrapped_row_ranges
12304 .iter()
12305 .any(|range| range.overlaps(&selection_range))
12306 {
12307 continue;
12308 }
12309
12310 let tab_size = language_settings.tab_size;
12311
12312 let (line_prefix, inside_comment) = match &comment_prefix {
12313 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12314 (Some(prefix.as_str()), true)
12315 }
12316 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12317 (Some(prefix.as_ref()), true)
12318 }
12319 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12320 start: _,
12321 end: _,
12322 prefix,
12323 tab_size,
12324 })) => {
12325 indent_size.len += tab_size;
12326 (Some(prefix.as_ref()), true)
12327 }
12328 None => (None, false),
12329 };
12330 let indent_prefix = indent_size.chars().collect::<String>();
12331 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12332
12333 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12334 RewrapBehavior::InComments => inside_comment,
12335 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12336 RewrapBehavior::Anywhere => true,
12337 };
12338
12339 let should_rewrap = options.override_language_settings
12340 || allow_rewrap_based_on_language
12341 || self.hard_wrap.is_some();
12342 if !should_rewrap {
12343 continue;
12344 }
12345
12346 if from_empty_selection {
12347 'expand_upwards: while start_row > 0 {
12348 let prev_row = start_row - 1;
12349 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12350 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12351 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12352 {
12353 start_row = prev_row;
12354 } else {
12355 break 'expand_upwards;
12356 }
12357 }
12358
12359 'expand_downwards: while end_row < buffer.max_point().row {
12360 let next_row = end_row + 1;
12361 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12362 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12363 && !buffer.is_line_blank(MultiBufferRow(next_row))
12364 {
12365 end_row = next_row;
12366 } else {
12367 break 'expand_downwards;
12368 }
12369 }
12370 }
12371
12372 let start = Point::new(start_row, 0);
12373 let start_offset = ToOffset::to_offset(&start, &buffer);
12374 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12375 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12376 let mut first_line_delimiter = None;
12377 let mut last_line_delimiter = None;
12378 let Some(lines_without_prefixes) = selection_text
12379 .lines()
12380 .enumerate()
12381 .map(|(ix, line)| {
12382 let line_trimmed = line.trim_start();
12383 if rewrap_prefix.is_some() && ix > 0 {
12384 Ok(line_trimmed)
12385 } else if let Some(
12386 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12387 start,
12388 prefix,
12389 end,
12390 tab_size,
12391 })
12392 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12393 start,
12394 prefix,
12395 end,
12396 tab_size,
12397 }),
12398 ) = &comment_prefix
12399 {
12400 let line_trimmed = line_trimmed
12401 .strip_prefix(start.as_ref())
12402 .map(|s| {
12403 let mut indent_size = indent_size;
12404 indent_size.len -= tab_size;
12405 let indent_prefix: String = indent_size.chars().collect();
12406 first_line_delimiter = Some((indent_prefix, start));
12407 s.trim_start()
12408 })
12409 .unwrap_or(line_trimmed);
12410 let line_trimmed = line_trimmed
12411 .strip_suffix(end.as_ref())
12412 .map(|s| {
12413 last_line_delimiter = Some(end);
12414 s.trim_end()
12415 })
12416 .unwrap_or(line_trimmed);
12417 let line_trimmed = line_trimmed
12418 .strip_prefix(prefix.as_ref())
12419 .unwrap_or(line_trimmed);
12420 Ok(line_trimmed)
12421 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12422 line_trimmed.strip_prefix(prefix).with_context(|| {
12423 format!("line did not start with prefix {prefix:?}: {line:?}")
12424 })
12425 } else {
12426 line_trimmed
12427 .strip_prefix(&line_prefix.trim_start())
12428 .with_context(|| {
12429 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12430 })
12431 }
12432 })
12433 .collect::<Result<Vec<_>, _>>()
12434 .log_err()
12435 else {
12436 continue;
12437 };
12438
12439 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12440 buffer
12441 .language_settings_at(Point::new(start_row, 0), cx)
12442 .preferred_line_length as usize
12443 });
12444
12445 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12446 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12447 } else {
12448 line_prefix.clone()
12449 };
12450
12451 let wrapped_text = {
12452 let mut wrapped_text = wrap_with_prefix(
12453 line_prefix,
12454 subsequent_lines_prefix,
12455 lines_without_prefixes.join("\n"),
12456 wrap_column,
12457 tab_size,
12458 options.preserve_existing_whitespace,
12459 );
12460
12461 if let Some((indent, delimiter)) = first_line_delimiter {
12462 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12463 }
12464 if let Some(last_line) = last_line_delimiter {
12465 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12466 }
12467
12468 wrapped_text
12469 };
12470
12471 // TODO: should always use char-based diff while still supporting cursor behavior that
12472 // matches vim.
12473 let mut diff_options = DiffOptions::default();
12474 if options.override_language_settings {
12475 diff_options.max_word_diff_len = 0;
12476 diff_options.max_word_diff_line_count = 0;
12477 } else {
12478 diff_options.max_word_diff_len = usize::MAX;
12479 diff_options.max_word_diff_line_count = usize::MAX;
12480 }
12481
12482 for (old_range, new_text) in
12483 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12484 {
12485 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12486 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12487 edits.push((edit_start..edit_end, new_text));
12488 }
12489
12490 rewrapped_row_ranges.push(start_row..=end_row);
12491 }
12492
12493 self.buffer
12494 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12495 }
12496
12497 pub fn cut_common(
12498 &mut self,
12499 cut_no_selection_line: bool,
12500 window: &mut Window,
12501 cx: &mut Context<Self>,
12502 ) -> ClipboardItem {
12503 let mut text = String::new();
12504 let buffer = self.buffer.read(cx).snapshot(cx);
12505 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12506 let mut clipboard_selections = Vec::with_capacity(selections.len());
12507 {
12508 let max_point = buffer.max_point();
12509 let mut is_first = true;
12510 for selection in &mut selections {
12511 let is_entire_line =
12512 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12513 if is_entire_line {
12514 selection.start = Point::new(selection.start.row, 0);
12515 if !selection.is_empty() && selection.end.column == 0 {
12516 selection.end = cmp::min(max_point, selection.end);
12517 } else {
12518 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12519 }
12520 selection.goal = SelectionGoal::None;
12521 }
12522 if is_first {
12523 is_first = false;
12524 } else {
12525 text += "\n";
12526 }
12527 let mut len = 0;
12528 for chunk in buffer.text_for_range(selection.start..selection.end) {
12529 text.push_str(chunk);
12530 len += chunk.len();
12531 }
12532 clipboard_selections.push(ClipboardSelection {
12533 len,
12534 is_entire_line,
12535 first_line_indent: buffer
12536 .indent_size_for_line(MultiBufferRow(selection.start.row))
12537 .len,
12538 });
12539 }
12540 }
12541
12542 self.transact(window, cx, |this, window, cx| {
12543 this.change_selections(Default::default(), window, cx, |s| {
12544 s.select(selections);
12545 });
12546 this.insert("", window, cx);
12547 });
12548 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12549 }
12550
12551 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12552 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12553 let item = self.cut_common(true, window, cx);
12554 cx.write_to_clipboard(item);
12555 }
12556
12557 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12558 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12559 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12560 s.move_with(|snapshot, sel| {
12561 if sel.is_empty() {
12562 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12563 }
12564 if sel.is_empty() {
12565 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12566 }
12567 });
12568 });
12569 let item = self.cut_common(false, window, cx);
12570 cx.set_global(KillRing(item))
12571 }
12572
12573 pub fn kill_ring_yank(
12574 &mut self,
12575 _: &KillRingYank,
12576 window: &mut Window,
12577 cx: &mut Context<Self>,
12578 ) {
12579 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12580 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12581 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12582 (kill_ring.text().to_string(), kill_ring.metadata_json())
12583 } else {
12584 return;
12585 }
12586 } else {
12587 return;
12588 };
12589 self.do_paste(&text, metadata, false, window, cx);
12590 }
12591
12592 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12593 self.do_copy(true, cx);
12594 }
12595
12596 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12597 self.do_copy(false, cx);
12598 }
12599
12600 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12601 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12602 let buffer = self.buffer.read(cx).read(cx);
12603 let mut text = String::new();
12604
12605 let mut clipboard_selections = Vec::with_capacity(selections.len());
12606 {
12607 let max_point = buffer.max_point();
12608 let mut is_first = true;
12609 for selection in &selections {
12610 let mut start = selection.start;
12611 let mut end = selection.end;
12612 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12613 let mut add_trailing_newline = false;
12614 if is_entire_line {
12615 start = Point::new(start.row, 0);
12616 let next_line_start = Point::new(end.row + 1, 0);
12617 if next_line_start <= max_point {
12618 end = next_line_start;
12619 } else {
12620 // We're on the last line without a trailing newline.
12621 // Copy to the end of the line and add a newline afterwards.
12622 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12623 add_trailing_newline = true;
12624 }
12625 }
12626
12627 let mut trimmed_selections = Vec::new();
12628 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12629 let row = MultiBufferRow(start.row);
12630 let first_indent = buffer.indent_size_for_line(row);
12631 if first_indent.len == 0 || start.column > first_indent.len {
12632 trimmed_selections.push(start..end);
12633 } else {
12634 trimmed_selections.push(
12635 Point::new(row.0, first_indent.len)
12636 ..Point::new(row.0, buffer.line_len(row)),
12637 );
12638 for row in start.row + 1..=end.row {
12639 let mut line_len = buffer.line_len(MultiBufferRow(row));
12640 if row == end.row {
12641 line_len = end.column;
12642 }
12643 if line_len == 0 {
12644 trimmed_selections
12645 .push(Point::new(row, 0)..Point::new(row, line_len));
12646 continue;
12647 }
12648 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12649 if row_indent_size.len >= first_indent.len {
12650 trimmed_selections.push(
12651 Point::new(row, first_indent.len)..Point::new(row, line_len),
12652 );
12653 } else {
12654 trimmed_selections.clear();
12655 trimmed_selections.push(start..end);
12656 break;
12657 }
12658 }
12659 }
12660 } else {
12661 trimmed_selections.push(start..end);
12662 }
12663
12664 for trimmed_range in trimmed_selections {
12665 if is_first {
12666 is_first = false;
12667 } else {
12668 text += "\n";
12669 }
12670 let mut len = 0;
12671 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12672 text.push_str(chunk);
12673 len += chunk.len();
12674 }
12675 if add_trailing_newline {
12676 text.push('\n');
12677 len += 1;
12678 }
12679 clipboard_selections.push(ClipboardSelection {
12680 len,
12681 is_entire_line,
12682 first_line_indent: buffer
12683 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12684 .len,
12685 });
12686 }
12687 }
12688 }
12689
12690 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12691 text,
12692 clipboard_selections,
12693 ));
12694 }
12695
12696 pub fn do_paste(
12697 &mut self,
12698 text: &String,
12699 clipboard_selections: Option<Vec<ClipboardSelection>>,
12700 handle_entire_lines: bool,
12701 window: &mut Window,
12702 cx: &mut Context<Self>,
12703 ) {
12704 if self.read_only(cx) {
12705 return;
12706 }
12707
12708 let clipboard_text = Cow::Borrowed(text.as_str());
12709
12710 self.transact(window, cx, |this, window, cx| {
12711 let had_active_edit_prediction = this.has_active_edit_prediction();
12712 let display_map = this.display_snapshot(cx);
12713 let old_selections = this.selections.all::<usize>(&display_map);
12714 let cursor_offset = this.selections.last::<usize>(&display_map).head();
12715
12716 if let Some(mut clipboard_selections) = clipboard_selections {
12717 let all_selections_were_entire_line =
12718 clipboard_selections.iter().all(|s| s.is_entire_line);
12719 let first_selection_indent_column =
12720 clipboard_selections.first().map(|s| s.first_line_indent);
12721 if clipboard_selections.len() != old_selections.len() {
12722 clipboard_selections.drain(..);
12723 }
12724 let mut auto_indent_on_paste = true;
12725
12726 this.buffer.update(cx, |buffer, cx| {
12727 let snapshot = buffer.read(cx);
12728 auto_indent_on_paste = snapshot
12729 .language_settings_at(cursor_offset, cx)
12730 .auto_indent_on_paste;
12731
12732 let mut start_offset = 0;
12733 let mut edits = Vec::new();
12734 let mut original_indent_columns = Vec::new();
12735 for (ix, selection) in old_selections.iter().enumerate() {
12736 let to_insert;
12737 let entire_line;
12738 let original_indent_column;
12739 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12740 let end_offset = start_offset + clipboard_selection.len;
12741 to_insert = &clipboard_text[start_offset..end_offset];
12742 entire_line = clipboard_selection.is_entire_line;
12743 start_offset = end_offset + 1;
12744 original_indent_column = Some(clipboard_selection.first_line_indent);
12745 } else {
12746 to_insert = &*clipboard_text;
12747 entire_line = all_selections_were_entire_line;
12748 original_indent_column = first_selection_indent_column
12749 }
12750
12751 let (range, to_insert) =
12752 if selection.is_empty() && handle_entire_lines && entire_line {
12753 // If the corresponding selection was empty when this slice of the
12754 // clipboard text was written, then the entire line containing the
12755 // selection was copied. If this selection is also currently empty,
12756 // then paste the line before the current line of the buffer.
12757 let column = selection.start.to_point(&snapshot).column as usize;
12758 let line_start = selection.start - column;
12759 (line_start..line_start, Cow::Borrowed(to_insert))
12760 } else {
12761 let language = snapshot.language_at(selection.head());
12762 let range = selection.range();
12763 if let Some(language) = language
12764 && language.name() == "Markdown".into()
12765 {
12766 edit_for_markdown_paste(
12767 &snapshot,
12768 range,
12769 to_insert,
12770 url::Url::parse(to_insert).ok(),
12771 )
12772 } else {
12773 (range, Cow::Borrowed(to_insert))
12774 }
12775 };
12776
12777 edits.push((range, to_insert));
12778 original_indent_columns.push(original_indent_column);
12779 }
12780 drop(snapshot);
12781
12782 buffer.edit(
12783 edits,
12784 if auto_indent_on_paste {
12785 Some(AutoindentMode::Block {
12786 original_indent_columns,
12787 })
12788 } else {
12789 None
12790 },
12791 cx,
12792 );
12793 });
12794
12795 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12796 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12797 } else {
12798 let url = url::Url::parse(&clipboard_text).ok();
12799
12800 let auto_indent_mode = if !clipboard_text.is_empty() {
12801 Some(AutoindentMode::Block {
12802 original_indent_columns: Vec::new(),
12803 })
12804 } else {
12805 None
12806 };
12807
12808 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12809 let snapshot = buffer.snapshot(cx);
12810
12811 let anchors = old_selections
12812 .iter()
12813 .map(|s| {
12814 let anchor = snapshot.anchor_after(s.head());
12815 s.map(|_| anchor)
12816 })
12817 .collect::<Vec<_>>();
12818
12819 let mut edits = Vec::new();
12820
12821 for selection in old_selections.iter() {
12822 let language = snapshot.language_at(selection.head());
12823 let range = selection.range();
12824
12825 let (edit_range, edit_text) = if let Some(language) = language
12826 && language.name() == "Markdown".into()
12827 {
12828 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12829 } else {
12830 (range, clipboard_text.clone())
12831 };
12832
12833 edits.push((edit_range, edit_text));
12834 }
12835
12836 drop(snapshot);
12837 buffer.edit(edits, auto_indent_mode, cx);
12838
12839 anchors
12840 });
12841
12842 this.change_selections(Default::default(), window, cx, |s| {
12843 s.select_anchors(selection_anchors);
12844 });
12845 }
12846
12847 let trigger_in_words =
12848 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12849
12850 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12851 });
12852 }
12853
12854 pub fn diff_clipboard_with_selection(
12855 &mut self,
12856 _: &DiffClipboardWithSelection,
12857 window: &mut Window,
12858 cx: &mut Context<Self>,
12859 ) {
12860 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
12861
12862 if selections.is_empty() {
12863 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12864 return;
12865 };
12866
12867 let clipboard_text = match cx.read_from_clipboard() {
12868 Some(item) => match item.entries().first() {
12869 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12870 _ => None,
12871 },
12872 None => None,
12873 };
12874
12875 let Some(clipboard_text) = clipboard_text else {
12876 log::warn!("Clipboard doesn't contain text.");
12877 return;
12878 };
12879
12880 window.dispatch_action(
12881 Box::new(DiffClipboardWithSelectionData {
12882 clipboard_text,
12883 editor: cx.entity(),
12884 }),
12885 cx,
12886 );
12887 }
12888
12889 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12890 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12891 if let Some(item) = cx.read_from_clipboard() {
12892 let entries = item.entries();
12893
12894 match entries.first() {
12895 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12896 // of all the pasted entries.
12897 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12898 .do_paste(
12899 clipboard_string.text(),
12900 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12901 true,
12902 window,
12903 cx,
12904 ),
12905 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12906 }
12907 }
12908 }
12909
12910 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12911 if self.read_only(cx) {
12912 return;
12913 }
12914
12915 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12916
12917 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12918 if let Some((selections, _)) =
12919 self.selection_history.transaction(transaction_id).cloned()
12920 {
12921 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12922 s.select_anchors(selections.to_vec());
12923 });
12924 } else {
12925 log::error!(
12926 "No entry in selection_history found for undo. \
12927 This may correspond to a bug where undo does not update the selection. \
12928 If this is occurring, please add details to \
12929 https://github.com/zed-industries/zed/issues/22692"
12930 );
12931 }
12932 self.request_autoscroll(Autoscroll::fit(), cx);
12933 self.unmark_text(window, cx);
12934 self.refresh_edit_prediction(true, false, window, cx);
12935 cx.emit(EditorEvent::Edited { transaction_id });
12936 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12937 }
12938 }
12939
12940 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12941 if self.read_only(cx) {
12942 return;
12943 }
12944
12945 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12946
12947 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12948 if let Some((_, Some(selections))) =
12949 self.selection_history.transaction(transaction_id).cloned()
12950 {
12951 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12952 s.select_anchors(selections.to_vec());
12953 });
12954 } else {
12955 log::error!(
12956 "No entry in selection_history found for redo. \
12957 This may correspond to a bug where undo does not update the selection. \
12958 If this is occurring, please add details to \
12959 https://github.com/zed-industries/zed/issues/22692"
12960 );
12961 }
12962 self.request_autoscroll(Autoscroll::fit(), cx);
12963 self.unmark_text(window, cx);
12964 self.refresh_edit_prediction(true, false, window, cx);
12965 cx.emit(EditorEvent::Edited { transaction_id });
12966 }
12967 }
12968
12969 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12970 self.buffer
12971 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12972 }
12973
12974 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12975 self.buffer
12976 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12977 }
12978
12979 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12980 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12981 self.change_selections(Default::default(), window, cx, |s| {
12982 s.move_with(|map, selection| {
12983 let cursor = if selection.is_empty() {
12984 movement::left(map, selection.start)
12985 } else {
12986 selection.start
12987 };
12988 selection.collapse_to(cursor, SelectionGoal::None);
12989 });
12990 })
12991 }
12992
12993 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12994 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12995 self.change_selections(Default::default(), window, cx, |s| {
12996 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12997 })
12998 }
12999
13000 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13001 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13002 self.change_selections(Default::default(), window, cx, |s| {
13003 s.move_with(|map, selection| {
13004 let cursor = if selection.is_empty() {
13005 movement::right(map, selection.end)
13006 } else {
13007 selection.end
13008 };
13009 selection.collapse_to(cursor, SelectionGoal::None)
13010 });
13011 })
13012 }
13013
13014 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13015 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13016 self.change_selections(Default::default(), window, cx, |s| {
13017 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13018 });
13019 }
13020
13021 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13022 if self.take_rename(true, window, cx).is_some() {
13023 return;
13024 }
13025
13026 if self.mode.is_single_line() {
13027 cx.propagate();
13028 return;
13029 }
13030
13031 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13032
13033 let text_layout_details = &self.text_layout_details(window);
13034 let selection_count = self.selections.count();
13035 let first_selection = self.selections.first_anchor();
13036
13037 self.change_selections(Default::default(), window, cx, |s| {
13038 s.move_with(|map, selection| {
13039 if !selection.is_empty() {
13040 selection.goal = SelectionGoal::None;
13041 }
13042 let (cursor, goal) = movement::up(
13043 map,
13044 selection.start,
13045 selection.goal,
13046 false,
13047 text_layout_details,
13048 );
13049 selection.collapse_to(cursor, goal);
13050 });
13051 });
13052
13053 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13054 {
13055 cx.propagate();
13056 }
13057 }
13058
13059 pub fn move_up_by_lines(
13060 &mut self,
13061 action: &MoveUpByLines,
13062 window: &mut Window,
13063 cx: &mut Context<Self>,
13064 ) {
13065 if self.take_rename(true, window, cx).is_some() {
13066 return;
13067 }
13068
13069 if self.mode.is_single_line() {
13070 cx.propagate();
13071 return;
13072 }
13073
13074 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13075
13076 let text_layout_details = &self.text_layout_details(window);
13077
13078 self.change_selections(Default::default(), window, cx, |s| {
13079 s.move_with(|map, selection| {
13080 if !selection.is_empty() {
13081 selection.goal = SelectionGoal::None;
13082 }
13083 let (cursor, goal) = movement::up_by_rows(
13084 map,
13085 selection.start,
13086 action.lines,
13087 selection.goal,
13088 false,
13089 text_layout_details,
13090 );
13091 selection.collapse_to(cursor, goal);
13092 });
13093 })
13094 }
13095
13096 pub fn move_down_by_lines(
13097 &mut self,
13098 action: &MoveDownByLines,
13099 window: &mut Window,
13100 cx: &mut Context<Self>,
13101 ) {
13102 if self.take_rename(true, window, cx).is_some() {
13103 return;
13104 }
13105
13106 if self.mode.is_single_line() {
13107 cx.propagate();
13108 return;
13109 }
13110
13111 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13112
13113 let text_layout_details = &self.text_layout_details(window);
13114
13115 self.change_selections(Default::default(), window, cx, |s| {
13116 s.move_with(|map, selection| {
13117 if !selection.is_empty() {
13118 selection.goal = SelectionGoal::None;
13119 }
13120 let (cursor, goal) = movement::down_by_rows(
13121 map,
13122 selection.start,
13123 action.lines,
13124 selection.goal,
13125 false,
13126 text_layout_details,
13127 );
13128 selection.collapse_to(cursor, goal);
13129 });
13130 })
13131 }
13132
13133 pub fn select_down_by_lines(
13134 &mut self,
13135 action: &SelectDownByLines,
13136 window: &mut Window,
13137 cx: &mut Context<Self>,
13138 ) {
13139 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13140 let text_layout_details = &self.text_layout_details(window);
13141 self.change_selections(Default::default(), window, cx, |s| {
13142 s.move_heads_with(|map, head, goal| {
13143 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13144 })
13145 })
13146 }
13147
13148 pub fn select_up_by_lines(
13149 &mut self,
13150 action: &SelectUpByLines,
13151 window: &mut Window,
13152 cx: &mut Context<Self>,
13153 ) {
13154 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13155 let text_layout_details = &self.text_layout_details(window);
13156 self.change_selections(Default::default(), window, cx, |s| {
13157 s.move_heads_with(|map, head, goal| {
13158 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13159 })
13160 })
13161 }
13162
13163 pub fn select_page_up(
13164 &mut self,
13165 _: &SelectPageUp,
13166 window: &mut Window,
13167 cx: &mut Context<Self>,
13168 ) {
13169 let Some(row_count) = self.visible_row_count() else {
13170 return;
13171 };
13172
13173 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13174
13175 let text_layout_details = &self.text_layout_details(window);
13176
13177 self.change_selections(Default::default(), window, cx, |s| {
13178 s.move_heads_with(|map, head, goal| {
13179 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13180 })
13181 })
13182 }
13183
13184 pub fn move_page_up(
13185 &mut self,
13186 action: &MovePageUp,
13187 window: &mut Window,
13188 cx: &mut Context<Self>,
13189 ) {
13190 if self.take_rename(true, window, cx).is_some() {
13191 return;
13192 }
13193
13194 if self
13195 .context_menu
13196 .borrow_mut()
13197 .as_mut()
13198 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13199 .unwrap_or(false)
13200 {
13201 return;
13202 }
13203
13204 if matches!(self.mode, EditorMode::SingleLine) {
13205 cx.propagate();
13206 return;
13207 }
13208
13209 let Some(row_count) = self.visible_row_count() else {
13210 return;
13211 };
13212
13213 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13214
13215 let effects = if action.center_cursor {
13216 SelectionEffects::scroll(Autoscroll::center())
13217 } else {
13218 SelectionEffects::default()
13219 };
13220
13221 let text_layout_details = &self.text_layout_details(window);
13222
13223 self.change_selections(effects, window, cx, |s| {
13224 s.move_with(|map, selection| {
13225 if !selection.is_empty() {
13226 selection.goal = SelectionGoal::None;
13227 }
13228 let (cursor, goal) = movement::up_by_rows(
13229 map,
13230 selection.end,
13231 row_count,
13232 selection.goal,
13233 false,
13234 text_layout_details,
13235 );
13236 selection.collapse_to(cursor, goal);
13237 });
13238 });
13239 }
13240
13241 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13242 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13243 let text_layout_details = &self.text_layout_details(window);
13244 self.change_selections(Default::default(), window, cx, |s| {
13245 s.move_heads_with(|map, head, goal| {
13246 movement::up(map, head, goal, false, text_layout_details)
13247 })
13248 })
13249 }
13250
13251 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13252 self.take_rename(true, window, cx);
13253
13254 if self.mode.is_single_line() {
13255 cx.propagate();
13256 return;
13257 }
13258
13259 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13260
13261 let text_layout_details = &self.text_layout_details(window);
13262 let selection_count = self.selections.count();
13263 let first_selection = self.selections.first_anchor();
13264
13265 self.change_selections(Default::default(), window, cx, |s| {
13266 s.move_with(|map, selection| {
13267 if !selection.is_empty() {
13268 selection.goal = SelectionGoal::None;
13269 }
13270 let (cursor, goal) = movement::down(
13271 map,
13272 selection.end,
13273 selection.goal,
13274 false,
13275 text_layout_details,
13276 );
13277 selection.collapse_to(cursor, goal);
13278 });
13279 });
13280
13281 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13282 {
13283 cx.propagate();
13284 }
13285 }
13286
13287 pub fn select_page_down(
13288 &mut self,
13289 _: &SelectPageDown,
13290 window: &mut Window,
13291 cx: &mut Context<Self>,
13292 ) {
13293 let Some(row_count) = self.visible_row_count() else {
13294 return;
13295 };
13296
13297 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13298
13299 let text_layout_details = &self.text_layout_details(window);
13300
13301 self.change_selections(Default::default(), window, cx, |s| {
13302 s.move_heads_with(|map, head, goal| {
13303 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13304 })
13305 })
13306 }
13307
13308 pub fn move_page_down(
13309 &mut self,
13310 action: &MovePageDown,
13311 window: &mut Window,
13312 cx: &mut Context<Self>,
13313 ) {
13314 if self.take_rename(true, window, cx).is_some() {
13315 return;
13316 }
13317
13318 if self
13319 .context_menu
13320 .borrow_mut()
13321 .as_mut()
13322 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13323 .unwrap_or(false)
13324 {
13325 return;
13326 }
13327
13328 if matches!(self.mode, EditorMode::SingleLine) {
13329 cx.propagate();
13330 return;
13331 }
13332
13333 let Some(row_count) = self.visible_row_count() else {
13334 return;
13335 };
13336
13337 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13338
13339 let effects = if action.center_cursor {
13340 SelectionEffects::scroll(Autoscroll::center())
13341 } else {
13342 SelectionEffects::default()
13343 };
13344
13345 let text_layout_details = &self.text_layout_details(window);
13346 self.change_selections(effects, window, cx, |s| {
13347 s.move_with(|map, selection| {
13348 if !selection.is_empty() {
13349 selection.goal = SelectionGoal::None;
13350 }
13351 let (cursor, goal) = movement::down_by_rows(
13352 map,
13353 selection.end,
13354 row_count,
13355 selection.goal,
13356 false,
13357 text_layout_details,
13358 );
13359 selection.collapse_to(cursor, goal);
13360 });
13361 });
13362 }
13363
13364 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13365 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13366 let text_layout_details = &self.text_layout_details(window);
13367 self.change_selections(Default::default(), window, cx, |s| {
13368 s.move_heads_with(|map, head, goal| {
13369 movement::down(map, head, goal, false, text_layout_details)
13370 })
13371 });
13372 }
13373
13374 pub fn context_menu_first(
13375 &mut self,
13376 _: &ContextMenuFirst,
13377 window: &mut Window,
13378 cx: &mut Context<Self>,
13379 ) {
13380 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13381 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13382 }
13383 }
13384
13385 pub fn context_menu_prev(
13386 &mut self,
13387 _: &ContextMenuPrevious,
13388 window: &mut Window,
13389 cx: &mut Context<Self>,
13390 ) {
13391 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13392 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13393 }
13394 }
13395
13396 pub fn context_menu_next(
13397 &mut self,
13398 _: &ContextMenuNext,
13399 window: &mut Window,
13400 cx: &mut Context<Self>,
13401 ) {
13402 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13403 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13404 }
13405 }
13406
13407 pub fn context_menu_last(
13408 &mut self,
13409 _: &ContextMenuLast,
13410 window: &mut Window,
13411 cx: &mut Context<Self>,
13412 ) {
13413 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13414 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13415 }
13416 }
13417
13418 pub fn signature_help_prev(
13419 &mut self,
13420 _: &SignatureHelpPrevious,
13421 _: &mut Window,
13422 cx: &mut Context<Self>,
13423 ) {
13424 if let Some(popover) = self.signature_help_state.popover_mut() {
13425 if popover.current_signature == 0 {
13426 popover.current_signature = popover.signatures.len() - 1;
13427 } else {
13428 popover.current_signature -= 1;
13429 }
13430 cx.notify();
13431 }
13432 }
13433
13434 pub fn signature_help_next(
13435 &mut self,
13436 _: &SignatureHelpNext,
13437 _: &mut Window,
13438 cx: &mut Context<Self>,
13439 ) {
13440 if let Some(popover) = self.signature_help_state.popover_mut() {
13441 if popover.current_signature + 1 == popover.signatures.len() {
13442 popover.current_signature = 0;
13443 } else {
13444 popover.current_signature += 1;
13445 }
13446 cx.notify();
13447 }
13448 }
13449
13450 pub fn move_to_previous_word_start(
13451 &mut self,
13452 _: &MoveToPreviousWordStart,
13453 window: &mut Window,
13454 cx: &mut Context<Self>,
13455 ) {
13456 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13457 self.change_selections(Default::default(), window, cx, |s| {
13458 s.move_cursors_with(|map, head, _| {
13459 (
13460 movement::previous_word_start(map, head),
13461 SelectionGoal::None,
13462 )
13463 });
13464 })
13465 }
13466
13467 pub fn move_to_previous_subword_start(
13468 &mut self,
13469 _: &MoveToPreviousSubwordStart,
13470 window: &mut Window,
13471 cx: &mut Context<Self>,
13472 ) {
13473 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13474 self.change_selections(Default::default(), window, cx, |s| {
13475 s.move_cursors_with(|map, head, _| {
13476 (
13477 movement::previous_subword_start(map, head),
13478 SelectionGoal::None,
13479 )
13480 });
13481 })
13482 }
13483
13484 pub fn select_to_previous_word_start(
13485 &mut self,
13486 _: &SelectToPreviousWordStart,
13487 window: &mut Window,
13488 cx: &mut Context<Self>,
13489 ) {
13490 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13491 self.change_selections(Default::default(), window, cx, |s| {
13492 s.move_heads_with(|map, head, _| {
13493 (
13494 movement::previous_word_start(map, head),
13495 SelectionGoal::None,
13496 )
13497 });
13498 })
13499 }
13500
13501 pub fn select_to_previous_subword_start(
13502 &mut self,
13503 _: &SelectToPreviousSubwordStart,
13504 window: &mut Window,
13505 cx: &mut Context<Self>,
13506 ) {
13507 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13508 self.change_selections(Default::default(), window, cx, |s| {
13509 s.move_heads_with(|map, head, _| {
13510 (
13511 movement::previous_subword_start(map, head),
13512 SelectionGoal::None,
13513 )
13514 });
13515 })
13516 }
13517
13518 pub fn delete_to_previous_word_start(
13519 &mut self,
13520 action: &DeleteToPreviousWordStart,
13521 window: &mut Window,
13522 cx: &mut Context<Self>,
13523 ) {
13524 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13525 self.transact(window, cx, |this, window, cx| {
13526 this.select_autoclose_pair(window, cx);
13527 this.change_selections(Default::default(), window, cx, |s| {
13528 s.move_with(|map, selection| {
13529 if selection.is_empty() {
13530 let mut cursor = if action.ignore_newlines {
13531 movement::previous_word_start(map, selection.head())
13532 } else {
13533 movement::previous_word_start_or_newline(map, selection.head())
13534 };
13535 cursor = movement::adjust_greedy_deletion(
13536 map,
13537 selection.head(),
13538 cursor,
13539 action.ignore_brackets,
13540 );
13541 selection.set_head(cursor, SelectionGoal::None);
13542 }
13543 });
13544 });
13545 this.insert("", window, cx);
13546 });
13547 }
13548
13549 pub fn delete_to_previous_subword_start(
13550 &mut self,
13551 _: &DeleteToPreviousSubwordStart,
13552 window: &mut Window,
13553 cx: &mut Context<Self>,
13554 ) {
13555 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13556 self.transact(window, cx, |this, window, cx| {
13557 this.select_autoclose_pair(window, cx);
13558 this.change_selections(Default::default(), window, cx, |s| {
13559 s.move_with(|map, selection| {
13560 if selection.is_empty() {
13561 let mut cursor = movement::previous_subword_start(map, selection.head());
13562 cursor =
13563 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13564 selection.set_head(cursor, SelectionGoal::None);
13565 }
13566 });
13567 });
13568 this.insert("", window, cx);
13569 });
13570 }
13571
13572 pub fn move_to_next_word_end(
13573 &mut self,
13574 _: &MoveToNextWordEnd,
13575 window: &mut Window,
13576 cx: &mut Context<Self>,
13577 ) {
13578 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13579 self.change_selections(Default::default(), window, cx, |s| {
13580 s.move_cursors_with(|map, head, _| {
13581 (movement::next_word_end(map, head), SelectionGoal::None)
13582 });
13583 })
13584 }
13585
13586 pub fn move_to_next_subword_end(
13587 &mut self,
13588 _: &MoveToNextSubwordEnd,
13589 window: &mut Window,
13590 cx: &mut Context<Self>,
13591 ) {
13592 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13593 self.change_selections(Default::default(), window, cx, |s| {
13594 s.move_cursors_with(|map, head, _| {
13595 (movement::next_subword_end(map, head), SelectionGoal::None)
13596 });
13597 })
13598 }
13599
13600 pub fn select_to_next_word_end(
13601 &mut self,
13602 _: &SelectToNextWordEnd,
13603 window: &mut Window,
13604 cx: &mut Context<Self>,
13605 ) {
13606 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13607 self.change_selections(Default::default(), window, cx, |s| {
13608 s.move_heads_with(|map, head, _| {
13609 (movement::next_word_end(map, head), SelectionGoal::None)
13610 });
13611 })
13612 }
13613
13614 pub fn select_to_next_subword_end(
13615 &mut self,
13616 _: &SelectToNextSubwordEnd,
13617 window: &mut Window,
13618 cx: &mut Context<Self>,
13619 ) {
13620 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13621 self.change_selections(Default::default(), window, cx, |s| {
13622 s.move_heads_with(|map, head, _| {
13623 (movement::next_subword_end(map, head), SelectionGoal::None)
13624 });
13625 })
13626 }
13627
13628 pub fn delete_to_next_word_end(
13629 &mut self,
13630 action: &DeleteToNextWordEnd,
13631 window: &mut Window,
13632 cx: &mut Context<Self>,
13633 ) {
13634 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13635 self.transact(window, cx, |this, window, cx| {
13636 this.change_selections(Default::default(), window, cx, |s| {
13637 s.move_with(|map, selection| {
13638 if selection.is_empty() {
13639 let mut cursor = if action.ignore_newlines {
13640 movement::next_word_end(map, selection.head())
13641 } else {
13642 movement::next_word_end_or_newline(map, selection.head())
13643 };
13644 cursor = movement::adjust_greedy_deletion(
13645 map,
13646 selection.head(),
13647 cursor,
13648 action.ignore_brackets,
13649 );
13650 selection.set_head(cursor, SelectionGoal::None);
13651 }
13652 });
13653 });
13654 this.insert("", window, cx);
13655 });
13656 }
13657
13658 pub fn delete_to_next_subword_end(
13659 &mut self,
13660 _: &DeleteToNextSubwordEnd,
13661 window: &mut Window,
13662 cx: &mut Context<Self>,
13663 ) {
13664 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13665 self.transact(window, cx, |this, window, cx| {
13666 this.change_selections(Default::default(), window, cx, |s| {
13667 s.move_with(|map, selection| {
13668 if selection.is_empty() {
13669 let mut cursor = movement::next_subword_end(map, selection.head());
13670 cursor =
13671 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13672 selection.set_head(cursor, SelectionGoal::None);
13673 }
13674 });
13675 });
13676 this.insert("", window, cx);
13677 });
13678 }
13679
13680 pub fn move_to_beginning_of_line(
13681 &mut self,
13682 action: &MoveToBeginningOfLine,
13683 window: &mut Window,
13684 cx: &mut Context<Self>,
13685 ) {
13686 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13687 self.change_selections(Default::default(), window, cx, |s| {
13688 s.move_cursors_with(|map, head, _| {
13689 (
13690 movement::indented_line_beginning(
13691 map,
13692 head,
13693 action.stop_at_soft_wraps,
13694 action.stop_at_indent,
13695 ),
13696 SelectionGoal::None,
13697 )
13698 });
13699 })
13700 }
13701
13702 pub fn select_to_beginning_of_line(
13703 &mut self,
13704 action: &SelectToBeginningOfLine,
13705 window: &mut Window,
13706 cx: &mut Context<Self>,
13707 ) {
13708 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13709 self.change_selections(Default::default(), window, cx, |s| {
13710 s.move_heads_with(|map, head, _| {
13711 (
13712 movement::indented_line_beginning(
13713 map,
13714 head,
13715 action.stop_at_soft_wraps,
13716 action.stop_at_indent,
13717 ),
13718 SelectionGoal::None,
13719 )
13720 });
13721 });
13722 }
13723
13724 pub fn delete_to_beginning_of_line(
13725 &mut self,
13726 action: &DeleteToBeginningOfLine,
13727 window: &mut Window,
13728 cx: &mut Context<Self>,
13729 ) {
13730 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13731 self.transact(window, cx, |this, window, cx| {
13732 this.change_selections(Default::default(), window, cx, |s| {
13733 s.move_with(|_, selection| {
13734 selection.reversed = true;
13735 });
13736 });
13737
13738 this.select_to_beginning_of_line(
13739 &SelectToBeginningOfLine {
13740 stop_at_soft_wraps: false,
13741 stop_at_indent: action.stop_at_indent,
13742 },
13743 window,
13744 cx,
13745 );
13746 this.backspace(&Backspace, window, cx);
13747 });
13748 }
13749
13750 pub fn move_to_end_of_line(
13751 &mut self,
13752 action: &MoveToEndOfLine,
13753 window: &mut Window,
13754 cx: &mut Context<Self>,
13755 ) {
13756 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13757 self.change_selections(Default::default(), window, cx, |s| {
13758 s.move_cursors_with(|map, head, _| {
13759 (
13760 movement::line_end(map, head, action.stop_at_soft_wraps),
13761 SelectionGoal::None,
13762 )
13763 });
13764 })
13765 }
13766
13767 pub fn select_to_end_of_line(
13768 &mut self,
13769 action: &SelectToEndOfLine,
13770 window: &mut Window,
13771 cx: &mut Context<Self>,
13772 ) {
13773 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13774 self.change_selections(Default::default(), window, cx, |s| {
13775 s.move_heads_with(|map, head, _| {
13776 (
13777 movement::line_end(map, head, action.stop_at_soft_wraps),
13778 SelectionGoal::None,
13779 )
13780 });
13781 })
13782 }
13783
13784 pub fn delete_to_end_of_line(
13785 &mut self,
13786 _: &DeleteToEndOfLine,
13787 window: &mut Window,
13788 cx: &mut Context<Self>,
13789 ) {
13790 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13791 self.transact(window, cx, |this, window, cx| {
13792 this.select_to_end_of_line(
13793 &SelectToEndOfLine {
13794 stop_at_soft_wraps: false,
13795 },
13796 window,
13797 cx,
13798 );
13799 this.delete(&Delete, window, cx);
13800 });
13801 }
13802
13803 pub fn cut_to_end_of_line(
13804 &mut self,
13805 action: &CutToEndOfLine,
13806 window: &mut Window,
13807 cx: &mut Context<Self>,
13808 ) {
13809 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13810 self.transact(window, cx, |this, window, cx| {
13811 this.select_to_end_of_line(
13812 &SelectToEndOfLine {
13813 stop_at_soft_wraps: false,
13814 },
13815 window,
13816 cx,
13817 );
13818 if !action.stop_at_newlines {
13819 this.change_selections(Default::default(), window, cx, |s| {
13820 s.move_with(|_, sel| {
13821 if sel.is_empty() {
13822 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13823 }
13824 });
13825 });
13826 }
13827 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13828 let item = this.cut_common(false, window, cx);
13829 cx.write_to_clipboard(item);
13830 });
13831 }
13832
13833 pub fn move_to_start_of_paragraph(
13834 &mut self,
13835 _: &MoveToStartOfParagraph,
13836 window: &mut Window,
13837 cx: &mut Context<Self>,
13838 ) {
13839 if matches!(self.mode, EditorMode::SingleLine) {
13840 cx.propagate();
13841 return;
13842 }
13843 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13844 self.change_selections(Default::default(), window, cx, |s| {
13845 s.move_with(|map, selection| {
13846 selection.collapse_to(
13847 movement::start_of_paragraph(map, selection.head(), 1),
13848 SelectionGoal::None,
13849 )
13850 });
13851 })
13852 }
13853
13854 pub fn move_to_end_of_paragraph(
13855 &mut self,
13856 _: &MoveToEndOfParagraph,
13857 window: &mut Window,
13858 cx: &mut Context<Self>,
13859 ) {
13860 if matches!(self.mode, EditorMode::SingleLine) {
13861 cx.propagate();
13862 return;
13863 }
13864 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13865 self.change_selections(Default::default(), window, cx, |s| {
13866 s.move_with(|map, selection| {
13867 selection.collapse_to(
13868 movement::end_of_paragraph(map, selection.head(), 1),
13869 SelectionGoal::None,
13870 )
13871 });
13872 })
13873 }
13874
13875 pub fn select_to_start_of_paragraph(
13876 &mut self,
13877 _: &SelectToStartOfParagraph,
13878 window: &mut Window,
13879 cx: &mut Context<Self>,
13880 ) {
13881 if matches!(self.mode, EditorMode::SingleLine) {
13882 cx.propagate();
13883 return;
13884 }
13885 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13886 self.change_selections(Default::default(), window, cx, |s| {
13887 s.move_heads_with(|map, head, _| {
13888 (
13889 movement::start_of_paragraph(map, head, 1),
13890 SelectionGoal::None,
13891 )
13892 });
13893 })
13894 }
13895
13896 pub fn select_to_end_of_paragraph(
13897 &mut self,
13898 _: &SelectToEndOfParagraph,
13899 window: &mut Window,
13900 cx: &mut Context<Self>,
13901 ) {
13902 if matches!(self.mode, EditorMode::SingleLine) {
13903 cx.propagate();
13904 return;
13905 }
13906 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13907 self.change_selections(Default::default(), window, cx, |s| {
13908 s.move_heads_with(|map, head, _| {
13909 (
13910 movement::end_of_paragraph(map, head, 1),
13911 SelectionGoal::None,
13912 )
13913 });
13914 })
13915 }
13916
13917 pub fn move_to_start_of_excerpt(
13918 &mut self,
13919 _: &MoveToStartOfExcerpt,
13920 window: &mut Window,
13921 cx: &mut Context<Self>,
13922 ) {
13923 if matches!(self.mode, EditorMode::SingleLine) {
13924 cx.propagate();
13925 return;
13926 }
13927 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13928 self.change_selections(Default::default(), window, cx, |s| {
13929 s.move_with(|map, selection| {
13930 selection.collapse_to(
13931 movement::start_of_excerpt(
13932 map,
13933 selection.head(),
13934 workspace::searchable::Direction::Prev,
13935 ),
13936 SelectionGoal::None,
13937 )
13938 });
13939 })
13940 }
13941
13942 pub fn move_to_start_of_next_excerpt(
13943 &mut self,
13944 _: &MoveToStartOfNextExcerpt,
13945 window: &mut Window,
13946 cx: &mut Context<Self>,
13947 ) {
13948 if matches!(self.mode, EditorMode::SingleLine) {
13949 cx.propagate();
13950 return;
13951 }
13952
13953 self.change_selections(Default::default(), window, cx, |s| {
13954 s.move_with(|map, selection| {
13955 selection.collapse_to(
13956 movement::start_of_excerpt(
13957 map,
13958 selection.head(),
13959 workspace::searchable::Direction::Next,
13960 ),
13961 SelectionGoal::None,
13962 )
13963 });
13964 })
13965 }
13966
13967 pub fn move_to_end_of_excerpt(
13968 &mut self,
13969 _: &MoveToEndOfExcerpt,
13970 window: &mut Window,
13971 cx: &mut Context<Self>,
13972 ) {
13973 if matches!(self.mode, EditorMode::SingleLine) {
13974 cx.propagate();
13975 return;
13976 }
13977 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13978 self.change_selections(Default::default(), window, cx, |s| {
13979 s.move_with(|map, selection| {
13980 selection.collapse_to(
13981 movement::end_of_excerpt(
13982 map,
13983 selection.head(),
13984 workspace::searchable::Direction::Next,
13985 ),
13986 SelectionGoal::None,
13987 )
13988 });
13989 })
13990 }
13991
13992 pub fn move_to_end_of_previous_excerpt(
13993 &mut self,
13994 _: &MoveToEndOfPreviousExcerpt,
13995 window: &mut Window,
13996 cx: &mut Context<Self>,
13997 ) {
13998 if matches!(self.mode, EditorMode::SingleLine) {
13999 cx.propagate();
14000 return;
14001 }
14002 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14003 self.change_selections(Default::default(), window, cx, |s| {
14004 s.move_with(|map, selection| {
14005 selection.collapse_to(
14006 movement::end_of_excerpt(
14007 map,
14008 selection.head(),
14009 workspace::searchable::Direction::Prev,
14010 ),
14011 SelectionGoal::None,
14012 )
14013 });
14014 })
14015 }
14016
14017 pub fn select_to_start_of_excerpt(
14018 &mut self,
14019 _: &SelectToStartOfExcerpt,
14020 window: &mut Window,
14021 cx: &mut Context<Self>,
14022 ) {
14023 if matches!(self.mode, EditorMode::SingleLine) {
14024 cx.propagate();
14025 return;
14026 }
14027 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14028 self.change_selections(Default::default(), window, cx, |s| {
14029 s.move_heads_with(|map, head, _| {
14030 (
14031 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14032 SelectionGoal::None,
14033 )
14034 });
14035 })
14036 }
14037
14038 pub fn select_to_start_of_next_excerpt(
14039 &mut self,
14040 _: &SelectToStartOfNextExcerpt,
14041 window: &mut Window,
14042 cx: &mut Context<Self>,
14043 ) {
14044 if matches!(self.mode, EditorMode::SingleLine) {
14045 cx.propagate();
14046 return;
14047 }
14048 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14049 self.change_selections(Default::default(), window, cx, |s| {
14050 s.move_heads_with(|map, head, _| {
14051 (
14052 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14053 SelectionGoal::None,
14054 )
14055 });
14056 })
14057 }
14058
14059 pub fn select_to_end_of_excerpt(
14060 &mut self,
14061 _: &SelectToEndOfExcerpt,
14062 window: &mut Window,
14063 cx: &mut Context<Self>,
14064 ) {
14065 if matches!(self.mode, EditorMode::SingleLine) {
14066 cx.propagate();
14067 return;
14068 }
14069 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14070 self.change_selections(Default::default(), window, cx, |s| {
14071 s.move_heads_with(|map, head, _| {
14072 (
14073 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14074 SelectionGoal::None,
14075 )
14076 });
14077 })
14078 }
14079
14080 pub fn select_to_end_of_previous_excerpt(
14081 &mut self,
14082 _: &SelectToEndOfPreviousExcerpt,
14083 window: &mut Window,
14084 cx: &mut Context<Self>,
14085 ) {
14086 if matches!(self.mode, EditorMode::SingleLine) {
14087 cx.propagate();
14088 return;
14089 }
14090 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14091 self.change_selections(Default::default(), window, cx, |s| {
14092 s.move_heads_with(|map, head, _| {
14093 (
14094 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14095 SelectionGoal::None,
14096 )
14097 });
14098 })
14099 }
14100
14101 pub fn move_to_beginning(
14102 &mut self,
14103 _: &MoveToBeginning,
14104 window: &mut Window,
14105 cx: &mut Context<Self>,
14106 ) {
14107 if matches!(self.mode, EditorMode::SingleLine) {
14108 cx.propagate();
14109 return;
14110 }
14111 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14112 self.change_selections(Default::default(), window, cx, |s| {
14113 s.select_ranges(vec![0..0]);
14114 });
14115 }
14116
14117 pub fn select_to_beginning(
14118 &mut self,
14119 _: &SelectToBeginning,
14120 window: &mut Window,
14121 cx: &mut Context<Self>,
14122 ) {
14123 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14124 selection.set_head(Point::zero(), SelectionGoal::None);
14125 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14126 self.change_selections(Default::default(), window, cx, |s| {
14127 s.select(vec![selection]);
14128 });
14129 }
14130
14131 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14132 if matches!(self.mode, EditorMode::SingleLine) {
14133 cx.propagate();
14134 return;
14135 }
14136 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14137 let cursor = self.buffer.read(cx).read(cx).len();
14138 self.change_selections(Default::default(), window, cx, |s| {
14139 s.select_ranges(vec![cursor..cursor])
14140 });
14141 }
14142
14143 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14144 self.nav_history = nav_history;
14145 }
14146
14147 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14148 self.nav_history.as_ref()
14149 }
14150
14151 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14152 self.push_to_nav_history(
14153 self.selections.newest_anchor().head(),
14154 None,
14155 false,
14156 true,
14157 cx,
14158 );
14159 }
14160
14161 fn push_to_nav_history(
14162 &mut self,
14163 cursor_anchor: Anchor,
14164 new_position: Option<Point>,
14165 is_deactivate: bool,
14166 always: bool,
14167 cx: &mut Context<Self>,
14168 ) {
14169 if let Some(nav_history) = self.nav_history.as_mut() {
14170 let buffer = self.buffer.read(cx).read(cx);
14171 let cursor_position = cursor_anchor.to_point(&buffer);
14172 let scroll_state = self.scroll_manager.anchor();
14173 let scroll_top_row = scroll_state.top_row(&buffer);
14174 drop(buffer);
14175
14176 if let Some(new_position) = new_position {
14177 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14178 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14179 return;
14180 }
14181 }
14182
14183 nav_history.push(
14184 Some(NavigationData {
14185 cursor_anchor,
14186 cursor_position,
14187 scroll_anchor: scroll_state,
14188 scroll_top_row,
14189 }),
14190 cx,
14191 );
14192 cx.emit(EditorEvent::PushedToNavHistory {
14193 anchor: cursor_anchor,
14194 is_deactivate,
14195 })
14196 }
14197 }
14198
14199 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14200 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14201 let buffer = self.buffer.read(cx).snapshot(cx);
14202 let mut selection = self.selections.first::<usize>(&self.display_snapshot(cx));
14203 selection.set_head(buffer.len(), SelectionGoal::None);
14204 self.change_selections(Default::default(), window, cx, |s| {
14205 s.select(vec![selection]);
14206 });
14207 }
14208
14209 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14210 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14211 let end = self.buffer.read(cx).read(cx).len();
14212 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14213 s.select_ranges(vec![0..end]);
14214 });
14215 }
14216
14217 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14218 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14219 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14220 let mut selections = self.selections.all::<Point>(&display_map);
14221 let max_point = display_map.buffer_snapshot().max_point();
14222 for selection in &mut selections {
14223 let rows = selection.spanned_rows(true, &display_map);
14224 selection.start = Point::new(rows.start.0, 0);
14225 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14226 selection.reversed = false;
14227 }
14228 self.change_selections(Default::default(), window, cx, |s| {
14229 s.select(selections);
14230 });
14231 }
14232
14233 pub fn split_selection_into_lines(
14234 &mut self,
14235 action: &SplitSelectionIntoLines,
14236 window: &mut Window,
14237 cx: &mut Context<Self>,
14238 ) {
14239 let selections = self
14240 .selections
14241 .all::<Point>(&self.display_snapshot(cx))
14242 .into_iter()
14243 .map(|selection| selection.start..selection.end)
14244 .collect::<Vec<_>>();
14245 self.unfold_ranges(&selections, true, true, cx);
14246
14247 let mut new_selection_ranges = Vec::new();
14248 {
14249 let buffer = self.buffer.read(cx).read(cx);
14250 for selection in selections {
14251 for row in selection.start.row..selection.end.row {
14252 let line_start = Point::new(row, 0);
14253 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14254
14255 if action.keep_selections {
14256 // Keep the selection range for each line
14257 let selection_start = if row == selection.start.row {
14258 selection.start
14259 } else {
14260 line_start
14261 };
14262 new_selection_ranges.push(selection_start..line_end);
14263 } else {
14264 // Collapse to cursor at end of line
14265 new_selection_ranges.push(line_end..line_end);
14266 }
14267 }
14268
14269 let is_multiline_selection = selection.start.row != selection.end.row;
14270 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14271 // so this action feels more ergonomic when paired with other selection operations
14272 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14273 if !should_skip_last {
14274 if action.keep_selections {
14275 if is_multiline_selection {
14276 let line_start = Point::new(selection.end.row, 0);
14277 new_selection_ranges.push(line_start..selection.end);
14278 } else {
14279 new_selection_ranges.push(selection.start..selection.end);
14280 }
14281 } else {
14282 new_selection_ranges.push(selection.end..selection.end);
14283 }
14284 }
14285 }
14286 }
14287 self.change_selections(Default::default(), window, cx, |s| {
14288 s.select_ranges(new_selection_ranges);
14289 });
14290 }
14291
14292 pub fn add_selection_above(
14293 &mut self,
14294 action: &AddSelectionAbove,
14295 window: &mut Window,
14296 cx: &mut Context<Self>,
14297 ) {
14298 self.add_selection(true, action.skip_soft_wrap, window, cx);
14299 }
14300
14301 pub fn add_selection_below(
14302 &mut self,
14303 action: &AddSelectionBelow,
14304 window: &mut Window,
14305 cx: &mut Context<Self>,
14306 ) {
14307 self.add_selection(false, action.skip_soft_wrap, window, cx);
14308 }
14309
14310 fn add_selection(
14311 &mut self,
14312 above: bool,
14313 skip_soft_wrap: bool,
14314 window: &mut Window,
14315 cx: &mut Context<Self>,
14316 ) {
14317 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14318
14319 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14320 let all_selections = self.selections.all::<Point>(&display_map);
14321 let text_layout_details = self.text_layout_details(window);
14322
14323 let (mut columnar_selections, new_selections_to_columnarize) = {
14324 if let Some(state) = self.add_selections_state.as_ref() {
14325 let columnar_selection_ids: HashSet<_> = state
14326 .groups
14327 .iter()
14328 .flat_map(|group| group.stack.iter())
14329 .copied()
14330 .collect();
14331
14332 all_selections
14333 .into_iter()
14334 .partition(|s| columnar_selection_ids.contains(&s.id))
14335 } else {
14336 (Vec::new(), all_selections)
14337 }
14338 };
14339
14340 let mut state = self
14341 .add_selections_state
14342 .take()
14343 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14344
14345 for selection in new_selections_to_columnarize {
14346 let range = selection.display_range(&display_map).sorted();
14347 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14348 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14349 let positions = start_x.min(end_x)..start_x.max(end_x);
14350 let mut stack = Vec::new();
14351 for row in range.start.row().0..=range.end.row().0 {
14352 if let Some(selection) = self.selections.build_columnar_selection(
14353 &display_map,
14354 DisplayRow(row),
14355 &positions,
14356 selection.reversed,
14357 &text_layout_details,
14358 ) {
14359 stack.push(selection.id);
14360 columnar_selections.push(selection);
14361 }
14362 }
14363 if !stack.is_empty() {
14364 if above {
14365 stack.reverse();
14366 }
14367 state.groups.push(AddSelectionsGroup { above, stack });
14368 }
14369 }
14370
14371 let mut final_selections = Vec::new();
14372 let end_row = if above {
14373 DisplayRow(0)
14374 } else {
14375 display_map.max_point().row()
14376 };
14377
14378 let mut last_added_item_per_group = HashMap::default();
14379 for group in state.groups.iter_mut() {
14380 if let Some(last_id) = group.stack.last() {
14381 last_added_item_per_group.insert(*last_id, group);
14382 }
14383 }
14384
14385 for selection in columnar_selections {
14386 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14387 if above == group.above {
14388 let range = selection.display_range(&display_map).sorted();
14389 debug_assert_eq!(range.start.row(), range.end.row());
14390 let mut row = range.start.row();
14391 let positions =
14392 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14393 Pixels::from(start)..Pixels::from(end)
14394 } else {
14395 let start_x =
14396 display_map.x_for_display_point(range.start, &text_layout_details);
14397 let end_x =
14398 display_map.x_for_display_point(range.end, &text_layout_details);
14399 start_x.min(end_x)..start_x.max(end_x)
14400 };
14401
14402 let mut maybe_new_selection = None;
14403 let direction = if above { -1 } else { 1 };
14404
14405 while row != end_row {
14406 if skip_soft_wrap {
14407 row = display_map
14408 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14409 .row();
14410 } else if above {
14411 row.0 -= 1;
14412 } else {
14413 row.0 += 1;
14414 }
14415
14416 if let Some(new_selection) = self.selections.build_columnar_selection(
14417 &display_map,
14418 row,
14419 &positions,
14420 selection.reversed,
14421 &text_layout_details,
14422 ) {
14423 maybe_new_selection = Some(new_selection);
14424 break;
14425 }
14426 }
14427
14428 if let Some(new_selection) = maybe_new_selection {
14429 group.stack.push(new_selection.id);
14430 if above {
14431 final_selections.push(new_selection);
14432 final_selections.push(selection);
14433 } else {
14434 final_selections.push(selection);
14435 final_selections.push(new_selection);
14436 }
14437 } else {
14438 final_selections.push(selection);
14439 }
14440 } else {
14441 group.stack.pop();
14442 }
14443 } else {
14444 final_selections.push(selection);
14445 }
14446 }
14447
14448 self.change_selections(Default::default(), window, cx, |s| {
14449 s.select(final_selections);
14450 });
14451
14452 let final_selection_ids: HashSet<_> = self
14453 .selections
14454 .all::<Point>(&display_map)
14455 .iter()
14456 .map(|s| s.id)
14457 .collect();
14458 state.groups.retain_mut(|group| {
14459 // selections might get merged above so we remove invalid items from stacks
14460 group.stack.retain(|id| final_selection_ids.contains(id));
14461
14462 // single selection in stack can be treated as initial state
14463 group.stack.len() > 1
14464 });
14465
14466 if !state.groups.is_empty() {
14467 self.add_selections_state = Some(state);
14468 }
14469 }
14470
14471 fn select_match_ranges(
14472 &mut self,
14473 range: Range<usize>,
14474 reversed: bool,
14475 replace_newest: bool,
14476 auto_scroll: Option<Autoscroll>,
14477 window: &mut Window,
14478 cx: &mut Context<Editor>,
14479 ) {
14480 self.unfold_ranges(
14481 std::slice::from_ref(&range),
14482 false,
14483 auto_scroll.is_some(),
14484 cx,
14485 );
14486 let effects = if let Some(scroll) = auto_scroll {
14487 SelectionEffects::scroll(scroll)
14488 } else {
14489 SelectionEffects::no_scroll()
14490 };
14491 self.change_selections(effects, window, cx, |s| {
14492 if replace_newest {
14493 s.delete(s.newest_anchor().id);
14494 }
14495 if reversed {
14496 s.insert_range(range.end..range.start);
14497 } else {
14498 s.insert_range(range);
14499 }
14500 });
14501 }
14502
14503 pub fn select_next_match_internal(
14504 &mut self,
14505 display_map: &DisplaySnapshot,
14506 replace_newest: bool,
14507 autoscroll: Option<Autoscroll>,
14508 window: &mut Window,
14509 cx: &mut Context<Self>,
14510 ) -> Result<()> {
14511 let buffer = display_map.buffer_snapshot();
14512 let mut selections = self.selections.all::<usize>(&display_map);
14513 if let Some(mut select_next_state) = self.select_next_state.take() {
14514 let query = &select_next_state.query;
14515 if !select_next_state.done {
14516 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14517 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14518 let mut next_selected_range = None;
14519
14520 // Collect and sort selection ranges for efficient overlap checking
14521 let mut selection_ranges: Vec<_> = selections.iter().map(|s| s.range()).collect();
14522 selection_ranges.sort_by_key(|r| r.start);
14523
14524 let bytes_after_last_selection =
14525 buffer.bytes_in_range(last_selection.end..buffer.len());
14526 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14527 let query_matches = query
14528 .stream_find_iter(bytes_after_last_selection)
14529 .map(|result| (last_selection.end, result))
14530 .chain(
14531 query
14532 .stream_find_iter(bytes_before_first_selection)
14533 .map(|result| (0, result)),
14534 );
14535
14536 for (start_offset, query_match) in query_matches {
14537 let query_match = query_match.unwrap(); // can only fail due to I/O
14538 let offset_range =
14539 start_offset + query_match.start()..start_offset + query_match.end();
14540
14541 if !select_next_state.wordwise
14542 || (!buffer.is_inside_word(offset_range.start, None)
14543 && !buffer.is_inside_word(offset_range.end, None))
14544 {
14545 // Use binary search to check for overlap (O(log n))
14546 let overlaps = selection_ranges
14547 .binary_search_by(|range| {
14548 if range.end <= offset_range.start {
14549 std::cmp::Ordering::Less
14550 } else if range.start >= offset_range.end {
14551 std::cmp::Ordering::Greater
14552 } else {
14553 std::cmp::Ordering::Equal
14554 }
14555 })
14556 .is_ok();
14557
14558 if !overlaps {
14559 next_selected_range = Some(offset_range);
14560 break;
14561 }
14562 }
14563 }
14564
14565 if let Some(next_selected_range) = next_selected_range {
14566 self.select_match_ranges(
14567 next_selected_range,
14568 last_selection.reversed,
14569 replace_newest,
14570 autoscroll,
14571 window,
14572 cx,
14573 );
14574 } else {
14575 select_next_state.done = true;
14576 }
14577 }
14578
14579 self.select_next_state = Some(select_next_state);
14580 } else {
14581 let mut only_carets = true;
14582 let mut same_text_selected = true;
14583 let mut selected_text = None;
14584
14585 let mut selections_iter = selections.iter().peekable();
14586 while let Some(selection) = selections_iter.next() {
14587 if selection.start != selection.end {
14588 only_carets = false;
14589 }
14590
14591 if same_text_selected {
14592 if selected_text.is_none() {
14593 selected_text =
14594 Some(buffer.text_for_range(selection.range()).collect::<String>());
14595 }
14596
14597 if let Some(next_selection) = selections_iter.peek() {
14598 if next_selection.range().len() == selection.range().len() {
14599 let next_selected_text = buffer
14600 .text_for_range(next_selection.range())
14601 .collect::<String>();
14602 if Some(next_selected_text) != selected_text {
14603 same_text_selected = false;
14604 selected_text = None;
14605 }
14606 } else {
14607 same_text_selected = false;
14608 selected_text = None;
14609 }
14610 }
14611 }
14612 }
14613
14614 if only_carets {
14615 for selection in &mut selections {
14616 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14617 selection.start = word_range.start;
14618 selection.end = word_range.end;
14619 selection.goal = SelectionGoal::None;
14620 selection.reversed = false;
14621 self.select_match_ranges(
14622 selection.start..selection.end,
14623 selection.reversed,
14624 replace_newest,
14625 autoscroll,
14626 window,
14627 cx,
14628 );
14629 }
14630
14631 if selections.len() == 1 {
14632 let selection = selections
14633 .last()
14634 .expect("ensured that there's only one selection");
14635 let query = buffer
14636 .text_for_range(selection.start..selection.end)
14637 .collect::<String>();
14638 let is_empty = query.is_empty();
14639 let select_state = SelectNextState {
14640 query: AhoCorasick::new(&[query])?,
14641 wordwise: true,
14642 done: is_empty,
14643 };
14644 self.select_next_state = Some(select_state);
14645 } else {
14646 self.select_next_state = None;
14647 }
14648 } else if let Some(selected_text) = selected_text {
14649 self.select_next_state = Some(SelectNextState {
14650 query: AhoCorasick::new(&[selected_text])?,
14651 wordwise: false,
14652 done: false,
14653 });
14654 self.select_next_match_internal(
14655 display_map,
14656 replace_newest,
14657 autoscroll,
14658 window,
14659 cx,
14660 )?;
14661 }
14662 }
14663 Ok(())
14664 }
14665
14666 pub fn select_all_matches(
14667 &mut self,
14668 _action: &SelectAllMatches,
14669 window: &mut Window,
14670 cx: &mut Context<Self>,
14671 ) -> Result<()> {
14672 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14673
14674 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14675
14676 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14677 let Some(select_next_state) = self.select_next_state.as_mut() else {
14678 return Ok(());
14679 };
14680 if select_next_state.done {
14681 return Ok(());
14682 }
14683
14684 let mut new_selections = Vec::new();
14685
14686 let reversed = self.selections.oldest::<usize>(&display_map).reversed;
14687 let buffer = display_map.buffer_snapshot();
14688 let query_matches = select_next_state
14689 .query
14690 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14691
14692 for query_match in query_matches.into_iter() {
14693 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14694 let offset_range = if reversed {
14695 query_match.end()..query_match.start()
14696 } else {
14697 query_match.start()..query_match.end()
14698 };
14699
14700 if !select_next_state.wordwise
14701 || (!buffer.is_inside_word(offset_range.start, None)
14702 && !buffer.is_inside_word(offset_range.end, None))
14703 {
14704 new_selections.push(offset_range.start..offset_range.end);
14705 }
14706 }
14707
14708 select_next_state.done = true;
14709
14710 if new_selections.is_empty() {
14711 log::error!("bug: new_selections is empty in select_all_matches");
14712 return Ok(());
14713 }
14714
14715 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14716 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14717 selections.select_ranges(new_selections)
14718 });
14719
14720 Ok(())
14721 }
14722
14723 pub fn select_next(
14724 &mut self,
14725 action: &SelectNext,
14726 window: &mut Window,
14727 cx: &mut Context<Self>,
14728 ) -> Result<()> {
14729 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14730 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14731 self.select_next_match_internal(
14732 &display_map,
14733 action.replace_newest,
14734 Some(Autoscroll::newest()),
14735 window,
14736 cx,
14737 )?;
14738 Ok(())
14739 }
14740
14741 pub fn select_previous(
14742 &mut self,
14743 action: &SelectPrevious,
14744 window: &mut Window,
14745 cx: &mut Context<Self>,
14746 ) -> Result<()> {
14747 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14748 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14749 let buffer = display_map.buffer_snapshot();
14750 let mut selections = self.selections.all::<usize>(&display_map);
14751 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14752 let query = &select_prev_state.query;
14753 if !select_prev_state.done {
14754 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14755 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14756 let mut next_selected_range = None;
14757 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14758 let bytes_before_last_selection =
14759 buffer.reversed_bytes_in_range(0..last_selection.start);
14760 let bytes_after_first_selection =
14761 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14762 let query_matches = query
14763 .stream_find_iter(bytes_before_last_selection)
14764 .map(|result| (last_selection.start, result))
14765 .chain(
14766 query
14767 .stream_find_iter(bytes_after_first_selection)
14768 .map(|result| (buffer.len(), result)),
14769 );
14770 for (end_offset, query_match) in query_matches {
14771 let query_match = query_match.unwrap(); // can only fail due to I/O
14772 let offset_range =
14773 end_offset - query_match.end()..end_offset - query_match.start();
14774
14775 if !select_prev_state.wordwise
14776 || (!buffer.is_inside_word(offset_range.start, None)
14777 && !buffer.is_inside_word(offset_range.end, None))
14778 {
14779 next_selected_range = Some(offset_range);
14780 break;
14781 }
14782 }
14783
14784 if let Some(next_selected_range) = next_selected_range {
14785 self.select_match_ranges(
14786 next_selected_range,
14787 last_selection.reversed,
14788 action.replace_newest,
14789 Some(Autoscroll::newest()),
14790 window,
14791 cx,
14792 );
14793 } else {
14794 select_prev_state.done = true;
14795 }
14796 }
14797
14798 self.select_prev_state = Some(select_prev_state);
14799 } else {
14800 let mut only_carets = true;
14801 let mut same_text_selected = true;
14802 let mut selected_text = None;
14803
14804 let mut selections_iter = selections.iter().peekable();
14805 while let Some(selection) = selections_iter.next() {
14806 if selection.start != selection.end {
14807 only_carets = false;
14808 }
14809
14810 if same_text_selected {
14811 if selected_text.is_none() {
14812 selected_text =
14813 Some(buffer.text_for_range(selection.range()).collect::<String>());
14814 }
14815
14816 if let Some(next_selection) = selections_iter.peek() {
14817 if next_selection.range().len() == selection.range().len() {
14818 let next_selected_text = buffer
14819 .text_for_range(next_selection.range())
14820 .collect::<String>();
14821 if Some(next_selected_text) != selected_text {
14822 same_text_selected = false;
14823 selected_text = None;
14824 }
14825 } else {
14826 same_text_selected = false;
14827 selected_text = None;
14828 }
14829 }
14830 }
14831 }
14832
14833 if only_carets {
14834 for selection in &mut selections {
14835 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14836 selection.start = word_range.start;
14837 selection.end = word_range.end;
14838 selection.goal = SelectionGoal::None;
14839 selection.reversed = false;
14840 self.select_match_ranges(
14841 selection.start..selection.end,
14842 selection.reversed,
14843 action.replace_newest,
14844 Some(Autoscroll::newest()),
14845 window,
14846 cx,
14847 );
14848 }
14849 if selections.len() == 1 {
14850 let selection = selections
14851 .last()
14852 .expect("ensured that there's only one selection");
14853 let query = buffer
14854 .text_for_range(selection.start..selection.end)
14855 .collect::<String>();
14856 let is_empty = query.is_empty();
14857 let select_state = SelectNextState {
14858 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14859 wordwise: true,
14860 done: is_empty,
14861 };
14862 self.select_prev_state = Some(select_state);
14863 } else {
14864 self.select_prev_state = None;
14865 }
14866 } else if let Some(selected_text) = selected_text {
14867 self.select_prev_state = Some(SelectNextState {
14868 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14869 wordwise: false,
14870 done: false,
14871 });
14872 self.select_previous(action, window, cx)?;
14873 }
14874 }
14875 Ok(())
14876 }
14877
14878 pub fn find_next_match(
14879 &mut self,
14880 _: &FindNextMatch,
14881 window: &mut Window,
14882 cx: &mut Context<Self>,
14883 ) -> Result<()> {
14884 let selections = self.selections.disjoint_anchors_arc();
14885 match selections.first() {
14886 Some(first) if selections.len() >= 2 => {
14887 self.change_selections(Default::default(), window, cx, |s| {
14888 s.select_ranges([first.range()]);
14889 });
14890 }
14891 _ => self.select_next(
14892 &SelectNext {
14893 replace_newest: true,
14894 },
14895 window,
14896 cx,
14897 )?,
14898 }
14899 Ok(())
14900 }
14901
14902 pub fn find_previous_match(
14903 &mut self,
14904 _: &FindPreviousMatch,
14905 window: &mut Window,
14906 cx: &mut Context<Self>,
14907 ) -> Result<()> {
14908 let selections = self.selections.disjoint_anchors_arc();
14909 match selections.last() {
14910 Some(last) if selections.len() >= 2 => {
14911 self.change_selections(Default::default(), window, cx, |s| {
14912 s.select_ranges([last.range()]);
14913 });
14914 }
14915 _ => self.select_previous(
14916 &SelectPrevious {
14917 replace_newest: true,
14918 },
14919 window,
14920 cx,
14921 )?,
14922 }
14923 Ok(())
14924 }
14925
14926 pub fn toggle_comments(
14927 &mut self,
14928 action: &ToggleComments,
14929 window: &mut Window,
14930 cx: &mut Context<Self>,
14931 ) {
14932 if self.read_only(cx) {
14933 return;
14934 }
14935 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14936 let text_layout_details = &self.text_layout_details(window);
14937 self.transact(window, cx, |this, window, cx| {
14938 let mut selections = this
14939 .selections
14940 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
14941 let mut edits = Vec::new();
14942 let mut selection_edit_ranges = Vec::new();
14943 let mut last_toggled_row = None;
14944 let snapshot = this.buffer.read(cx).read(cx);
14945 let empty_str: Arc<str> = Arc::default();
14946 let mut suffixes_inserted = Vec::new();
14947 let ignore_indent = action.ignore_indent;
14948
14949 fn comment_prefix_range(
14950 snapshot: &MultiBufferSnapshot,
14951 row: MultiBufferRow,
14952 comment_prefix: &str,
14953 comment_prefix_whitespace: &str,
14954 ignore_indent: bool,
14955 ) -> Range<Point> {
14956 let indent_size = if ignore_indent {
14957 0
14958 } else {
14959 snapshot.indent_size_for_line(row).len
14960 };
14961
14962 let start = Point::new(row.0, indent_size);
14963
14964 let mut line_bytes = snapshot
14965 .bytes_in_range(start..snapshot.max_point())
14966 .flatten()
14967 .copied();
14968
14969 // If this line currently begins with the line comment prefix, then record
14970 // the range containing the prefix.
14971 if line_bytes
14972 .by_ref()
14973 .take(comment_prefix.len())
14974 .eq(comment_prefix.bytes())
14975 {
14976 // Include any whitespace that matches the comment prefix.
14977 let matching_whitespace_len = line_bytes
14978 .zip(comment_prefix_whitespace.bytes())
14979 .take_while(|(a, b)| a == b)
14980 .count() as u32;
14981 let end = Point::new(
14982 start.row,
14983 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14984 );
14985 start..end
14986 } else {
14987 start..start
14988 }
14989 }
14990
14991 fn comment_suffix_range(
14992 snapshot: &MultiBufferSnapshot,
14993 row: MultiBufferRow,
14994 comment_suffix: &str,
14995 comment_suffix_has_leading_space: bool,
14996 ) -> Range<Point> {
14997 let end = Point::new(row.0, snapshot.line_len(row));
14998 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14999
15000 let mut line_end_bytes = snapshot
15001 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15002 .flatten()
15003 .copied();
15004
15005 let leading_space_len = if suffix_start_column > 0
15006 && line_end_bytes.next() == Some(b' ')
15007 && comment_suffix_has_leading_space
15008 {
15009 1
15010 } else {
15011 0
15012 };
15013
15014 // If this line currently begins with the line comment prefix, then record
15015 // the range containing the prefix.
15016 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15017 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15018 start..end
15019 } else {
15020 end..end
15021 }
15022 }
15023
15024 // TODO: Handle selections that cross excerpts
15025 for selection in &mut selections {
15026 let start_column = snapshot
15027 .indent_size_for_line(MultiBufferRow(selection.start.row))
15028 .len;
15029 let language = if let Some(language) =
15030 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15031 {
15032 language
15033 } else {
15034 continue;
15035 };
15036
15037 selection_edit_ranges.clear();
15038
15039 // If multiple selections contain a given row, avoid processing that
15040 // row more than once.
15041 let mut start_row = MultiBufferRow(selection.start.row);
15042 if last_toggled_row == Some(start_row) {
15043 start_row = start_row.next_row();
15044 }
15045 let end_row =
15046 if selection.end.row > selection.start.row && selection.end.column == 0 {
15047 MultiBufferRow(selection.end.row - 1)
15048 } else {
15049 MultiBufferRow(selection.end.row)
15050 };
15051 last_toggled_row = Some(end_row);
15052
15053 if start_row > end_row {
15054 continue;
15055 }
15056
15057 // If the language has line comments, toggle those.
15058 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15059
15060 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15061 if ignore_indent {
15062 full_comment_prefixes = full_comment_prefixes
15063 .into_iter()
15064 .map(|s| Arc::from(s.trim_end()))
15065 .collect();
15066 }
15067
15068 if !full_comment_prefixes.is_empty() {
15069 let first_prefix = full_comment_prefixes
15070 .first()
15071 .expect("prefixes is non-empty");
15072 let prefix_trimmed_lengths = full_comment_prefixes
15073 .iter()
15074 .map(|p| p.trim_end_matches(' ').len())
15075 .collect::<SmallVec<[usize; 4]>>();
15076
15077 let mut all_selection_lines_are_comments = true;
15078
15079 for row in start_row.0..=end_row.0 {
15080 let row = MultiBufferRow(row);
15081 if start_row < end_row && snapshot.is_line_blank(row) {
15082 continue;
15083 }
15084
15085 let prefix_range = full_comment_prefixes
15086 .iter()
15087 .zip(prefix_trimmed_lengths.iter().copied())
15088 .map(|(prefix, trimmed_prefix_len)| {
15089 comment_prefix_range(
15090 snapshot.deref(),
15091 row,
15092 &prefix[..trimmed_prefix_len],
15093 &prefix[trimmed_prefix_len..],
15094 ignore_indent,
15095 )
15096 })
15097 .max_by_key(|range| range.end.column - range.start.column)
15098 .expect("prefixes is non-empty");
15099
15100 if prefix_range.is_empty() {
15101 all_selection_lines_are_comments = false;
15102 }
15103
15104 selection_edit_ranges.push(prefix_range);
15105 }
15106
15107 if all_selection_lines_are_comments {
15108 edits.extend(
15109 selection_edit_ranges
15110 .iter()
15111 .cloned()
15112 .map(|range| (range, empty_str.clone())),
15113 );
15114 } else {
15115 let min_column = selection_edit_ranges
15116 .iter()
15117 .map(|range| range.start.column)
15118 .min()
15119 .unwrap_or(0);
15120 edits.extend(selection_edit_ranges.iter().map(|range| {
15121 let position = Point::new(range.start.row, min_column);
15122 (position..position, first_prefix.clone())
15123 }));
15124 }
15125 } else if let Some(BlockCommentConfig {
15126 start: full_comment_prefix,
15127 end: comment_suffix,
15128 ..
15129 }) = language.block_comment()
15130 {
15131 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15132 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15133 let prefix_range = comment_prefix_range(
15134 snapshot.deref(),
15135 start_row,
15136 comment_prefix,
15137 comment_prefix_whitespace,
15138 ignore_indent,
15139 );
15140 let suffix_range = comment_suffix_range(
15141 snapshot.deref(),
15142 end_row,
15143 comment_suffix.trim_start_matches(' '),
15144 comment_suffix.starts_with(' '),
15145 );
15146
15147 if prefix_range.is_empty() || suffix_range.is_empty() {
15148 edits.push((
15149 prefix_range.start..prefix_range.start,
15150 full_comment_prefix.clone(),
15151 ));
15152 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15153 suffixes_inserted.push((end_row, comment_suffix.len()));
15154 } else {
15155 edits.push((prefix_range, empty_str.clone()));
15156 edits.push((suffix_range, empty_str.clone()));
15157 }
15158 } else {
15159 continue;
15160 }
15161 }
15162
15163 drop(snapshot);
15164 this.buffer.update(cx, |buffer, cx| {
15165 buffer.edit(edits, None, cx);
15166 });
15167
15168 // Adjust selections so that they end before any comment suffixes that
15169 // were inserted.
15170 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15171 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15172 let snapshot = this.buffer.read(cx).read(cx);
15173 for selection in &mut selections {
15174 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15175 match row.cmp(&MultiBufferRow(selection.end.row)) {
15176 Ordering::Less => {
15177 suffixes_inserted.next();
15178 continue;
15179 }
15180 Ordering::Greater => break,
15181 Ordering::Equal => {
15182 if selection.end.column == snapshot.line_len(row) {
15183 if selection.is_empty() {
15184 selection.start.column -= suffix_len as u32;
15185 }
15186 selection.end.column -= suffix_len as u32;
15187 }
15188 break;
15189 }
15190 }
15191 }
15192 }
15193
15194 drop(snapshot);
15195 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15196
15197 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15198 let selections_on_single_row = selections.windows(2).all(|selections| {
15199 selections[0].start.row == selections[1].start.row
15200 && selections[0].end.row == selections[1].end.row
15201 && selections[0].start.row == selections[0].end.row
15202 });
15203 let selections_selecting = selections
15204 .iter()
15205 .any(|selection| selection.start != selection.end);
15206 let advance_downwards = action.advance_downwards
15207 && selections_on_single_row
15208 && !selections_selecting
15209 && !matches!(this.mode, EditorMode::SingleLine);
15210
15211 if advance_downwards {
15212 let snapshot = this.buffer.read(cx).snapshot(cx);
15213
15214 this.change_selections(Default::default(), window, cx, |s| {
15215 s.move_cursors_with(|display_snapshot, display_point, _| {
15216 let mut point = display_point.to_point(display_snapshot);
15217 point.row += 1;
15218 point = snapshot.clip_point(point, Bias::Left);
15219 let display_point = point.to_display_point(display_snapshot);
15220 let goal = SelectionGoal::HorizontalPosition(
15221 display_snapshot
15222 .x_for_display_point(display_point, text_layout_details)
15223 .into(),
15224 );
15225 (display_point, goal)
15226 })
15227 });
15228 }
15229 });
15230 }
15231
15232 pub fn select_enclosing_symbol(
15233 &mut self,
15234 _: &SelectEnclosingSymbol,
15235 window: &mut Window,
15236 cx: &mut Context<Self>,
15237 ) {
15238 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15239
15240 let buffer = self.buffer.read(cx).snapshot(cx);
15241 let old_selections = self
15242 .selections
15243 .all::<usize>(&self.display_snapshot(cx))
15244 .into_boxed_slice();
15245
15246 fn update_selection(
15247 selection: &Selection<usize>,
15248 buffer_snap: &MultiBufferSnapshot,
15249 ) -> Option<Selection<usize>> {
15250 let cursor = selection.head();
15251 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15252 for symbol in symbols.iter().rev() {
15253 let start = symbol.range.start.to_offset(buffer_snap);
15254 let end = symbol.range.end.to_offset(buffer_snap);
15255 let new_range = start..end;
15256 if start < selection.start || end > selection.end {
15257 return Some(Selection {
15258 id: selection.id,
15259 start: new_range.start,
15260 end: new_range.end,
15261 goal: SelectionGoal::None,
15262 reversed: selection.reversed,
15263 });
15264 }
15265 }
15266 None
15267 }
15268
15269 let mut selected_larger_symbol = false;
15270 let new_selections = old_selections
15271 .iter()
15272 .map(|selection| match update_selection(selection, &buffer) {
15273 Some(new_selection) => {
15274 if new_selection.range() != selection.range() {
15275 selected_larger_symbol = true;
15276 }
15277 new_selection
15278 }
15279 None => selection.clone(),
15280 })
15281 .collect::<Vec<_>>();
15282
15283 if selected_larger_symbol {
15284 self.change_selections(Default::default(), window, cx, |s| {
15285 s.select(new_selections);
15286 });
15287 }
15288 }
15289
15290 pub fn select_larger_syntax_node(
15291 &mut self,
15292 _: &SelectLargerSyntaxNode,
15293 window: &mut Window,
15294 cx: &mut Context<Self>,
15295 ) {
15296 let Some(visible_row_count) = self.visible_row_count() else {
15297 return;
15298 };
15299 let old_selections: Box<[_]> = self
15300 .selections
15301 .all::<usize>(&self.display_snapshot(cx))
15302 .into();
15303 if old_selections.is_empty() {
15304 return;
15305 }
15306
15307 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15308
15309 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15310 let buffer = self.buffer.read(cx).snapshot(cx);
15311
15312 let mut selected_larger_node = false;
15313 let mut new_selections = old_selections
15314 .iter()
15315 .map(|selection| {
15316 let old_range = selection.start..selection.end;
15317
15318 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15319 // manually select word at selection
15320 if ["string_content", "inline"].contains(&node.kind()) {
15321 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15322 // ignore if word is already selected
15323 if !word_range.is_empty() && old_range != word_range {
15324 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15325 // only select word if start and end point belongs to same word
15326 if word_range == last_word_range {
15327 selected_larger_node = true;
15328 return Selection {
15329 id: selection.id,
15330 start: word_range.start,
15331 end: word_range.end,
15332 goal: SelectionGoal::None,
15333 reversed: selection.reversed,
15334 };
15335 }
15336 }
15337 }
15338 }
15339
15340 let mut new_range = old_range.clone();
15341 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15342 new_range = range;
15343 if !node.is_named() {
15344 continue;
15345 }
15346 if !display_map.intersects_fold(new_range.start)
15347 && !display_map.intersects_fold(new_range.end)
15348 {
15349 break;
15350 }
15351 }
15352
15353 selected_larger_node |= new_range != old_range;
15354 Selection {
15355 id: selection.id,
15356 start: new_range.start,
15357 end: new_range.end,
15358 goal: SelectionGoal::None,
15359 reversed: selection.reversed,
15360 }
15361 })
15362 .collect::<Vec<_>>();
15363
15364 if !selected_larger_node {
15365 return; // don't put this call in the history
15366 }
15367
15368 // scroll based on transformation done to the last selection created by the user
15369 let (last_old, last_new) = old_selections
15370 .last()
15371 .zip(new_selections.last().cloned())
15372 .expect("old_selections isn't empty");
15373
15374 // revert selection
15375 let is_selection_reversed = {
15376 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15377 new_selections.last_mut().expect("checked above").reversed =
15378 should_newest_selection_be_reversed;
15379 should_newest_selection_be_reversed
15380 };
15381
15382 if selected_larger_node {
15383 self.select_syntax_node_history.disable_clearing = true;
15384 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15385 s.select(new_selections.clone());
15386 });
15387 self.select_syntax_node_history.disable_clearing = false;
15388 }
15389
15390 let start_row = last_new.start.to_display_point(&display_map).row().0;
15391 let end_row = last_new.end.to_display_point(&display_map).row().0;
15392 let selection_height = end_row - start_row + 1;
15393 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15394
15395 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15396 let scroll_behavior = if fits_on_the_screen {
15397 self.request_autoscroll(Autoscroll::fit(), cx);
15398 SelectSyntaxNodeScrollBehavior::FitSelection
15399 } else if is_selection_reversed {
15400 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15401 SelectSyntaxNodeScrollBehavior::CursorTop
15402 } else {
15403 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15404 SelectSyntaxNodeScrollBehavior::CursorBottom
15405 };
15406
15407 self.select_syntax_node_history.push((
15408 old_selections,
15409 scroll_behavior,
15410 is_selection_reversed,
15411 ));
15412 }
15413
15414 pub fn select_smaller_syntax_node(
15415 &mut self,
15416 _: &SelectSmallerSyntaxNode,
15417 window: &mut Window,
15418 cx: &mut Context<Self>,
15419 ) {
15420 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15421
15422 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15423 self.select_syntax_node_history.pop()
15424 {
15425 if let Some(selection) = selections.last_mut() {
15426 selection.reversed = is_selection_reversed;
15427 }
15428
15429 self.select_syntax_node_history.disable_clearing = true;
15430 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15431 s.select(selections.to_vec());
15432 });
15433 self.select_syntax_node_history.disable_clearing = false;
15434
15435 match scroll_behavior {
15436 SelectSyntaxNodeScrollBehavior::CursorTop => {
15437 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15438 }
15439 SelectSyntaxNodeScrollBehavior::FitSelection => {
15440 self.request_autoscroll(Autoscroll::fit(), cx);
15441 }
15442 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15443 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15444 }
15445 }
15446 }
15447 }
15448
15449 pub fn unwrap_syntax_node(
15450 &mut self,
15451 _: &UnwrapSyntaxNode,
15452 window: &mut Window,
15453 cx: &mut Context<Self>,
15454 ) {
15455 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15456
15457 let buffer = self.buffer.read(cx).snapshot(cx);
15458 let selections = self
15459 .selections
15460 .all::<usize>(&self.display_snapshot(cx))
15461 .into_iter()
15462 // subtracting the offset requires sorting
15463 .sorted_by_key(|i| i.start);
15464
15465 let full_edits = selections
15466 .into_iter()
15467 .filter_map(|selection| {
15468 let child = if selection.is_empty()
15469 && let Some((_, ancestor_range)) =
15470 buffer.syntax_ancestor(selection.start..selection.end)
15471 {
15472 ancestor_range
15473 } else {
15474 selection.range()
15475 };
15476
15477 let mut parent = child.clone();
15478 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15479 parent = ancestor_range;
15480 if parent.start < child.start || parent.end > child.end {
15481 break;
15482 }
15483 }
15484
15485 if parent == child {
15486 return None;
15487 }
15488 let text = buffer.text_for_range(child).collect::<String>();
15489 Some((selection.id, parent, text))
15490 })
15491 .collect::<Vec<_>>();
15492 if full_edits.is_empty() {
15493 return;
15494 }
15495
15496 self.transact(window, cx, |this, window, cx| {
15497 this.buffer.update(cx, |buffer, cx| {
15498 buffer.edit(
15499 full_edits
15500 .iter()
15501 .map(|(_, p, t)| (p.clone(), t.clone()))
15502 .collect::<Vec<_>>(),
15503 None,
15504 cx,
15505 );
15506 });
15507 this.change_selections(Default::default(), window, cx, |s| {
15508 let mut offset = 0;
15509 let mut selections = vec![];
15510 for (id, parent, text) in full_edits {
15511 let start = parent.start - offset;
15512 offset += parent.len() - text.len();
15513 selections.push(Selection {
15514 id,
15515 start,
15516 end: start + text.len(),
15517 reversed: false,
15518 goal: Default::default(),
15519 });
15520 }
15521 s.select(selections);
15522 });
15523 });
15524 }
15525
15526 pub fn select_next_syntax_node(
15527 &mut self,
15528 _: &SelectNextSyntaxNode,
15529 window: &mut Window,
15530 cx: &mut Context<Self>,
15531 ) {
15532 let old_selections: Box<[_]> = self
15533 .selections
15534 .all::<usize>(&self.display_snapshot(cx))
15535 .into();
15536 if old_selections.is_empty() {
15537 return;
15538 }
15539
15540 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15541
15542 let buffer = self.buffer.read(cx).snapshot(cx);
15543 let mut selected_sibling = false;
15544
15545 let new_selections = old_selections
15546 .iter()
15547 .map(|selection| {
15548 let old_range = selection.start..selection.end;
15549
15550 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15551 let new_range = node.byte_range();
15552 selected_sibling = true;
15553 Selection {
15554 id: selection.id,
15555 start: new_range.start,
15556 end: new_range.end,
15557 goal: SelectionGoal::None,
15558 reversed: selection.reversed,
15559 }
15560 } else {
15561 selection.clone()
15562 }
15563 })
15564 .collect::<Vec<_>>();
15565
15566 if selected_sibling {
15567 self.change_selections(
15568 SelectionEffects::scroll(Autoscroll::fit()),
15569 window,
15570 cx,
15571 |s| {
15572 s.select(new_selections);
15573 },
15574 );
15575 }
15576 }
15577
15578 pub fn select_prev_syntax_node(
15579 &mut self,
15580 _: &SelectPreviousSyntaxNode,
15581 window: &mut Window,
15582 cx: &mut Context<Self>,
15583 ) {
15584 let old_selections: Box<[_]> = self
15585 .selections
15586 .all::<usize>(&self.display_snapshot(cx))
15587 .into();
15588 if old_selections.is_empty() {
15589 return;
15590 }
15591
15592 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15593
15594 let buffer = self.buffer.read(cx).snapshot(cx);
15595 let mut selected_sibling = false;
15596
15597 let new_selections = old_selections
15598 .iter()
15599 .map(|selection| {
15600 let old_range = selection.start..selection.end;
15601
15602 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15603 let new_range = node.byte_range();
15604 selected_sibling = true;
15605 Selection {
15606 id: selection.id,
15607 start: new_range.start,
15608 end: new_range.end,
15609 goal: SelectionGoal::None,
15610 reversed: selection.reversed,
15611 }
15612 } else {
15613 selection.clone()
15614 }
15615 })
15616 .collect::<Vec<_>>();
15617
15618 if selected_sibling {
15619 self.change_selections(
15620 SelectionEffects::scroll(Autoscroll::fit()),
15621 window,
15622 cx,
15623 |s| {
15624 s.select(new_selections);
15625 },
15626 );
15627 }
15628 }
15629
15630 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15631 if !EditorSettings::get_global(cx).gutter.runnables {
15632 self.clear_tasks();
15633 return Task::ready(());
15634 }
15635 let project = self.project().map(Entity::downgrade);
15636 let task_sources = self.lsp_task_sources(cx);
15637 let multi_buffer = self.buffer.downgrade();
15638 cx.spawn_in(window, async move |editor, cx| {
15639 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15640 let Some(project) = project.and_then(|p| p.upgrade()) else {
15641 return;
15642 };
15643 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15644 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15645 }) else {
15646 return;
15647 };
15648
15649 let hide_runnables = project
15650 .update(cx, |project, _| project.is_via_collab())
15651 .unwrap_or(true);
15652 if hide_runnables {
15653 return;
15654 }
15655 let new_rows =
15656 cx.background_spawn({
15657 let snapshot = display_snapshot.clone();
15658 async move {
15659 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15660 }
15661 })
15662 .await;
15663 let Ok(lsp_tasks) =
15664 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15665 else {
15666 return;
15667 };
15668 let lsp_tasks = lsp_tasks.await;
15669
15670 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15671 lsp_tasks
15672 .into_iter()
15673 .flat_map(|(kind, tasks)| {
15674 tasks.into_iter().filter_map(move |(location, task)| {
15675 Some((kind.clone(), location?, task))
15676 })
15677 })
15678 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15679 let buffer = location.target.buffer;
15680 let buffer_snapshot = buffer.read(cx).snapshot();
15681 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15682 |(excerpt_id, snapshot, _)| {
15683 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15684 display_snapshot
15685 .buffer_snapshot()
15686 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15687 } else {
15688 None
15689 }
15690 },
15691 );
15692 if let Some(offset) = offset {
15693 let task_buffer_range =
15694 location.target.range.to_point(&buffer_snapshot);
15695 let context_buffer_range =
15696 task_buffer_range.to_offset(&buffer_snapshot);
15697 let context_range = BufferOffset(context_buffer_range.start)
15698 ..BufferOffset(context_buffer_range.end);
15699
15700 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15701 .or_insert_with(|| RunnableTasks {
15702 templates: Vec::new(),
15703 offset,
15704 column: task_buffer_range.start.column,
15705 extra_variables: HashMap::default(),
15706 context_range,
15707 })
15708 .templates
15709 .push((kind, task.original_task().clone()));
15710 }
15711
15712 acc
15713 })
15714 }) else {
15715 return;
15716 };
15717
15718 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15719 buffer.language_settings(cx).tasks.prefer_lsp
15720 }) else {
15721 return;
15722 };
15723
15724 let rows = Self::runnable_rows(
15725 project,
15726 display_snapshot,
15727 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15728 new_rows,
15729 cx.clone(),
15730 )
15731 .await;
15732 editor
15733 .update(cx, |editor, _| {
15734 editor.clear_tasks();
15735 for (key, mut value) in rows {
15736 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15737 value.templates.extend(lsp_tasks.templates);
15738 }
15739
15740 editor.insert_tasks(key, value);
15741 }
15742 for (key, value) in lsp_tasks_by_rows {
15743 editor.insert_tasks(key, value);
15744 }
15745 })
15746 .ok();
15747 })
15748 }
15749 fn fetch_runnable_ranges(
15750 snapshot: &DisplaySnapshot,
15751 range: Range<Anchor>,
15752 ) -> Vec<language::RunnableRange> {
15753 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15754 }
15755
15756 fn runnable_rows(
15757 project: Entity<Project>,
15758 snapshot: DisplaySnapshot,
15759 prefer_lsp: bool,
15760 runnable_ranges: Vec<RunnableRange>,
15761 cx: AsyncWindowContext,
15762 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15763 cx.spawn(async move |cx| {
15764 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15765 for mut runnable in runnable_ranges {
15766 let Some(tasks) = cx
15767 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15768 .ok()
15769 else {
15770 continue;
15771 };
15772 let mut tasks = tasks.await;
15773
15774 if prefer_lsp {
15775 tasks.retain(|(task_kind, _)| {
15776 !matches!(task_kind, TaskSourceKind::Language { .. })
15777 });
15778 }
15779 if tasks.is_empty() {
15780 continue;
15781 }
15782
15783 let point = runnable
15784 .run_range
15785 .start
15786 .to_point(&snapshot.buffer_snapshot());
15787 let Some(row) = snapshot
15788 .buffer_snapshot()
15789 .buffer_line_for_row(MultiBufferRow(point.row))
15790 .map(|(_, range)| range.start.row)
15791 else {
15792 continue;
15793 };
15794
15795 let context_range =
15796 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15797 runnable_rows.push((
15798 (runnable.buffer_id, row),
15799 RunnableTasks {
15800 templates: tasks,
15801 offset: snapshot
15802 .buffer_snapshot()
15803 .anchor_before(runnable.run_range.start),
15804 context_range,
15805 column: point.column,
15806 extra_variables: runnable.extra_captures,
15807 },
15808 ));
15809 }
15810 runnable_rows
15811 })
15812 }
15813
15814 fn templates_with_tags(
15815 project: &Entity<Project>,
15816 runnable: &mut Runnable,
15817 cx: &mut App,
15818 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15819 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15820 let (worktree_id, file) = project
15821 .buffer_for_id(runnable.buffer, cx)
15822 .and_then(|buffer| buffer.read(cx).file())
15823 .map(|file| (file.worktree_id(cx), file.clone()))
15824 .unzip();
15825
15826 (
15827 project.task_store().read(cx).task_inventory().cloned(),
15828 worktree_id,
15829 file,
15830 )
15831 });
15832
15833 let tags = mem::take(&mut runnable.tags);
15834 let language = runnable.language.clone();
15835 cx.spawn(async move |cx| {
15836 let mut templates_with_tags = Vec::new();
15837 if let Some(inventory) = inventory {
15838 for RunnableTag(tag) in tags {
15839 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15840 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15841 }) else {
15842 return templates_with_tags;
15843 };
15844 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15845 move |(_, template)| {
15846 template.tags.iter().any(|source_tag| source_tag == &tag)
15847 },
15848 ));
15849 }
15850 }
15851 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15852
15853 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15854 // Strongest source wins; if we have worktree tag binding, prefer that to
15855 // global and language bindings;
15856 // if we have a global binding, prefer that to language binding.
15857 let first_mismatch = templates_with_tags
15858 .iter()
15859 .position(|(tag_source, _)| tag_source != leading_tag_source);
15860 if let Some(index) = first_mismatch {
15861 templates_with_tags.truncate(index);
15862 }
15863 }
15864
15865 templates_with_tags
15866 })
15867 }
15868
15869 pub fn move_to_enclosing_bracket(
15870 &mut self,
15871 _: &MoveToEnclosingBracket,
15872 window: &mut Window,
15873 cx: &mut Context<Self>,
15874 ) {
15875 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15876 self.change_selections(Default::default(), window, cx, |s| {
15877 s.move_offsets_with(|snapshot, selection| {
15878 let Some(enclosing_bracket_ranges) =
15879 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15880 else {
15881 return;
15882 };
15883
15884 let mut best_length = usize::MAX;
15885 let mut best_inside = false;
15886 let mut best_in_bracket_range = false;
15887 let mut best_destination = None;
15888 for (open, close) in enclosing_bracket_ranges {
15889 let close = close.to_inclusive();
15890 let length = close.end() - open.start;
15891 let inside = selection.start >= open.end && selection.end <= *close.start();
15892 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15893 || close.contains(&selection.head());
15894
15895 // If best is next to a bracket and current isn't, skip
15896 if !in_bracket_range && best_in_bracket_range {
15897 continue;
15898 }
15899
15900 // Prefer smaller lengths unless best is inside and current isn't
15901 if length > best_length && (best_inside || !inside) {
15902 continue;
15903 }
15904
15905 best_length = length;
15906 best_inside = inside;
15907 best_in_bracket_range = in_bracket_range;
15908 best_destination = Some(
15909 if close.contains(&selection.start) && close.contains(&selection.end) {
15910 if inside { open.end } else { open.start }
15911 } else if inside {
15912 *close.start()
15913 } else {
15914 *close.end()
15915 },
15916 );
15917 }
15918
15919 if let Some(destination) = best_destination {
15920 selection.collapse_to(destination, SelectionGoal::None);
15921 }
15922 })
15923 });
15924 }
15925
15926 pub fn undo_selection(
15927 &mut self,
15928 _: &UndoSelection,
15929 window: &mut Window,
15930 cx: &mut Context<Self>,
15931 ) {
15932 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15933 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15934 self.selection_history.mode = SelectionHistoryMode::Undoing;
15935 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15936 this.end_selection(window, cx);
15937 this.change_selections(
15938 SelectionEffects::scroll(Autoscroll::newest()),
15939 window,
15940 cx,
15941 |s| s.select_anchors(entry.selections.to_vec()),
15942 );
15943 });
15944 self.selection_history.mode = SelectionHistoryMode::Normal;
15945
15946 self.select_next_state = entry.select_next_state;
15947 self.select_prev_state = entry.select_prev_state;
15948 self.add_selections_state = entry.add_selections_state;
15949 }
15950 }
15951
15952 pub fn redo_selection(
15953 &mut self,
15954 _: &RedoSelection,
15955 window: &mut Window,
15956 cx: &mut Context<Self>,
15957 ) {
15958 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15959 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15960 self.selection_history.mode = SelectionHistoryMode::Redoing;
15961 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15962 this.end_selection(window, cx);
15963 this.change_selections(
15964 SelectionEffects::scroll(Autoscroll::newest()),
15965 window,
15966 cx,
15967 |s| s.select_anchors(entry.selections.to_vec()),
15968 );
15969 });
15970 self.selection_history.mode = SelectionHistoryMode::Normal;
15971
15972 self.select_next_state = entry.select_next_state;
15973 self.select_prev_state = entry.select_prev_state;
15974 self.add_selections_state = entry.add_selections_state;
15975 }
15976 }
15977
15978 pub fn expand_excerpts(
15979 &mut self,
15980 action: &ExpandExcerpts,
15981 _: &mut Window,
15982 cx: &mut Context<Self>,
15983 ) {
15984 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15985 }
15986
15987 pub fn expand_excerpts_down(
15988 &mut self,
15989 action: &ExpandExcerptsDown,
15990 _: &mut Window,
15991 cx: &mut Context<Self>,
15992 ) {
15993 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15994 }
15995
15996 pub fn expand_excerpts_up(
15997 &mut self,
15998 action: &ExpandExcerptsUp,
15999 _: &mut Window,
16000 cx: &mut Context<Self>,
16001 ) {
16002 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16003 }
16004
16005 pub fn expand_excerpts_for_direction(
16006 &mut self,
16007 lines: u32,
16008 direction: ExpandExcerptDirection,
16009
16010 cx: &mut Context<Self>,
16011 ) {
16012 let selections = self.selections.disjoint_anchors_arc();
16013
16014 let lines = if lines == 0 {
16015 EditorSettings::get_global(cx).expand_excerpt_lines
16016 } else {
16017 lines
16018 };
16019
16020 self.buffer.update(cx, |buffer, cx| {
16021 let snapshot = buffer.snapshot(cx);
16022 let mut excerpt_ids = selections
16023 .iter()
16024 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16025 .collect::<Vec<_>>();
16026 excerpt_ids.sort();
16027 excerpt_ids.dedup();
16028 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16029 })
16030 }
16031
16032 pub fn expand_excerpt(
16033 &mut self,
16034 excerpt: ExcerptId,
16035 direction: ExpandExcerptDirection,
16036 window: &mut Window,
16037 cx: &mut Context<Self>,
16038 ) {
16039 let current_scroll_position = self.scroll_position(cx);
16040 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16041 let mut should_scroll_up = false;
16042
16043 if direction == ExpandExcerptDirection::Down {
16044 let multi_buffer = self.buffer.read(cx);
16045 let snapshot = multi_buffer.snapshot(cx);
16046 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16047 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16048 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16049 {
16050 let buffer_snapshot = buffer.read(cx).snapshot();
16051 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16052 let last_row = buffer_snapshot.max_point().row;
16053 let lines_below = last_row.saturating_sub(excerpt_end_row);
16054 should_scroll_up = lines_below >= lines_to_expand;
16055 }
16056 }
16057
16058 self.buffer.update(cx, |buffer, cx| {
16059 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16060 });
16061
16062 if should_scroll_up {
16063 let new_scroll_position =
16064 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as ScrollOffset);
16065 self.set_scroll_position(new_scroll_position, window, cx);
16066 }
16067 }
16068
16069 pub fn go_to_singleton_buffer_point(
16070 &mut self,
16071 point: Point,
16072 window: &mut Window,
16073 cx: &mut Context<Self>,
16074 ) {
16075 self.go_to_singleton_buffer_range(point..point, window, cx);
16076 }
16077
16078 pub fn go_to_singleton_buffer_range(
16079 &mut self,
16080 range: Range<Point>,
16081 window: &mut Window,
16082 cx: &mut Context<Self>,
16083 ) {
16084 let multibuffer = self.buffer().read(cx);
16085 let Some(buffer) = multibuffer.as_singleton() else {
16086 return;
16087 };
16088 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16089 return;
16090 };
16091 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16092 return;
16093 };
16094 self.change_selections(
16095 SelectionEffects::default().nav_history(true),
16096 window,
16097 cx,
16098 |s| s.select_anchor_ranges([start..end]),
16099 );
16100 }
16101
16102 pub fn go_to_diagnostic(
16103 &mut self,
16104 action: &GoToDiagnostic,
16105 window: &mut Window,
16106 cx: &mut Context<Self>,
16107 ) {
16108 if !self.diagnostics_enabled() {
16109 return;
16110 }
16111 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16112 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16113 }
16114
16115 pub fn go_to_prev_diagnostic(
16116 &mut self,
16117 action: &GoToPreviousDiagnostic,
16118 window: &mut Window,
16119 cx: &mut Context<Self>,
16120 ) {
16121 if !self.diagnostics_enabled() {
16122 return;
16123 }
16124 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16125 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16126 }
16127
16128 pub fn go_to_diagnostic_impl(
16129 &mut self,
16130 direction: Direction,
16131 severity: GoToDiagnosticSeverityFilter,
16132 window: &mut Window,
16133 cx: &mut Context<Self>,
16134 ) {
16135 let buffer = self.buffer.read(cx).snapshot(cx);
16136 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16137
16138 let mut active_group_id = None;
16139 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16140 && active_group.active_range.start.to_offset(&buffer) == selection.start
16141 {
16142 active_group_id = Some(active_group.group_id);
16143 }
16144
16145 fn filtered<'a>(
16146 snapshot: EditorSnapshot,
16147 severity: GoToDiagnosticSeverityFilter,
16148 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
16149 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
16150 diagnostics
16151 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16152 .filter(|entry| entry.range.start != entry.range.end)
16153 .filter(|entry| !entry.diagnostic.is_unnecessary)
16154 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
16155 }
16156
16157 let snapshot = self.snapshot(window, cx);
16158 let before = filtered(
16159 snapshot.clone(),
16160 severity,
16161 buffer
16162 .diagnostics_in_range(0..selection.start)
16163 .filter(|entry| entry.range.start <= selection.start),
16164 );
16165 let after = filtered(
16166 snapshot,
16167 severity,
16168 buffer
16169 .diagnostics_in_range(selection.start..buffer.len())
16170 .filter(|entry| entry.range.start >= selection.start),
16171 );
16172
16173 let mut found: Option<DiagnosticEntryRef<usize>> = None;
16174 if direction == Direction::Prev {
16175 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16176 {
16177 for diagnostic in prev_diagnostics.into_iter().rev() {
16178 if diagnostic.range.start != selection.start
16179 || active_group_id
16180 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16181 {
16182 found = Some(diagnostic);
16183 break 'outer;
16184 }
16185 }
16186 }
16187 } else {
16188 for diagnostic in after.chain(before) {
16189 if diagnostic.range.start != selection.start
16190 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16191 {
16192 found = Some(diagnostic);
16193 break;
16194 }
16195 }
16196 }
16197 let Some(next_diagnostic) = found else {
16198 return;
16199 };
16200
16201 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16202 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16203 return;
16204 };
16205 self.change_selections(Default::default(), window, cx, |s| {
16206 s.select_ranges(vec![
16207 next_diagnostic.range.start..next_diagnostic.range.start,
16208 ])
16209 });
16210 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16211 self.refresh_edit_prediction(false, true, window, cx);
16212 }
16213
16214 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16215 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16216 let snapshot = self.snapshot(window, cx);
16217 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16218 self.go_to_hunk_before_or_after_position(
16219 &snapshot,
16220 selection.head(),
16221 Direction::Next,
16222 window,
16223 cx,
16224 );
16225 }
16226
16227 pub fn go_to_hunk_before_or_after_position(
16228 &mut self,
16229 snapshot: &EditorSnapshot,
16230 position: Point,
16231 direction: Direction,
16232 window: &mut Window,
16233 cx: &mut Context<Editor>,
16234 ) {
16235 let row = if direction == Direction::Next {
16236 self.hunk_after_position(snapshot, position)
16237 .map(|hunk| hunk.row_range.start)
16238 } else {
16239 self.hunk_before_position(snapshot, position)
16240 };
16241
16242 if let Some(row) = row {
16243 let destination = Point::new(row.0, 0);
16244 let autoscroll = Autoscroll::center();
16245
16246 self.unfold_ranges(&[destination..destination], false, false, cx);
16247 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16248 s.select_ranges([destination..destination]);
16249 });
16250 }
16251 }
16252
16253 fn hunk_after_position(
16254 &mut self,
16255 snapshot: &EditorSnapshot,
16256 position: Point,
16257 ) -> Option<MultiBufferDiffHunk> {
16258 snapshot
16259 .buffer_snapshot()
16260 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16261 .find(|hunk| hunk.row_range.start.0 > position.row)
16262 .or_else(|| {
16263 snapshot
16264 .buffer_snapshot()
16265 .diff_hunks_in_range(Point::zero()..position)
16266 .find(|hunk| hunk.row_range.end.0 < position.row)
16267 })
16268 }
16269
16270 fn go_to_prev_hunk(
16271 &mut self,
16272 _: &GoToPreviousHunk,
16273 window: &mut Window,
16274 cx: &mut Context<Self>,
16275 ) {
16276 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16277 let snapshot = self.snapshot(window, cx);
16278 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16279 self.go_to_hunk_before_or_after_position(
16280 &snapshot,
16281 selection.head(),
16282 Direction::Prev,
16283 window,
16284 cx,
16285 );
16286 }
16287
16288 fn hunk_before_position(
16289 &mut self,
16290 snapshot: &EditorSnapshot,
16291 position: Point,
16292 ) -> Option<MultiBufferRow> {
16293 snapshot
16294 .buffer_snapshot()
16295 .diff_hunk_before(position)
16296 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16297 }
16298
16299 fn go_to_next_change(
16300 &mut self,
16301 _: &GoToNextChange,
16302 window: &mut Window,
16303 cx: &mut Context<Self>,
16304 ) {
16305 if let Some(selections) = self
16306 .change_list
16307 .next_change(1, Direction::Next)
16308 .map(|s| s.to_vec())
16309 {
16310 self.change_selections(Default::default(), window, cx, |s| {
16311 let map = s.display_map();
16312 s.select_display_ranges(selections.iter().map(|a| {
16313 let point = a.to_display_point(&map);
16314 point..point
16315 }))
16316 })
16317 }
16318 }
16319
16320 fn go_to_previous_change(
16321 &mut self,
16322 _: &GoToPreviousChange,
16323 window: &mut Window,
16324 cx: &mut Context<Self>,
16325 ) {
16326 if let Some(selections) = self
16327 .change_list
16328 .next_change(1, Direction::Prev)
16329 .map(|s| s.to_vec())
16330 {
16331 self.change_selections(Default::default(), window, cx, |s| {
16332 let map = s.display_map();
16333 s.select_display_ranges(selections.iter().map(|a| {
16334 let point = a.to_display_point(&map);
16335 point..point
16336 }))
16337 })
16338 }
16339 }
16340
16341 pub fn go_to_next_document_highlight(
16342 &mut self,
16343 _: &GoToNextDocumentHighlight,
16344 window: &mut Window,
16345 cx: &mut Context<Self>,
16346 ) {
16347 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16348 }
16349
16350 pub fn go_to_prev_document_highlight(
16351 &mut self,
16352 _: &GoToPreviousDocumentHighlight,
16353 window: &mut Window,
16354 cx: &mut Context<Self>,
16355 ) {
16356 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16357 }
16358
16359 pub fn go_to_document_highlight_before_or_after_position(
16360 &mut self,
16361 direction: Direction,
16362 window: &mut Window,
16363 cx: &mut Context<Editor>,
16364 ) {
16365 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16366 let snapshot = self.snapshot(window, cx);
16367 let buffer = &snapshot.buffer_snapshot();
16368 let position = self
16369 .selections
16370 .newest::<Point>(&snapshot.display_snapshot)
16371 .head();
16372 let anchor_position = buffer.anchor_after(position);
16373
16374 // Get all document highlights (both read and write)
16375 let mut all_highlights = Vec::new();
16376
16377 if let Some((_, read_highlights)) = self
16378 .background_highlights
16379 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16380 {
16381 all_highlights.extend(read_highlights.iter());
16382 }
16383
16384 if let Some((_, write_highlights)) = self
16385 .background_highlights
16386 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16387 {
16388 all_highlights.extend(write_highlights.iter());
16389 }
16390
16391 if all_highlights.is_empty() {
16392 return;
16393 }
16394
16395 // Sort highlights by position
16396 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16397
16398 let target_highlight = match direction {
16399 Direction::Next => {
16400 // Find the first highlight after the current position
16401 all_highlights
16402 .iter()
16403 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16404 }
16405 Direction::Prev => {
16406 // Find the last highlight before the current position
16407 all_highlights
16408 .iter()
16409 .rev()
16410 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16411 }
16412 };
16413
16414 if let Some(highlight) = target_highlight {
16415 let destination = highlight.start.to_point(buffer);
16416 let autoscroll = Autoscroll::center();
16417
16418 self.unfold_ranges(&[destination..destination], false, false, cx);
16419 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16420 s.select_ranges([destination..destination]);
16421 });
16422 }
16423 }
16424
16425 fn go_to_line<T: 'static>(
16426 &mut self,
16427 position: Anchor,
16428 highlight_color: Option<Hsla>,
16429 window: &mut Window,
16430 cx: &mut Context<Self>,
16431 ) {
16432 let snapshot = self.snapshot(window, cx).display_snapshot;
16433 let position = position.to_point(&snapshot.buffer_snapshot());
16434 let start = snapshot
16435 .buffer_snapshot()
16436 .clip_point(Point::new(position.row, 0), Bias::Left);
16437 let end = start + Point::new(1, 0);
16438 let start = snapshot.buffer_snapshot().anchor_before(start);
16439 let end = snapshot.buffer_snapshot().anchor_before(end);
16440
16441 self.highlight_rows::<T>(
16442 start..end,
16443 highlight_color
16444 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16445 Default::default(),
16446 cx,
16447 );
16448
16449 if self.buffer.read(cx).is_singleton() {
16450 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16451 }
16452 }
16453
16454 pub fn go_to_definition(
16455 &mut self,
16456 _: &GoToDefinition,
16457 window: &mut Window,
16458 cx: &mut Context<Self>,
16459 ) -> Task<Result<Navigated>> {
16460 let definition =
16461 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16462 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16463 cx.spawn_in(window, async move |editor, cx| {
16464 if definition.await? == Navigated::Yes {
16465 return Ok(Navigated::Yes);
16466 }
16467 match fallback_strategy {
16468 GoToDefinitionFallback::None => Ok(Navigated::No),
16469 GoToDefinitionFallback::FindAllReferences => {
16470 match editor.update_in(cx, |editor, window, cx| {
16471 editor.find_all_references(&FindAllReferences, window, cx)
16472 })? {
16473 Some(references) => references.await,
16474 None => Ok(Navigated::No),
16475 }
16476 }
16477 }
16478 })
16479 }
16480
16481 pub fn go_to_declaration(
16482 &mut self,
16483 _: &GoToDeclaration,
16484 window: &mut Window,
16485 cx: &mut Context<Self>,
16486 ) -> Task<Result<Navigated>> {
16487 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16488 }
16489
16490 pub fn go_to_declaration_split(
16491 &mut self,
16492 _: &GoToDeclaration,
16493 window: &mut Window,
16494 cx: &mut Context<Self>,
16495 ) -> Task<Result<Navigated>> {
16496 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16497 }
16498
16499 pub fn go_to_implementation(
16500 &mut self,
16501 _: &GoToImplementation,
16502 window: &mut Window,
16503 cx: &mut Context<Self>,
16504 ) -> Task<Result<Navigated>> {
16505 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16506 }
16507
16508 pub fn go_to_implementation_split(
16509 &mut self,
16510 _: &GoToImplementationSplit,
16511 window: &mut Window,
16512 cx: &mut Context<Self>,
16513 ) -> Task<Result<Navigated>> {
16514 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16515 }
16516
16517 pub fn go_to_type_definition(
16518 &mut self,
16519 _: &GoToTypeDefinition,
16520 window: &mut Window,
16521 cx: &mut Context<Self>,
16522 ) -> Task<Result<Navigated>> {
16523 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16524 }
16525
16526 pub fn go_to_definition_split(
16527 &mut self,
16528 _: &GoToDefinitionSplit,
16529 window: &mut Window,
16530 cx: &mut Context<Self>,
16531 ) -> Task<Result<Navigated>> {
16532 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16533 }
16534
16535 pub fn go_to_type_definition_split(
16536 &mut self,
16537 _: &GoToTypeDefinitionSplit,
16538 window: &mut Window,
16539 cx: &mut Context<Self>,
16540 ) -> Task<Result<Navigated>> {
16541 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16542 }
16543
16544 fn go_to_definition_of_kind(
16545 &mut self,
16546 kind: GotoDefinitionKind,
16547 split: bool,
16548 window: &mut Window,
16549 cx: &mut Context<Self>,
16550 ) -> Task<Result<Navigated>> {
16551 let Some(provider) = self.semantics_provider.clone() else {
16552 return Task::ready(Ok(Navigated::No));
16553 };
16554 let head = self
16555 .selections
16556 .newest::<usize>(&self.display_snapshot(cx))
16557 .head();
16558 let buffer = self.buffer.read(cx);
16559 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16560 return Task::ready(Ok(Navigated::No));
16561 };
16562 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16563 return Task::ready(Ok(Navigated::No));
16564 };
16565
16566 cx.spawn_in(window, async move |editor, cx| {
16567 let Some(definitions) = definitions.await? else {
16568 return Ok(Navigated::No);
16569 };
16570 let navigated = editor
16571 .update_in(cx, |editor, window, cx| {
16572 editor.navigate_to_hover_links(
16573 Some(kind),
16574 definitions
16575 .into_iter()
16576 .filter(|location| {
16577 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16578 })
16579 .map(HoverLink::Text)
16580 .collect::<Vec<_>>(),
16581 split,
16582 window,
16583 cx,
16584 )
16585 })?
16586 .await?;
16587 anyhow::Ok(navigated)
16588 })
16589 }
16590
16591 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16592 let selection = self.selections.newest_anchor();
16593 let head = selection.head();
16594 let tail = selection.tail();
16595
16596 let Some((buffer, start_position)) =
16597 self.buffer.read(cx).text_anchor_for_position(head, cx)
16598 else {
16599 return;
16600 };
16601
16602 let end_position = if head != tail {
16603 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16604 return;
16605 };
16606 Some(pos)
16607 } else {
16608 None
16609 };
16610
16611 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16612 let url = if let Some(end_pos) = end_position {
16613 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16614 } else {
16615 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16616 };
16617
16618 if let Some(url) = url {
16619 cx.update(|window, cx| {
16620 if parse_zed_link(&url, cx).is_some() {
16621 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16622 } else {
16623 cx.open_url(&url);
16624 }
16625 })?;
16626 }
16627
16628 anyhow::Ok(())
16629 });
16630
16631 url_finder.detach();
16632 }
16633
16634 pub fn open_selected_filename(
16635 &mut self,
16636 _: &OpenSelectedFilename,
16637 window: &mut Window,
16638 cx: &mut Context<Self>,
16639 ) {
16640 let Some(workspace) = self.workspace() else {
16641 return;
16642 };
16643
16644 let position = self.selections.newest_anchor().head();
16645
16646 let Some((buffer, buffer_position)) =
16647 self.buffer.read(cx).text_anchor_for_position(position, cx)
16648 else {
16649 return;
16650 };
16651
16652 let project = self.project.clone();
16653
16654 cx.spawn_in(window, async move |_, cx| {
16655 let result = find_file(&buffer, project, buffer_position, cx).await;
16656
16657 if let Some((_, path)) = result {
16658 workspace
16659 .update_in(cx, |workspace, window, cx| {
16660 workspace.open_resolved_path(path, window, cx)
16661 })?
16662 .await?;
16663 }
16664 anyhow::Ok(())
16665 })
16666 .detach();
16667 }
16668
16669 pub(crate) fn navigate_to_hover_links(
16670 &mut self,
16671 kind: Option<GotoDefinitionKind>,
16672 definitions: Vec<HoverLink>,
16673 split: bool,
16674 window: &mut Window,
16675 cx: &mut Context<Editor>,
16676 ) -> Task<Result<Navigated>> {
16677 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16678 let mut first_url_or_file = None;
16679 let definitions: Vec<_> = definitions
16680 .into_iter()
16681 .filter_map(|def| match def {
16682 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16683 HoverLink::InlayHint(lsp_location, server_id) => {
16684 let computation =
16685 self.compute_target_location(lsp_location, server_id, window, cx);
16686 Some(cx.background_spawn(computation))
16687 }
16688 HoverLink::Url(url) => {
16689 first_url_or_file = Some(Either::Left(url));
16690 None
16691 }
16692 HoverLink::File(path) => {
16693 first_url_or_file = Some(Either::Right(path));
16694 None
16695 }
16696 })
16697 .collect();
16698
16699 let workspace = self.workspace();
16700
16701 cx.spawn_in(window, async move |editor, cx| {
16702 let locations: Vec<Location> = future::join_all(definitions)
16703 .await
16704 .into_iter()
16705 .filter_map(|location| location.transpose())
16706 .collect::<Result<_>>()
16707 .context("location tasks")?;
16708 let mut locations = cx.update(|_, cx| {
16709 locations
16710 .into_iter()
16711 .map(|location| {
16712 let buffer = location.buffer.read(cx);
16713 (location.buffer, location.range.to_point(buffer))
16714 })
16715 .into_group_map()
16716 })?;
16717 let mut num_locations = 0;
16718 for ranges in locations.values_mut() {
16719 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16720 ranges.dedup();
16721 num_locations += ranges.len();
16722 }
16723
16724 if num_locations > 1 {
16725 let Some(workspace) = workspace else {
16726 return Ok(Navigated::No);
16727 };
16728
16729 let tab_kind = match kind {
16730 Some(GotoDefinitionKind::Implementation) => "Implementations",
16731 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16732 Some(GotoDefinitionKind::Declaration) => "Declarations",
16733 Some(GotoDefinitionKind::Type) => "Types",
16734 };
16735 let title = editor
16736 .update_in(cx, |_, _, cx| {
16737 let target = locations
16738 .iter()
16739 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16740 .map(|(buffer, location)| {
16741 buffer
16742 .read(cx)
16743 .text_for_range(location.clone())
16744 .collect::<String>()
16745 })
16746 .filter(|text| !text.contains('\n'))
16747 .unique()
16748 .take(3)
16749 .join(", ");
16750 if target.is_empty() {
16751 tab_kind.to_owned()
16752 } else {
16753 format!("{tab_kind} for {target}")
16754 }
16755 })
16756 .context("buffer title")?;
16757
16758 let opened = workspace
16759 .update_in(cx, |workspace, window, cx| {
16760 Self::open_locations_in_multibuffer(
16761 workspace,
16762 locations,
16763 title,
16764 split,
16765 MultibufferSelectionMode::First,
16766 window,
16767 cx,
16768 )
16769 })
16770 .is_ok();
16771
16772 anyhow::Ok(Navigated::from_bool(opened))
16773 } else if num_locations == 0 {
16774 // If there is one url or file, open it directly
16775 match first_url_or_file {
16776 Some(Either::Left(url)) => {
16777 cx.update(|_, cx| cx.open_url(&url))?;
16778 Ok(Navigated::Yes)
16779 }
16780 Some(Either::Right(path)) => {
16781 let Some(workspace) = workspace else {
16782 return Ok(Navigated::No);
16783 };
16784
16785 workspace
16786 .update_in(cx, |workspace, window, cx| {
16787 workspace.open_resolved_path(path, window, cx)
16788 })?
16789 .await?;
16790 Ok(Navigated::Yes)
16791 }
16792 None => Ok(Navigated::No),
16793 }
16794 } else {
16795 let Some(workspace) = workspace else {
16796 return Ok(Navigated::No);
16797 };
16798
16799 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16800 let target_range = target_ranges.first().unwrap().clone();
16801
16802 editor.update_in(cx, |editor, window, cx| {
16803 let range = target_range.to_point(target_buffer.read(cx));
16804 let range = editor.range_for_match(&range);
16805 let range = collapse_multiline_range(range);
16806
16807 if !split
16808 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16809 {
16810 editor.go_to_singleton_buffer_range(range, window, cx);
16811 } else {
16812 let pane = workspace.read(cx).active_pane().clone();
16813 window.defer(cx, move |window, cx| {
16814 let target_editor: Entity<Self> =
16815 workspace.update(cx, |workspace, cx| {
16816 let pane = if split {
16817 workspace.adjacent_pane(window, cx)
16818 } else {
16819 workspace.active_pane().clone()
16820 };
16821
16822 workspace.open_project_item(
16823 pane,
16824 target_buffer.clone(),
16825 true,
16826 true,
16827 window,
16828 cx,
16829 )
16830 });
16831 target_editor.update(cx, |target_editor, cx| {
16832 // When selecting a definition in a different buffer, disable the nav history
16833 // to avoid creating a history entry at the previous cursor location.
16834 pane.update(cx, |pane, _| pane.disable_history());
16835 target_editor.go_to_singleton_buffer_range(range, window, cx);
16836 pane.update(cx, |pane, _| pane.enable_history());
16837 });
16838 });
16839 }
16840 Navigated::Yes
16841 })
16842 }
16843 })
16844 }
16845
16846 fn compute_target_location(
16847 &self,
16848 lsp_location: lsp::Location,
16849 server_id: LanguageServerId,
16850 window: &mut Window,
16851 cx: &mut Context<Self>,
16852 ) -> Task<anyhow::Result<Option<Location>>> {
16853 let Some(project) = self.project.clone() else {
16854 return Task::ready(Ok(None));
16855 };
16856
16857 cx.spawn_in(window, async move |editor, cx| {
16858 let location_task = editor.update(cx, |_, cx| {
16859 project.update(cx, |project, cx| {
16860 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16861 })
16862 })?;
16863 let location = Some({
16864 let target_buffer_handle = location_task.await.context("open local buffer")?;
16865 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16866 let target_start = target_buffer
16867 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16868 let target_end = target_buffer
16869 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16870 target_buffer.anchor_after(target_start)
16871 ..target_buffer.anchor_before(target_end)
16872 })?;
16873 Location {
16874 buffer: target_buffer_handle,
16875 range,
16876 }
16877 });
16878 Ok(location)
16879 })
16880 }
16881
16882 pub fn find_all_references(
16883 &mut self,
16884 _: &FindAllReferences,
16885 window: &mut Window,
16886 cx: &mut Context<Self>,
16887 ) -> Option<Task<Result<Navigated>>> {
16888 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16889 let multi_buffer = self.buffer.read(cx);
16890 let head = selection.head();
16891
16892 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16893 let head_anchor = multi_buffer_snapshot.anchor_at(
16894 head,
16895 if head < selection.tail() {
16896 Bias::Right
16897 } else {
16898 Bias::Left
16899 },
16900 );
16901
16902 match self
16903 .find_all_references_task_sources
16904 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16905 {
16906 Ok(_) => {
16907 log::info!(
16908 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16909 );
16910 return None;
16911 }
16912 Err(i) => {
16913 self.find_all_references_task_sources.insert(i, head_anchor);
16914 }
16915 }
16916
16917 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16918 let workspace = self.workspace()?;
16919 let project = workspace.read(cx).project().clone();
16920 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16921 Some(cx.spawn_in(window, async move |editor, cx| {
16922 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16923 if let Ok(i) = editor
16924 .find_all_references_task_sources
16925 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16926 {
16927 editor.find_all_references_task_sources.remove(i);
16928 }
16929 });
16930
16931 let Some(locations) = references.await? else {
16932 return anyhow::Ok(Navigated::No);
16933 };
16934 let mut locations = cx.update(|_, cx| {
16935 locations
16936 .into_iter()
16937 .map(|location| {
16938 let buffer = location.buffer.read(cx);
16939 (location.buffer, location.range.to_point(buffer))
16940 })
16941 .into_group_map()
16942 })?;
16943 if locations.is_empty() {
16944 return anyhow::Ok(Navigated::No);
16945 }
16946 for ranges in locations.values_mut() {
16947 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16948 ranges.dedup();
16949 }
16950
16951 workspace.update_in(cx, |workspace, window, cx| {
16952 let target = locations
16953 .iter()
16954 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16955 .map(|(buffer, location)| {
16956 buffer
16957 .read(cx)
16958 .text_for_range(location.clone())
16959 .collect::<String>()
16960 })
16961 .filter(|text| !text.contains('\n'))
16962 .unique()
16963 .take(3)
16964 .join(", ");
16965 let title = if target.is_empty() {
16966 "References".to_owned()
16967 } else {
16968 format!("References to {target}")
16969 };
16970 Self::open_locations_in_multibuffer(
16971 workspace,
16972 locations,
16973 title,
16974 false,
16975 MultibufferSelectionMode::First,
16976 window,
16977 cx,
16978 );
16979 Navigated::Yes
16980 })
16981 }))
16982 }
16983
16984 /// Opens a multibuffer with the given project locations in it
16985 pub fn open_locations_in_multibuffer(
16986 workspace: &mut Workspace,
16987 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
16988 title: String,
16989 split: bool,
16990 multibuffer_selection_mode: MultibufferSelectionMode,
16991 window: &mut Window,
16992 cx: &mut Context<Workspace>,
16993 ) {
16994 if locations.is_empty() {
16995 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16996 return;
16997 }
16998
16999 let capability = workspace.project().read(cx).capability();
17000 let mut ranges = <Vec<Range<Anchor>>>::new();
17001
17002 // a key to find existing multibuffer editors with the same set of locations
17003 // to prevent us from opening more and more multibuffer tabs for searches and the like
17004 let mut key = (title.clone(), vec![]);
17005 let excerpt_buffer = cx.new(|cx| {
17006 let key = &mut key.1;
17007 let mut multibuffer = MultiBuffer::new(capability);
17008 for (buffer, mut ranges_for_buffer) in locations {
17009 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17010 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17011 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17012 PathKey::for_buffer(&buffer, cx),
17013 buffer.clone(),
17014 ranges_for_buffer,
17015 multibuffer_context_lines(cx),
17016 cx,
17017 );
17018 ranges.extend(new_ranges)
17019 }
17020
17021 multibuffer.with_title(title)
17022 });
17023 let existing = workspace.active_pane().update(cx, |pane, cx| {
17024 pane.items()
17025 .filter_map(|item| item.downcast::<Editor>())
17026 .find(|editor| {
17027 editor
17028 .read(cx)
17029 .lookup_key
17030 .as_ref()
17031 .and_then(|it| {
17032 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17033 })
17034 .is_some_and(|it| *it == key)
17035 })
17036 });
17037 let editor = existing.unwrap_or_else(|| {
17038 cx.new(|cx| {
17039 let mut editor = Editor::for_multibuffer(
17040 excerpt_buffer,
17041 Some(workspace.project().clone()),
17042 window,
17043 cx,
17044 );
17045 editor.lookup_key = Some(Box::new(key));
17046 editor
17047 })
17048 });
17049 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17050 MultibufferSelectionMode::First => {
17051 if let Some(first_range) = ranges.first() {
17052 editor.change_selections(
17053 SelectionEffects::no_scroll(),
17054 window,
17055 cx,
17056 |selections| {
17057 selections.clear_disjoint();
17058 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17059 },
17060 );
17061 }
17062 editor.highlight_background::<Self>(
17063 &ranges,
17064 |theme| theme.colors().editor_highlighted_line_background,
17065 cx,
17066 );
17067 }
17068 MultibufferSelectionMode::All => {
17069 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17070 selections.clear_disjoint();
17071 selections.select_anchor_ranges(ranges);
17072 });
17073 }
17074 });
17075
17076 let item = Box::new(editor);
17077 let item_id = item.item_id();
17078
17079 if split {
17080 let pane = workspace.adjacent_pane(window, cx);
17081 workspace.add_item(pane, item, None, true, true, window, cx);
17082 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17083 let (preview_item_id, preview_item_idx) =
17084 workspace.active_pane().read_with(cx, |pane, _| {
17085 (pane.preview_item_id(), pane.preview_item_idx())
17086 });
17087
17088 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17089
17090 if let Some(preview_item_id) = preview_item_id {
17091 workspace.active_pane().update(cx, |pane, cx| {
17092 pane.remove_item(preview_item_id, false, false, window, cx);
17093 });
17094 }
17095 } else {
17096 workspace.add_item_to_active_pane(item, None, true, window, cx);
17097 }
17098 workspace.active_pane().update(cx, |pane, cx| {
17099 pane.set_preview_item_id(Some(item_id), cx);
17100 });
17101 }
17102
17103 pub fn rename(
17104 &mut self,
17105 _: &Rename,
17106 window: &mut Window,
17107 cx: &mut Context<Self>,
17108 ) -> Option<Task<Result<()>>> {
17109 use language::ToOffset as _;
17110
17111 let provider = self.semantics_provider.clone()?;
17112 let selection = self.selections.newest_anchor().clone();
17113 let (cursor_buffer, cursor_buffer_position) = self
17114 .buffer
17115 .read(cx)
17116 .text_anchor_for_position(selection.head(), cx)?;
17117 let (tail_buffer, cursor_buffer_position_end) = self
17118 .buffer
17119 .read(cx)
17120 .text_anchor_for_position(selection.tail(), cx)?;
17121 if tail_buffer != cursor_buffer {
17122 return None;
17123 }
17124
17125 let snapshot = cursor_buffer.read(cx).snapshot();
17126 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17127 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17128 let prepare_rename = provider
17129 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17130 .unwrap_or_else(|| Task::ready(Ok(None)));
17131 drop(snapshot);
17132
17133 Some(cx.spawn_in(window, async move |this, cx| {
17134 let rename_range = if let Some(range) = prepare_rename.await? {
17135 Some(range)
17136 } else {
17137 this.update(cx, |this, cx| {
17138 let buffer = this.buffer.read(cx).snapshot(cx);
17139 let mut buffer_highlights = this
17140 .document_highlights_for_position(selection.head(), &buffer)
17141 .filter(|highlight| {
17142 highlight.start.excerpt_id == selection.head().excerpt_id
17143 && highlight.end.excerpt_id == selection.head().excerpt_id
17144 });
17145 buffer_highlights
17146 .next()
17147 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17148 })?
17149 };
17150 if let Some(rename_range) = rename_range {
17151 this.update_in(cx, |this, window, cx| {
17152 let snapshot = cursor_buffer.read(cx).snapshot();
17153 let rename_buffer_range = rename_range.to_offset(&snapshot);
17154 let cursor_offset_in_rename_range =
17155 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17156 let cursor_offset_in_rename_range_end =
17157 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17158
17159 this.take_rename(false, window, cx);
17160 let buffer = this.buffer.read(cx).read(cx);
17161 let cursor_offset = selection.head().to_offset(&buffer);
17162 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17163 let rename_end = rename_start + rename_buffer_range.len();
17164 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17165 let mut old_highlight_id = None;
17166 let old_name: Arc<str> = buffer
17167 .chunks(rename_start..rename_end, true)
17168 .map(|chunk| {
17169 if old_highlight_id.is_none() {
17170 old_highlight_id = chunk.syntax_highlight_id;
17171 }
17172 chunk.text
17173 })
17174 .collect::<String>()
17175 .into();
17176
17177 drop(buffer);
17178
17179 // Position the selection in the rename editor so that it matches the current selection.
17180 this.show_local_selections = false;
17181 let rename_editor = cx.new(|cx| {
17182 let mut editor = Editor::single_line(window, cx);
17183 editor.buffer.update(cx, |buffer, cx| {
17184 buffer.edit([(0..0, old_name.clone())], None, cx)
17185 });
17186 let rename_selection_range = match cursor_offset_in_rename_range
17187 .cmp(&cursor_offset_in_rename_range_end)
17188 {
17189 Ordering::Equal => {
17190 editor.select_all(&SelectAll, window, cx);
17191 return editor;
17192 }
17193 Ordering::Less => {
17194 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17195 }
17196 Ordering::Greater => {
17197 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17198 }
17199 };
17200 if rename_selection_range.end > old_name.len() {
17201 editor.select_all(&SelectAll, window, cx);
17202 } else {
17203 editor.change_selections(Default::default(), window, cx, |s| {
17204 s.select_ranges([rename_selection_range]);
17205 });
17206 }
17207 editor
17208 });
17209 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17210 if e == &EditorEvent::Focused {
17211 cx.emit(EditorEvent::FocusedIn)
17212 }
17213 })
17214 .detach();
17215
17216 let write_highlights =
17217 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17218 let read_highlights =
17219 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17220 let ranges = write_highlights
17221 .iter()
17222 .flat_map(|(_, ranges)| ranges.iter())
17223 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17224 .cloned()
17225 .collect();
17226
17227 this.highlight_text::<Rename>(
17228 ranges,
17229 HighlightStyle {
17230 fade_out: Some(0.6),
17231 ..Default::default()
17232 },
17233 cx,
17234 );
17235 let rename_focus_handle = rename_editor.focus_handle(cx);
17236 window.focus(&rename_focus_handle);
17237 let block_id = this.insert_blocks(
17238 [BlockProperties {
17239 style: BlockStyle::Flex,
17240 placement: BlockPlacement::Below(range.start),
17241 height: Some(1),
17242 render: Arc::new({
17243 let rename_editor = rename_editor.clone();
17244 move |cx: &mut BlockContext| {
17245 let mut text_style = cx.editor_style.text.clone();
17246 if let Some(highlight_style) = old_highlight_id
17247 .and_then(|h| h.style(&cx.editor_style.syntax))
17248 {
17249 text_style = text_style.highlight(highlight_style);
17250 }
17251 div()
17252 .block_mouse_except_scroll()
17253 .pl(cx.anchor_x)
17254 .child(EditorElement::new(
17255 &rename_editor,
17256 EditorStyle {
17257 background: cx.theme().system().transparent,
17258 local_player: cx.editor_style.local_player,
17259 text: text_style,
17260 scrollbar_width: cx.editor_style.scrollbar_width,
17261 syntax: cx.editor_style.syntax.clone(),
17262 status: cx.editor_style.status.clone(),
17263 inlay_hints_style: HighlightStyle {
17264 font_weight: Some(FontWeight::BOLD),
17265 ..make_inlay_hints_style(cx.app)
17266 },
17267 edit_prediction_styles: make_suggestion_styles(
17268 cx.app,
17269 ),
17270 ..EditorStyle::default()
17271 },
17272 ))
17273 .into_any_element()
17274 }
17275 }),
17276 priority: 0,
17277 }],
17278 Some(Autoscroll::fit()),
17279 cx,
17280 )[0];
17281 this.pending_rename = Some(RenameState {
17282 range,
17283 old_name,
17284 editor: rename_editor,
17285 block_id,
17286 });
17287 })?;
17288 }
17289
17290 Ok(())
17291 }))
17292 }
17293
17294 pub fn confirm_rename(
17295 &mut self,
17296 _: &ConfirmRename,
17297 window: &mut Window,
17298 cx: &mut Context<Self>,
17299 ) -> Option<Task<Result<()>>> {
17300 let rename = self.take_rename(false, window, cx)?;
17301 let workspace = self.workspace()?.downgrade();
17302 let (buffer, start) = self
17303 .buffer
17304 .read(cx)
17305 .text_anchor_for_position(rename.range.start, cx)?;
17306 let (end_buffer, _) = self
17307 .buffer
17308 .read(cx)
17309 .text_anchor_for_position(rename.range.end, cx)?;
17310 if buffer != end_buffer {
17311 return None;
17312 }
17313
17314 let old_name = rename.old_name;
17315 let new_name = rename.editor.read(cx).text(cx);
17316
17317 let rename = self.semantics_provider.as_ref()?.perform_rename(
17318 &buffer,
17319 start,
17320 new_name.clone(),
17321 cx,
17322 )?;
17323
17324 Some(cx.spawn_in(window, async move |editor, cx| {
17325 let project_transaction = rename.await?;
17326 Self::open_project_transaction(
17327 &editor,
17328 workspace,
17329 project_transaction,
17330 format!("Rename: {} → {}", old_name, new_name),
17331 cx,
17332 )
17333 .await?;
17334
17335 editor.update(cx, |editor, cx| {
17336 editor.refresh_document_highlights(cx);
17337 })?;
17338 Ok(())
17339 }))
17340 }
17341
17342 fn take_rename(
17343 &mut self,
17344 moving_cursor: bool,
17345 window: &mut Window,
17346 cx: &mut Context<Self>,
17347 ) -> Option<RenameState> {
17348 let rename = self.pending_rename.take()?;
17349 if rename.editor.focus_handle(cx).is_focused(window) {
17350 window.focus(&self.focus_handle);
17351 }
17352
17353 self.remove_blocks(
17354 [rename.block_id].into_iter().collect(),
17355 Some(Autoscroll::fit()),
17356 cx,
17357 );
17358 self.clear_highlights::<Rename>(cx);
17359 self.show_local_selections = true;
17360
17361 if moving_cursor {
17362 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17363 editor
17364 .selections
17365 .newest::<usize>(&editor.display_snapshot(cx))
17366 .head()
17367 });
17368
17369 // Update the selection to match the position of the selection inside
17370 // the rename editor.
17371 let snapshot = self.buffer.read(cx).read(cx);
17372 let rename_range = rename.range.to_offset(&snapshot);
17373 let cursor_in_editor = snapshot
17374 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17375 .min(rename_range.end);
17376 drop(snapshot);
17377
17378 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17379 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17380 });
17381 } else {
17382 self.refresh_document_highlights(cx);
17383 }
17384
17385 Some(rename)
17386 }
17387
17388 pub fn pending_rename(&self) -> Option<&RenameState> {
17389 self.pending_rename.as_ref()
17390 }
17391
17392 fn format(
17393 &mut self,
17394 _: &Format,
17395 window: &mut Window,
17396 cx: &mut Context<Self>,
17397 ) -> Option<Task<Result<()>>> {
17398 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17399
17400 let project = match &self.project {
17401 Some(project) => project.clone(),
17402 None => return None,
17403 };
17404
17405 Some(self.perform_format(
17406 project,
17407 FormatTrigger::Manual,
17408 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17409 window,
17410 cx,
17411 ))
17412 }
17413
17414 fn format_selections(
17415 &mut self,
17416 _: &FormatSelections,
17417 window: &mut Window,
17418 cx: &mut Context<Self>,
17419 ) -> Option<Task<Result<()>>> {
17420 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17421
17422 let project = match &self.project {
17423 Some(project) => project.clone(),
17424 None => return None,
17425 };
17426
17427 let ranges = self
17428 .selections
17429 .all_adjusted(&self.display_snapshot(cx))
17430 .into_iter()
17431 .map(|selection| selection.range())
17432 .collect_vec();
17433
17434 Some(self.perform_format(
17435 project,
17436 FormatTrigger::Manual,
17437 FormatTarget::Ranges(ranges),
17438 window,
17439 cx,
17440 ))
17441 }
17442
17443 fn perform_format(
17444 &mut self,
17445 project: Entity<Project>,
17446 trigger: FormatTrigger,
17447 target: FormatTarget,
17448 window: &mut Window,
17449 cx: &mut Context<Self>,
17450 ) -> Task<Result<()>> {
17451 let buffer = self.buffer.clone();
17452 let (buffers, target) = match target {
17453 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17454 FormatTarget::Ranges(selection_ranges) => {
17455 let multi_buffer = buffer.read(cx);
17456 let snapshot = multi_buffer.read(cx);
17457 let mut buffers = HashSet::default();
17458 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17459 BTreeMap::new();
17460 for selection_range in selection_ranges {
17461 for (buffer, buffer_range, _) in
17462 snapshot.range_to_buffer_ranges(selection_range)
17463 {
17464 let buffer_id = buffer.remote_id();
17465 let start = buffer.anchor_before(buffer_range.start);
17466 let end = buffer.anchor_after(buffer_range.end);
17467 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17468 buffer_id_to_ranges
17469 .entry(buffer_id)
17470 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17471 .or_insert_with(|| vec![start..end]);
17472 }
17473 }
17474 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17475 }
17476 };
17477
17478 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17479 let selections_prev = transaction_id_prev
17480 .and_then(|transaction_id_prev| {
17481 // default to selections as they were after the last edit, if we have them,
17482 // instead of how they are now.
17483 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17484 // will take you back to where you made the last edit, instead of staying where you scrolled
17485 self.selection_history
17486 .transaction(transaction_id_prev)
17487 .map(|t| t.0.clone())
17488 })
17489 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17490
17491 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17492 let format = project.update(cx, |project, cx| {
17493 project.format(buffers, target, true, trigger, cx)
17494 });
17495
17496 cx.spawn_in(window, async move |editor, cx| {
17497 let transaction = futures::select_biased! {
17498 transaction = format.log_err().fuse() => transaction,
17499 () = timeout => {
17500 log::warn!("timed out waiting for formatting");
17501 None
17502 }
17503 };
17504
17505 buffer
17506 .update(cx, |buffer, cx| {
17507 if let Some(transaction) = transaction
17508 && !buffer.is_singleton()
17509 {
17510 buffer.push_transaction(&transaction.0, cx);
17511 }
17512 cx.notify();
17513 })
17514 .ok();
17515
17516 if let Some(transaction_id_now) =
17517 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17518 {
17519 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17520 if has_new_transaction {
17521 _ = editor.update(cx, |editor, _| {
17522 editor
17523 .selection_history
17524 .insert_transaction(transaction_id_now, selections_prev);
17525 });
17526 }
17527 }
17528
17529 Ok(())
17530 })
17531 }
17532
17533 fn organize_imports(
17534 &mut self,
17535 _: &OrganizeImports,
17536 window: &mut Window,
17537 cx: &mut Context<Self>,
17538 ) -> Option<Task<Result<()>>> {
17539 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17540 let project = match &self.project {
17541 Some(project) => project.clone(),
17542 None => return None,
17543 };
17544 Some(self.perform_code_action_kind(
17545 project,
17546 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17547 window,
17548 cx,
17549 ))
17550 }
17551
17552 fn perform_code_action_kind(
17553 &mut self,
17554 project: Entity<Project>,
17555 kind: CodeActionKind,
17556 window: &mut Window,
17557 cx: &mut Context<Self>,
17558 ) -> Task<Result<()>> {
17559 let buffer = self.buffer.clone();
17560 let buffers = buffer.read(cx).all_buffers();
17561 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17562 let apply_action = project.update(cx, |project, cx| {
17563 project.apply_code_action_kind(buffers, kind, true, cx)
17564 });
17565 cx.spawn_in(window, async move |_, cx| {
17566 let transaction = futures::select_biased! {
17567 () = timeout => {
17568 log::warn!("timed out waiting for executing code action");
17569 None
17570 }
17571 transaction = apply_action.log_err().fuse() => transaction,
17572 };
17573 buffer
17574 .update(cx, |buffer, cx| {
17575 // check if we need this
17576 if let Some(transaction) = transaction
17577 && !buffer.is_singleton()
17578 {
17579 buffer.push_transaction(&transaction.0, cx);
17580 }
17581 cx.notify();
17582 })
17583 .ok();
17584 Ok(())
17585 })
17586 }
17587
17588 pub fn restart_language_server(
17589 &mut self,
17590 _: &RestartLanguageServer,
17591 _: &mut Window,
17592 cx: &mut Context<Self>,
17593 ) {
17594 if let Some(project) = self.project.clone() {
17595 self.buffer.update(cx, |multi_buffer, cx| {
17596 project.update(cx, |project, cx| {
17597 project.restart_language_servers_for_buffers(
17598 multi_buffer.all_buffers().into_iter().collect(),
17599 HashSet::default(),
17600 cx,
17601 );
17602 });
17603 })
17604 }
17605 }
17606
17607 pub fn stop_language_server(
17608 &mut self,
17609 _: &StopLanguageServer,
17610 _: &mut Window,
17611 cx: &mut Context<Self>,
17612 ) {
17613 if let Some(project) = self.project.clone() {
17614 self.buffer.update(cx, |multi_buffer, cx| {
17615 project.update(cx, |project, cx| {
17616 project.stop_language_servers_for_buffers(
17617 multi_buffer.all_buffers().into_iter().collect(),
17618 HashSet::default(),
17619 cx,
17620 );
17621 cx.emit(project::Event::RefreshInlayHints);
17622 });
17623 });
17624 }
17625 }
17626
17627 fn cancel_language_server_work(
17628 workspace: &mut Workspace,
17629 _: &actions::CancelLanguageServerWork,
17630 _: &mut Window,
17631 cx: &mut Context<Workspace>,
17632 ) {
17633 let project = workspace.project();
17634 let buffers = workspace
17635 .active_item(cx)
17636 .and_then(|item| item.act_as::<Editor>(cx))
17637 .map_or(HashSet::default(), |editor| {
17638 editor.read(cx).buffer.read(cx).all_buffers()
17639 });
17640 project.update(cx, |project, cx| {
17641 project.cancel_language_server_work_for_buffers(buffers, cx);
17642 });
17643 }
17644
17645 fn show_character_palette(
17646 &mut self,
17647 _: &ShowCharacterPalette,
17648 window: &mut Window,
17649 _: &mut Context<Self>,
17650 ) {
17651 window.show_character_palette();
17652 }
17653
17654 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17655 if !self.diagnostics_enabled() {
17656 return;
17657 }
17658
17659 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17660 let buffer = self.buffer.read(cx).snapshot(cx);
17661 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17662 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17663 let is_valid = buffer
17664 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17665 .any(|entry| {
17666 entry.diagnostic.is_primary
17667 && !entry.range.is_empty()
17668 && entry.range.start == primary_range_start
17669 && entry.diagnostic.message == active_diagnostics.active_message
17670 });
17671
17672 if !is_valid {
17673 self.dismiss_diagnostics(cx);
17674 }
17675 }
17676 }
17677
17678 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17679 match &self.active_diagnostics {
17680 ActiveDiagnostic::Group(group) => Some(group),
17681 _ => None,
17682 }
17683 }
17684
17685 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17686 if !self.diagnostics_enabled() {
17687 return;
17688 }
17689 self.dismiss_diagnostics(cx);
17690 self.active_diagnostics = ActiveDiagnostic::All;
17691 }
17692
17693 fn activate_diagnostics(
17694 &mut self,
17695 buffer_id: BufferId,
17696 diagnostic: DiagnosticEntryRef<'_, usize>,
17697 window: &mut Window,
17698 cx: &mut Context<Self>,
17699 ) {
17700 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17701 return;
17702 }
17703 self.dismiss_diagnostics(cx);
17704 let snapshot = self.snapshot(window, cx);
17705 let buffer = self.buffer.read(cx).snapshot(cx);
17706 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17707 return;
17708 };
17709
17710 let diagnostic_group = buffer
17711 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17712 .collect::<Vec<_>>();
17713
17714 let blocks =
17715 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17716
17717 let blocks = self.display_map.update(cx, |display_map, cx| {
17718 display_map.insert_blocks(blocks, cx).into_iter().collect()
17719 });
17720 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17721 active_range: buffer.anchor_before(diagnostic.range.start)
17722 ..buffer.anchor_after(diagnostic.range.end),
17723 active_message: diagnostic.diagnostic.message.clone(),
17724 group_id: diagnostic.diagnostic.group_id,
17725 blocks,
17726 });
17727 cx.notify();
17728 }
17729
17730 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17731 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17732 return;
17733 };
17734
17735 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17736 if let ActiveDiagnostic::Group(group) = prev {
17737 self.display_map.update(cx, |display_map, cx| {
17738 display_map.remove_blocks(group.blocks, cx);
17739 });
17740 cx.notify();
17741 }
17742 }
17743
17744 /// Disable inline diagnostics rendering for this editor.
17745 pub fn disable_inline_diagnostics(&mut self) {
17746 self.inline_diagnostics_enabled = false;
17747 self.inline_diagnostics_update = Task::ready(());
17748 self.inline_diagnostics.clear();
17749 }
17750
17751 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17752 self.diagnostics_enabled = false;
17753 self.dismiss_diagnostics(cx);
17754 self.inline_diagnostics_update = Task::ready(());
17755 self.inline_diagnostics.clear();
17756 }
17757
17758 pub fn disable_word_completions(&mut self) {
17759 self.word_completions_enabled = false;
17760 }
17761
17762 pub fn diagnostics_enabled(&self) -> bool {
17763 self.diagnostics_enabled && self.mode.is_full()
17764 }
17765
17766 pub fn inline_diagnostics_enabled(&self) -> bool {
17767 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17768 }
17769
17770 pub fn show_inline_diagnostics(&self) -> bool {
17771 self.show_inline_diagnostics
17772 }
17773
17774 pub fn toggle_inline_diagnostics(
17775 &mut self,
17776 _: &ToggleInlineDiagnostics,
17777 window: &mut Window,
17778 cx: &mut Context<Editor>,
17779 ) {
17780 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17781 self.refresh_inline_diagnostics(false, window, cx);
17782 }
17783
17784 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17785 self.diagnostics_max_severity = severity;
17786 self.display_map.update(cx, |display_map, _| {
17787 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17788 });
17789 }
17790
17791 pub fn toggle_diagnostics(
17792 &mut self,
17793 _: &ToggleDiagnostics,
17794 window: &mut Window,
17795 cx: &mut Context<Editor>,
17796 ) {
17797 if !self.diagnostics_enabled() {
17798 return;
17799 }
17800
17801 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17802 EditorSettings::get_global(cx)
17803 .diagnostics_max_severity
17804 .filter(|severity| severity != &DiagnosticSeverity::Off)
17805 .unwrap_or(DiagnosticSeverity::Hint)
17806 } else {
17807 DiagnosticSeverity::Off
17808 };
17809 self.set_max_diagnostics_severity(new_severity, cx);
17810 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17811 self.active_diagnostics = ActiveDiagnostic::None;
17812 self.inline_diagnostics_update = Task::ready(());
17813 self.inline_diagnostics.clear();
17814 } else {
17815 self.refresh_inline_diagnostics(false, window, cx);
17816 }
17817
17818 cx.notify();
17819 }
17820
17821 pub fn toggle_minimap(
17822 &mut self,
17823 _: &ToggleMinimap,
17824 window: &mut Window,
17825 cx: &mut Context<Editor>,
17826 ) {
17827 if self.supports_minimap(cx) {
17828 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17829 }
17830 }
17831
17832 fn refresh_inline_diagnostics(
17833 &mut self,
17834 debounce: bool,
17835 window: &mut Window,
17836 cx: &mut Context<Self>,
17837 ) {
17838 let max_severity = ProjectSettings::get_global(cx)
17839 .diagnostics
17840 .inline
17841 .max_severity
17842 .unwrap_or(self.diagnostics_max_severity);
17843
17844 if !self.inline_diagnostics_enabled()
17845 || !self.show_inline_diagnostics
17846 || max_severity == DiagnosticSeverity::Off
17847 {
17848 self.inline_diagnostics_update = Task::ready(());
17849 self.inline_diagnostics.clear();
17850 return;
17851 }
17852
17853 let debounce_ms = ProjectSettings::get_global(cx)
17854 .diagnostics
17855 .inline
17856 .update_debounce_ms;
17857 let debounce = if debounce && debounce_ms > 0 {
17858 Some(Duration::from_millis(debounce_ms))
17859 } else {
17860 None
17861 };
17862 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17863 if let Some(debounce) = debounce {
17864 cx.background_executor().timer(debounce).await;
17865 }
17866 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17867 editor
17868 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17869 .ok()
17870 }) else {
17871 return;
17872 };
17873
17874 let new_inline_diagnostics = cx
17875 .background_spawn(async move {
17876 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17877 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17878 let message = diagnostic_entry
17879 .diagnostic
17880 .message
17881 .split_once('\n')
17882 .map(|(line, _)| line)
17883 .map(SharedString::new)
17884 .unwrap_or_else(|| {
17885 SharedString::new(&*diagnostic_entry.diagnostic.message)
17886 });
17887 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17888 let (Ok(i) | Err(i)) = inline_diagnostics
17889 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17890 inline_diagnostics.insert(
17891 i,
17892 (
17893 start_anchor,
17894 InlineDiagnostic {
17895 message,
17896 group_id: diagnostic_entry.diagnostic.group_id,
17897 start: diagnostic_entry.range.start.to_point(&snapshot),
17898 is_primary: diagnostic_entry.diagnostic.is_primary,
17899 severity: diagnostic_entry.diagnostic.severity,
17900 },
17901 ),
17902 );
17903 }
17904 inline_diagnostics
17905 })
17906 .await;
17907
17908 editor
17909 .update(cx, |editor, cx| {
17910 editor.inline_diagnostics = new_inline_diagnostics;
17911 cx.notify();
17912 })
17913 .ok();
17914 });
17915 }
17916
17917 fn pull_diagnostics(
17918 &mut self,
17919 buffer_id: Option<BufferId>,
17920 window: &Window,
17921 cx: &mut Context<Self>,
17922 ) -> Option<()> {
17923 if self.ignore_lsp_data() {
17924 return None;
17925 }
17926 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17927 .diagnostics
17928 .lsp_pull_diagnostics;
17929 if !pull_diagnostics_settings.enabled {
17930 return None;
17931 }
17932 let project = self.project()?.downgrade();
17933 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17934 let mut buffers = self.buffer.read(cx).all_buffers();
17935 buffers.retain(|buffer| {
17936 let buffer_id_to_retain = buffer.read(cx).remote_id();
17937 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
17938 && self.registered_buffers.contains_key(&buffer_id_to_retain)
17939 });
17940 if buffers.is_empty() {
17941 self.pull_diagnostics_task = Task::ready(());
17942 return None;
17943 }
17944
17945 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17946 cx.background_executor().timer(debounce).await;
17947
17948 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17949 buffers
17950 .into_iter()
17951 .filter_map(|buffer| {
17952 project
17953 .update(cx, |project, cx| {
17954 project.lsp_store().update(cx, |lsp_store, cx| {
17955 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17956 })
17957 })
17958 .ok()
17959 })
17960 .collect::<FuturesUnordered<_>>()
17961 }) else {
17962 return;
17963 };
17964
17965 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17966 match pull_task {
17967 Ok(()) => {
17968 if editor
17969 .update_in(cx, |editor, window, cx| {
17970 editor.update_diagnostics_state(window, cx);
17971 })
17972 .is_err()
17973 {
17974 return;
17975 }
17976 }
17977 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17978 }
17979 }
17980 });
17981
17982 Some(())
17983 }
17984
17985 pub fn set_selections_from_remote(
17986 &mut self,
17987 selections: Vec<Selection<Anchor>>,
17988 pending_selection: Option<Selection<Anchor>>,
17989 window: &mut Window,
17990 cx: &mut Context<Self>,
17991 ) {
17992 let old_cursor_position = self.selections.newest_anchor().head();
17993 self.selections.change_with(cx, |s| {
17994 s.select_anchors(selections);
17995 if let Some(pending_selection) = pending_selection {
17996 s.set_pending(pending_selection, SelectMode::Character);
17997 } else {
17998 s.clear_pending();
17999 }
18000 });
18001 self.selections_did_change(
18002 false,
18003 &old_cursor_position,
18004 SelectionEffects::default(),
18005 window,
18006 cx,
18007 );
18008 }
18009
18010 pub fn transact(
18011 &mut self,
18012 window: &mut Window,
18013 cx: &mut Context<Self>,
18014 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18015 ) -> Option<TransactionId> {
18016 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18017 this.start_transaction_at(Instant::now(), window, cx);
18018 update(this, window, cx);
18019 this.end_transaction_at(Instant::now(), cx)
18020 })
18021 }
18022
18023 pub fn start_transaction_at(
18024 &mut self,
18025 now: Instant,
18026 window: &mut Window,
18027 cx: &mut Context<Self>,
18028 ) -> Option<TransactionId> {
18029 self.end_selection(window, cx);
18030 if let Some(tx_id) = self
18031 .buffer
18032 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18033 {
18034 self.selection_history
18035 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18036 cx.emit(EditorEvent::TransactionBegun {
18037 transaction_id: tx_id,
18038 });
18039 Some(tx_id)
18040 } else {
18041 None
18042 }
18043 }
18044
18045 pub fn end_transaction_at(
18046 &mut self,
18047 now: Instant,
18048 cx: &mut Context<Self>,
18049 ) -> Option<TransactionId> {
18050 if let Some(transaction_id) = self
18051 .buffer
18052 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18053 {
18054 if let Some((_, end_selections)) =
18055 self.selection_history.transaction_mut(transaction_id)
18056 {
18057 *end_selections = Some(self.selections.disjoint_anchors_arc());
18058 } else {
18059 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18060 }
18061
18062 cx.emit(EditorEvent::Edited { transaction_id });
18063 Some(transaction_id)
18064 } else {
18065 None
18066 }
18067 }
18068
18069 pub fn modify_transaction_selection_history(
18070 &mut self,
18071 transaction_id: TransactionId,
18072 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18073 ) -> bool {
18074 self.selection_history
18075 .transaction_mut(transaction_id)
18076 .map(modify)
18077 .is_some()
18078 }
18079
18080 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18081 if self.selection_mark_mode {
18082 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18083 s.move_with(|_, sel| {
18084 sel.collapse_to(sel.head(), SelectionGoal::None);
18085 });
18086 })
18087 }
18088 self.selection_mark_mode = true;
18089 cx.notify();
18090 }
18091
18092 pub fn swap_selection_ends(
18093 &mut self,
18094 _: &actions::SwapSelectionEnds,
18095 window: &mut Window,
18096 cx: &mut Context<Self>,
18097 ) {
18098 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18099 s.move_with(|_, sel| {
18100 if sel.start != sel.end {
18101 sel.reversed = !sel.reversed
18102 }
18103 });
18104 });
18105 self.request_autoscroll(Autoscroll::newest(), cx);
18106 cx.notify();
18107 }
18108
18109 pub fn toggle_focus(
18110 workspace: &mut Workspace,
18111 _: &actions::ToggleFocus,
18112 window: &mut Window,
18113 cx: &mut Context<Workspace>,
18114 ) {
18115 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18116 return;
18117 };
18118 workspace.activate_item(&item, true, true, window, cx);
18119 }
18120
18121 pub fn toggle_fold(
18122 &mut self,
18123 _: &actions::ToggleFold,
18124 window: &mut Window,
18125 cx: &mut Context<Self>,
18126 ) {
18127 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18128 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18129 let selection = self.selections.newest::<Point>(&display_map);
18130
18131 let range = if selection.is_empty() {
18132 let point = selection.head().to_display_point(&display_map);
18133 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18134 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18135 .to_point(&display_map);
18136 start..end
18137 } else {
18138 selection.range()
18139 };
18140 if display_map.folds_in_range(range).next().is_some() {
18141 self.unfold_lines(&Default::default(), window, cx)
18142 } else {
18143 self.fold(&Default::default(), window, cx)
18144 }
18145 } else {
18146 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18147 let buffer_ids: HashSet<_> = self
18148 .selections
18149 .disjoint_anchor_ranges()
18150 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18151 .collect();
18152
18153 let should_unfold = buffer_ids
18154 .iter()
18155 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18156
18157 for buffer_id in buffer_ids {
18158 if should_unfold {
18159 self.unfold_buffer(buffer_id, cx);
18160 } else {
18161 self.fold_buffer(buffer_id, cx);
18162 }
18163 }
18164 }
18165 }
18166
18167 pub fn toggle_fold_recursive(
18168 &mut self,
18169 _: &actions::ToggleFoldRecursive,
18170 window: &mut Window,
18171 cx: &mut Context<Self>,
18172 ) {
18173 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18174
18175 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18176 let range = if selection.is_empty() {
18177 let point = selection.head().to_display_point(&display_map);
18178 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18179 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18180 .to_point(&display_map);
18181 start..end
18182 } else {
18183 selection.range()
18184 };
18185 if display_map.folds_in_range(range).next().is_some() {
18186 self.unfold_recursive(&Default::default(), window, cx)
18187 } else {
18188 self.fold_recursive(&Default::default(), window, cx)
18189 }
18190 }
18191
18192 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18193 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18194 let mut to_fold = Vec::new();
18195 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18196 let selections = self.selections.all_adjusted(&display_map);
18197
18198 for selection in selections {
18199 let range = selection.range().sorted();
18200 let buffer_start_row = range.start.row;
18201
18202 if range.start.row != range.end.row {
18203 let mut found = false;
18204 let mut row = range.start.row;
18205 while row <= range.end.row {
18206 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18207 {
18208 found = true;
18209 row = crease.range().end.row + 1;
18210 to_fold.push(crease);
18211 } else {
18212 row += 1
18213 }
18214 }
18215 if found {
18216 continue;
18217 }
18218 }
18219
18220 for row in (0..=range.start.row).rev() {
18221 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18222 && crease.range().end.row >= buffer_start_row
18223 {
18224 to_fold.push(crease);
18225 if row <= range.start.row {
18226 break;
18227 }
18228 }
18229 }
18230 }
18231
18232 self.fold_creases(to_fold, true, window, cx);
18233 } else {
18234 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18235 let buffer_ids = self
18236 .selections
18237 .disjoint_anchor_ranges()
18238 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18239 .collect::<HashSet<_>>();
18240 for buffer_id in buffer_ids {
18241 self.fold_buffer(buffer_id, cx);
18242 }
18243 }
18244 }
18245
18246 pub fn toggle_fold_all(
18247 &mut self,
18248 _: &actions::ToggleFoldAll,
18249 window: &mut Window,
18250 cx: &mut Context<Self>,
18251 ) {
18252 if self.buffer.read(cx).is_singleton() {
18253 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18254 let has_folds = display_map
18255 .folds_in_range(0..display_map.buffer_snapshot().len())
18256 .next()
18257 .is_some();
18258
18259 if has_folds {
18260 self.unfold_all(&actions::UnfoldAll, window, cx);
18261 } else {
18262 self.fold_all(&actions::FoldAll, window, cx);
18263 }
18264 } else {
18265 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18266 let should_unfold = buffer_ids
18267 .iter()
18268 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18269
18270 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18271 editor
18272 .update_in(cx, |editor, _, cx| {
18273 for buffer_id in buffer_ids {
18274 if should_unfold {
18275 editor.unfold_buffer(buffer_id, cx);
18276 } else {
18277 editor.fold_buffer(buffer_id, cx);
18278 }
18279 }
18280 })
18281 .ok();
18282 });
18283 }
18284 }
18285
18286 fn fold_at_level(
18287 &mut self,
18288 fold_at: &FoldAtLevel,
18289 window: &mut Window,
18290 cx: &mut Context<Self>,
18291 ) {
18292 if !self.buffer.read(cx).is_singleton() {
18293 return;
18294 }
18295
18296 let fold_at_level = fold_at.0;
18297 let snapshot = self.buffer.read(cx).snapshot(cx);
18298 let mut to_fold = Vec::new();
18299 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18300
18301 let row_ranges_to_keep: Vec<Range<u32>> = self
18302 .selections
18303 .all::<Point>(&self.display_snapshot(cx))
18304 .into_iter()
18305 .map(|sel| sel.start.row..sel.end.row)
18306 .collect();
18307
18308 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18309 while start_row < end_row {
18310 match self
18311 .snapshot(window, cx)
18312 .crease_for_buffer_row(MultiBufferRow(start_row))
18313 {
18314 Some(crease) => {
18315 let nested_start_row = crease.range().start.row + 1;
18316 let nested_end_row = crease.range().end.row;
18317
18318 if current_level < fold_at_level {
18319 stack.push((nested_start_row, nested_end_row, current_level + 1));
18320 } else if current_level == fold_at_level {
18321 // Fold iff there is no selection completely contained within the fold region
18322 if !row_ranges_to_keep.iter().any(|selection| {
18323 selection.end >= nested_start_row
18324 && selection.start <= nested_end_row
18325 }) {
18326 to_fold.push(crease);
18327 }
18328 }
18329
18330 start_row = nested_end_row + 1;
18331 }
18332 None => start_row += 1,
18333 }
18334 }
18335 }
18336
18337 self.fold_creases(to_fold, true, window, cx);
18338 }
18339
18340 pub fn fold_at_level_1(
18341 &mut self,
18342 _: &actions::FoldAtLevel1,
18343 window: &mut Window,
18344 cx: &mut Context<Self>,
18345 ) {
18346 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18347 }
18348
18349 pub fn fold_at_level_2(
18350 &mut self,
18351 _: &actions::FoldAtLevel2,
18352 window: &mut Window,
18353 cx: &mut Context<Self>,
18354 ) {
18355 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18356 }
18357
18358 pub fn fold_at_level_3(
18359 &mut self,
18360 _: &actions::FoldAtLevel3,
18361 window: &mut Window,
18362 cx: &mut Context<Self>,
18363 ) {
18364 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18365 }
18366
18367 pub fn fold_at_level_4(
18368 &mut self,
18369 _: &actions::FoldAtLevel4,
18370 window: &mut Window,
18371 cx: &mut Context<Self>,
18372 ) {
18373 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18374 }
18375
18376 pub fn fold_at_level_5(
18377 &mut self,
18378 _: &actions::FoldAtLevel5,
18379 window: &mut Window,
18380 cx: &mut Context<Self>,
18381 ) {
18382 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18383 }
18384
18385 pub fn fold_at_level_6(
18386 &mut self,
18387 _: &actions::FoldAtLevel6,
18388 window: &mut Window,
18389 cx: &mut Context<Self>,
18390 ) {
18391 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18392 }
18393
18394 pub fn fold_at_level_7(
18395 &mut self,
18396 _: &actions::FoldAtLevel7,
18397 window: &mut Window,
18398 cx: &mut Context<Self>,
18399 ) {
18400 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18401 }
18402
18403 pub fn fold_at_level_8(
18404 &mut self,
18405 _: &actions::FoldAtLevel8,
18406 window: &mut Window,
18407 cx: &mut Context<Self>,
18408 ) {
18409 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18410 }
18411
18412 pub fn fold_at_level_9(
18413 &mut self,
18414 _: &actions::FoldAtLevel9,
18415 window: &mut Window,
18416 cx: &mut Context<Self>,
18417 ) {
18418 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18419 }
18420
18421 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18422 if self.buffer.read(cx).is_singleton() {
18423 let mut fold_ranges = Vec::new();
18424 let snapshot = self.buffer.read(cx).snapshot(cx);
18425
18426 for row in 0..snapshot.max_row().0 {
18427 if let Some(foldable_range) = self
18428 .snapshot(window, cx)
18429 .crease_for_buffer_row(MultiBufferRow(row))
18430 {
18431 fold_ranges.push(foldable_range);
18432 }
18433 }
18434
18435 self.fold_creases(fold_ranges, true, window, cx);
18436 } else {
18437 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18438 editor
18439 .update_in(cx, |editor, _, cx| {
18440 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18441 editor.fold_buffer(buffer_id, cx);
18442 }
18443 })
18444 .ok();
18445 });
18446 }
18447 }
18448
18449 pub fn fold_function_bodies(
18450 &mut self,
18451 _: &actions::FoldFunctionBodies,
18452 window: &mut Window,
18453 cx: &mut Context<Self>,
18454 ) {
18455 let snapshot = self.buffer.read(cx).snapshot(cx);
18456
18457 let ranges = snapshot
18458 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18459 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18460 .collect::<Vec<_>>();
18461
18462 let creases = ranges
18463 .into_iter()
18464 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18465 .collect();
18466
18467 self.fold_creases(creases, true, window, cx);
18468 }
18469
18470 pub fn fold_recursive(
18471 &mut self,
18472 _: &actions::FoldRecursive,
18473 window: &mut Window,
18474 cx: &mut Context<Self>,
18475 ) {
18476 let mut to_fold = Vec::new();
18477 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18478 let selections = self.selections.all_adjusted(&display_map);
18479
18480 for selection in selections {
18481 let range = selection.range().sorted();
18482 let buffer_start_row = range.start.row;
18483
18484 if range.start.row != range.end.row {
18485 let mut found = false;
18486 for row in range.start.row..=range.end.row {
18487 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18488 found = true;
18489 to_fold.push(crease);
18490 }
18491 }
18492 if found {
18493 continue;
18494 }
18495 }
18496
18497 for row in (0..=range.start.row).rev() {
18498 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18499 if crease.range().end.row >= buffer_start_row {
18500 to_fold.push(crease);
18501 } else {
18502 break;
18503 }
18504 }
18505 }
18506 }
18507
18508 self.fold_creases(to_fold, true, window, cx);
18509 }
18510
18511 pub fn fold_at(
18512 &mut self,
18513 buffer_row: MultiBufferRow,
18514 window: &mut Window,
18515 cx: &mut Context<Self>,
18516 ) {
18517 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18518
18519 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18520 let autoscroll = self
18521 .selections
18522 .all::<Point>(&display_map)
18523 .iter()
18524 .any(|selection| crease.range().overlaps(&selection.range()));
18525
18526 self.fold_creases(vec![crease], autoscroll, window, cx);
18527 }
18528 }
18529
18530 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18531 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18532 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18533 let buffer = display_map.buffer_snapshot();
18534 let selections = self.selections.all::<Point>(&display_map);
18535 let ranges = selections
18536 .iter()
18537 .map(|s| {
18538 let range = s.display_range(&display_map).sorted();
18539 let mut start = range.start.to_point(&display_map);
18540 let mut end = range.end.to_point(&display_map);
18541 start.column = 0;
18542 end.column = buffer.line_len(MultiBufferRow(end.row));
18543 start..end
18544 })
18545 .collect::<Vec<_>>();
18546
18547 self.unfold_ranges(&ranges, true, true, cx);
18548 } else {
18549 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18550 let buffer_ids = self
18551 .selections
18552 .disjoint_anchor_ranges()
18553 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18554 .collect::<HashSet<_>>();
18555 for buffer_id in buffer_ids {
18556 self.unfold_buffer(buffer_id, cx);
18557 }
18558 }
18559 }
18560
18561 pub fn unfold_recursive(
18562 &mut self,
18563 _: &UnfoldRecursive,
18564 _window: &mut Window,
18565 cx: &mut Context<Self>,
18566 ) {
18567 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18568 let selections = self.selections.all::<Point>(&display_map);
18569 let ranges = selections
18570 .iter()
18571 .map(|s| {
18572 let mut range = s.display_range(&display_map).sorted();
18573 *range.start.column_mut() = 0;
18574 *range.end.column_mut() = display_map.line_len(range.end.row());
18575 let start = range.start.to_point(&display_map);
18576 let end = range.end.to_point(&display_map);
18577 start..end
18578 })
18579 .collect::<Vec<_>>();
18580
18581 self.unfold_ranges(&ranges, true, true, cx);
18582 }
18583
18584 pub fn unfold_at(
18585 &mut self,
18586 buffer_row: MultiBufferRow,
18587 _window: &mut Window,
18588 cx: &mut Context<Self>,
18589 ) {
18590 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18591
18592 let intersection_range = Point::new(buffer_row.0, 0)
18593 ..Point::new(
18594 buffer_row.0,
18595 display_map.buffer_snapshot().line_len(buffer_row),
18596 );
18597
18598 let autoscroll = self
18599 .selections
18600 .all::<Point>(&display_map)
18601 .iter()
18602 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18603
18604 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18605 }
18606
18607 pub fn unfold_all(
18608 &mut self,
18609 _: &actions::UnfoldAll,
18610 _window: &mut Window,
18611 cx: &mut Context<Self>,
18612 ) {
18613 if self.buffer.read(cx).is_singleton() {
18614 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18615 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18616 } else {
18617 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18618 editor
18619 .update(cx, |editor, cx| {
18620 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18621 editor.unfold_buffer(buffer_id, cx);
18622 }
18623 })
18624 .ok();
18625 });
18626 }
18627 }
18628
18629 pub fn fold_selected_ranges(
18630 &mut self,
18631 _: &FoldSelectedRanges,
18632 window: &mut Window,
18633 cx: &mut Context<Self>,
18634 ) {
18635 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18636 let selections = self.selections.all_adjusted(&display_map);
18637 let ranges = selections
18638 .into_iter()
18639 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18640 .collect::<Vec<_>>();
18641 self.fold_creases(ranges, true, window, cx);
18642 }
18643
18644 pub fn fold_ranges<T: ToOffset + Clone>(
18645 &mut self,
18646 ranges: Vec<Range<T>>,
18647 auto_scroll: bool,
18648 window: &mut Window,
18649 cx: &mut Context<Self>,
18650 ) {
18651 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18652 let ranges = ranges
18653 .into_iter()
18654 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18655 .collect::<Vec<_>>();
18656 self.fold_creases(ranges, auto_scroll, window, cx);
18657 }
18658
18659 pub fn fold_creases<T: ToOffset + Clone>(
18660 &mut self,
18661 creases: Vec<Crease<T>>,
18662 auto_scroll: bool,
18663 _window: &mut Window,
18664 cx: &mut Context<Self>,
18665 ) {
18666 if creases.is_empty() {
18667 return;
18668 }
18669
18670 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18671
18672 if auto_scroll {
18673 self.request_autoscroll(Autoscroll::fit(), cx);
18674 }
18675
18676 cx.notify();
18677
18678 self.scrollbar_marker_state.dirty = true;
18679 self.folds_did_change(cx);
18680 }
18681
18682 /// Removes any folds whose ranges intersect any of the given ranges.
18683 pub fn unfold_ranges<T: ToOffset + Clone>(
18684 &mut self,
18685 ranges: &[Range<T>],
18686 inclusive: bool,
18687 auto_scroll: bool,
18688 cx: &mut Context<Self>,
18689 ) {
18690 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18691 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18692 });
18693 self.folds_did_change(cx);
18694 }
18695
18696 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18697 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18698 return;
18699 }
18700 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18701 self.display_map.update(cx, |display_map, cx| {
18702 display_map.fold_buffers([buffer_id], cx)
18703 });
18704 cx.emit(EditorEvent::BufferFoldToggled {
18705 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18706 folded: true,
18707 });
18708 cx.notify();
18709 }
18710
18711 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18712 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18713 return;
18714 }
18715 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18716 self.display_map.update(cx, |display_map, cx| {
18717 display_map.unfold_buffers([buffer_id], cx);
18718 });
18719 cx.emit(EditorEvent::BufferFoldToggled {
18720 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18721 folded: false,
18722 });
18723 cx.notify();
18724 }
18725
18726 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18727 self.display_map.read(cx).is_buffer_folded(buffer)
18728 }
18729
18730 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18731 self.display_map.read(cx).folded_buffers()
18732 }
18733
18734 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18735 self.display_map.update(cx, |display_map, cx| {
18736 display_map.disable_header_for_buffer(buffer_id, cx);
18737 });
18738 cx.notify();
18739 }
18740
18741 /// Removes any folds with the given ranges.
18742 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18743 &mut self,
18744 ranges: &[Range<T>],
18745 type_id: TypeId,
18746 auto_scroll: bool,
18747 cx: &mut Context<Self>,
18748 ) {
18749 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18750 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18751 });
18752 self.folds_did_change(cx);
18753 }
18754
18755 fn remove_folds_with<T: ToOffset + Clone>(
18756 &mut self,
18757 ranges: &[Range<T>],
18758 auto_scroll: bool,
18759 cx: &mut Context<Self>,
18760 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18761 ) {
18762 if ranges.is_empty() {
18763 return;
18764 }
18765
18766 let mut buffers_affected = HashSet::default();
18767 let multi_buffer = self.buffer().read(cx);
18768 for range in ranges {
18769 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18770 buffers_affected.insert(buffer.read(cx).remote_id());
18771 };
18772 }
18773
18774 self.display_map.update(cx, update);
18775
18776 if auto_scroll {
18777 self.request_autoscroll(Autoscroll::fit(), cx);
18778 }
18779
18780 cx.notify();
18781 self.scrollbar_marker_state.dirty = true;
18782 self.active_indent_guides_state.dirty = true;
18783 }
18784
18785 pub fn update_renderer_widths(
18786 &mut self,
18787 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18788 cx: &mut Context<Self>,
18789 ) -> bool {
18790 self.display_map
18791 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18792 }
18793
18794 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18795 self.display_map.read(cx).fold_placeholder.clone()
18796 }
18797
18798 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18799 self.buffer.update(cx, |buffer, cx| {
18800 buffer.set_all_diff_hunks_expanded(cx);
18801 });
18802 }
18803
18804 pub fn expand_all_diff_hunks(
18805 &mut self,
18806 _: &ExpandAllDiffHunks,
18807 _window: &mut Window,
18808 cx: &mut Context<Self>,
18809 ) {
18810 self.buffer.update(cx, |buffer, cx| {
18811 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18812 });
18813 }
18814
18815 pub fn collapse_all_diff_hunks(
18816 &mut self,
18817 _: &CollapseAllDiffHunks,
18818 _window: &mut Window,
18819 cx: &mut Context<Self>,
18820 ) {
18821 self.buffer.update(cx, |buffer, cx| {
18822 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18823 });
18824 }
18825
18826 pub fn toggle_selected_diff_hunks(
18827 &mut self,
18828 _: &ToggleSelectedDiffHunks,
18829 _window: &mut Window,
18830 cx: &mut Context<Self>,
18831 ) {
18832 let ranges: Vec<_> = self
18833 .selections
18834 .disjoint_anchors()
18835 .iter()
18836 .map(|s| s.range())
18837 .collect();
18838 self.toggle_diff_hunks_in_ranges(ranges, cx);
18839 }
18840
18841 pub fn diff_hunks_in_ranges<'a>(
18842 &'a self,
18843 ranges: &'a [Range<Anchor>],
18844 buffer: &'a MultiBufferSnapshot,
18845 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18846 ranges.iter().flat_map(move |range| {
18847 let end_excerpt_id = range.end.excerpt_id;
18848 let range = range.to_point(buffer);
18849 let mut peek_end = range.end;
18850 if range.end.row < buffer.max_row().0 {
18851 peek_end = Point::new(range.end.row + 1, 0);
18852 }
18853 buffer
18854 .diff_hunks_in_range(range.start..peek_end)
18855 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18856 })
18857 }
18858
18859 pub fn has_stageable_diff_hunks_in_ranges(
18860 &self,
18861 ranges: &[Range<Anchor>],
18862 snapshot: &MultiBufferSnapshot,
18863 ) -> bool {
18864 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18865 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18866 }
18867
18868 pub fn toggle_staged_selected_diff_hunks(
18869 &mut self,
18870 _: &::git::ToggleStaged,
18871 _: &mut Window,
18872 cx: &mut Context<Self>,
18873 ) {
18874 let snapshot = self.buffer.read(cx).snapshot(cx);
18875 let ranges: Vec<_> = self
18876 .selections
18877 .disjoint_anchors()
18878 .iter()
18879 .map(|s| s.range())
18880 .collect();
18881 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18882 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18883 }
18884
18885 pub fn set_render_diff_hunk_controls(
18886 &mut self,
18887 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18888 cx: &mut Context<Self>,
18889 ) {
18890 self.render_diff_hunk_controls = render_diff_hunk_controls;
18891 cx.notify();
18892 }
18893
18894 pub fn stage_and_next(
18895 &mut self,
18896 _: &::git::StageAndNext,
18897 window: &mut Window,
18898 cx: &mut Context<Self>,
18899 ) {
18900 self.do_stage_or_unstage_and_next(true, window, cx);
18901 }
18902
18903 pub fn unstage_and_next(
18904 &mut self,
18905 _: &::git::UnstageAndNext,
18906 window: &mut Window,
18907 cx: &mut Context<Self>,
18908 ) {
18909 self.do_stage_or_unstage_and_next(false, window, cx);
18910 }
18911
18912 pub fn stage_or_unstage_diff_hunks(
18913 &mut self,
18914 stage: bool,
18915 ranges: Vec<Range<Anchor>>,
18916 cx: &mut Context<Self>,
18917 ) {
18918 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18919 cx.spawn(async move |this, cx| {
18920 task.await?;
18921 this.update(cx, |this, cx| {
18922 let snapshot = this.buffer.read(cx).snapshot(cx);
18923 let chunk_by = this
18924 .diff_hunks_in_ranges(&ranges, &snapshot)
18925 .chunk_by(|hunk| hunk.buffer_id);
18926 for (buffer_id, hunks) in &chunk_by {
18927 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18928 }
18929 })
18930 })
18931 .detach_and_log_err(cx);
18932 }
18933
18934 fn save_buffers_for_ranges_if_needed(
18935 &mut self,
18936 ranges: &[Range<Anchor>],
18937 cx: &mut Context<Editor>,
18938 ) -> Task<Result<()>> {
18939 let multibuffer = self.buffer.read(cx);
18940 let snapshot = multibuffer.read(cx);
18941 let buffer_ids: HashSet<_> = ranges
18942 .iter()
18943 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18944 .collect();
18945 drop(snapshot);
18946
18947 let mut buffers = HashSet::default();
18948 for buffer_id in buffer_ids {
18949 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18950 let buffer = buffer_entity.read(cx);
18951 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18952 {
18953 buffers.insert(buffer_entity);
18954 }
18955 }
18956 }
18957
18958 if let Some(project) = &self.project {
18959 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18960 } else {
18961 Task::ready(Ok(()))
18962 }
18963 }
18964
18965 fn do_stage_or_unstage_and_next(
18966 &mut self,
18967 stage: bool,
18968 window: &mut Window,
18969 cx: &mut Context<Self>,
18970 ) {
18971 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18972
18973 if ranges.iter().any(|range| range.start != range.end) {
18974 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18975 return;
18976 }
18977
18978 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18979 let snapshot = self.snapshot(window, cx);
18980 let position = self
18981 .selections
18982 .newest::<Point>(&snapshot.display_snapshot)
18983 .head();
18984 let mut row = snapshot
18985 .buffer_snapshot()
18986 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
18987 .find(|hunk| hunk.row_range.start.0 > position.row)
18988 .map(|hunk| hunk.row_range.start);
18989
18990 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18991 // Outside of the project diff editor, wrap around to the beginning.
18992 if !all_diff_hunks_expanded {
18993 row = row.or_else(|| {
18994 snapshot
18995 .buffer_snapshot()
18996 .diff_hunks_in_range(Point::zero()..position)
18997 .find(|hunk| hunk.row_range.end.0 < position.row)
18998 .map(|hunk| hunk.row_range.start)
18999 });
19000 }
19001
19002 if let Some(row) = row {
19003 let destination = Point::new(row.0, 0);
19004 let autoscroll = Autoscroll::center();
19005
19006 self.unfold_ranges(&[destination..destination], false, false, cx);
19007 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19008 s.select_ranges([destination..destination]);
19009 });
19010 }
19011 }
19012
19013 fn do_stage_or_unstage(
19014 &self,
19015 stage: bool,
19016 buffer_id: BufferId,
19017 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19018 cx: &mut App,
19019 ) -> Option<()> {
19020 let project = self.project()?;
19021 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19022 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19023 let buffer_snapshot = buffer.read(cx).snapshot();
19024 let file_exists = buffer_snapshot
19025 .file()
19026 .is_some_and(|file| file.disk_state().exists());
19027 diff.update(cx, |diff, cx| {
19028 diff.stage_or_unstage_hunks(
19029 stage,
19030 &hunks
19031 .map(|hunk| buffer_diff::DiffHunk {
19032 buffer_range: hunk.buffer_range,
19033 diff_base_byte_range: hunk.diff_base_byte_range,
19034 secondary_status: hunk.secondary_status,
19035 range: Point::zero()..Point::zero(), // unused
19036 })
19037 .collect::<Vec<_>>(),
19038 &buffer_snapshot,
19039 file_exists,
19040 cx,
19041 )
19042 });
19043 None
19044 }
19045
19046 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19047 let ranges: Vec<_> = self
19048 .selections
19049 .disjoint_anchors()
19050 .iter()
19051 .map(|s| s.range())
19052 .collect();
19053 self.buffer
19054 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19055 }
19056
19057 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19058 self.buffer.update(cx, |buffer, cx| {
19059 let ranges = vec![Anchor::min()..Anchor::max()];
19060 if !buffer.all_diff_hunks_expanded()
19061 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19062 {
19063 buffer.collapse_diff_hunks(ranges, cx);
19064 true
19065 } else {
19066 false
19067 }
19068 })
19069 }
19070
19071 fn toggle_diff_hunks_in_ranges(
19072 &mut self,
19073 ranges: Vec<Range<Anchor>>,
19074 cx: &mut Context<Editor>,
19075 ) {
19076 self.buffer.update(cx, |buffer, cx| {
19077 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19078 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19079 })
19080 }
19081
19082 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19083 self.buffer.update(cx, |buffer, cx| {
19084 let snapshot = buffer.snapshot(cx);
19085 let excerpt_id = range.end.excerpt_id;
19086 let point_range = range.to_point(&snapshot);
19087 let expand = !buffer.single_hunk_is_expanded(range, cx);
19088 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19089 })
19090 }
19091
19092 pub(crate) fn apply_all_diff_hunks(
19093 &mut self,
19094 _: &ApplyAllDiffHunks,
19095 window: &mut Window,
19096 cx: &mut Context<Self>,
19097 ) {
19098 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19099
19100 let buffers = self.buffer.read(cx).all_buffers();
19101 for branch_buffer in buffers {
19102 branch_buffer.update(cx, |branch_buffer, cx| {
19103 branch_buffer.merge_into_base(Vec::new(), cx);
19104 });
19105 }
19106
19107 if let Some(project) = self.project.clone() {
19108 self.save(
19109 SaveOptions {
19110 format: true,
19111 autosave: false,
19112 },
19113 project,
19114 window,
19115 cx,
19116 )
19117 .detach_and_log_err(cx);
19118 }
19119 }
19120
19121 pub(crate) fn apply_selected_diff_hunks(
19122 &mut self,
19123 _: &ApplyDiffHunk,
19124 window: &mut Window,
19125 cx: &mut Context<Self>,
19126 ) {
19127 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19128 let snapshot = self.snapshot(window, cx);
19129 let hunks = snapshot.hunks_for_ranges(
19130 self.selections
19131 .all(&snapshot.display_snapshot)
19132 .into_iter()
19133 .map(|selection| selection.range()),
19134 );
19135 let mut ranges_by_buffer = HashMap::default();
19136 self.transact(window, cx, |editor, _window, cx| {
19137 for hunk in hunks {
19138 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19139 ranges_by_buffer
19140 .entry(buffer.clone())
19141 .or_insert_with(Vec::new)
19142 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19143 }
19144 }
19145
19146 for (buffer, ranges) in ranges_by_buffer {
19147 buffer.update(cx, |buffer, cx| {
19148 buffer.merge_into_base(ranges, cx);
19149 });
19150 }
19151 });
19152
19153 if let Some(project) = self.project.clone() {
19154 self.save(
19155 SaveOptions {
19156 format: true,
19157 autosave: false,
19158 },
19159 project,
19160 window,
19161 cx,
19162 )
19163 .detach_and_log_err(cx);
19164 }
19165 }
19166
19167 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19168 if hovered != self.gutter_hovered {
19169 self.gutter_hovered = hovered;
19170 cx.notify();
19171 }
19172 }
19173
19174 pub fn insert_blocks(
19175 &mut self,
19176 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19177 autoscroll: Option<Autoscroll>,
19178 cx: &mut Context<Self>,
19179 ) -> Vec<CustomBlockId> {
19180 let blocks = self
19181 .display_map
19182 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19183 if let Some(autoscroll) = autoscroll {
19184 self.request_autoscroll(autoscroll, cx);
19185 }
19186 cx.notify();
19187 blocks
19188 }
19189
19190 pub fn resize_blocks(
19191 &mut self,
19192 heights: HashMap<CustomBlockId, u32>,
19193 autoscroll: Option<Autoscroll>,
19194 cx: &mut Context<Self>,
19195 ) {
19196 self.display_map
19197 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19198 if let Some(autoscroll) = autoscroll {
19199 self.request_autoscroll(autoscroll, cx);
19200 }
19201 cx.notify();
19202 }
19203
19204 pub fn replace_blocks(
19205 &mut self,
19206 renderers: HashMap<CustomBlockId, RenderBlock>,
19207 autoscroll: Option<Autoscroll>,
19208 cx: &mut Context<Self>,
19209 ) {
19210 self.display_map
19211 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19212 if let Some(autoscroll) = autoscroll {
19213 self.request_autoscroll(autoscroll, cx);
19214 }
19215 cx.notify();
19216 }
19217
19218 pub fn remove_blocks(
19219 &mut self,
19220 block_ids: HashSet<CustomBlockId>,
19221 autoscroll: Option<Autoscroll>,
19222 cx: &mut Context<Self>,
19223 ) {
19224 self.display_map.update(cx, |display_map, cx| {
19225 display_map.remove_blocks(block_ids, cx)
19226 });
19227 if let Some(autoscroll) = autoscroll {
19228 self.request_autoscroll(autoscroll, cx);
19229 }
19230 cx.notify();
19231 }
19232
19233 pub fn row_for_block(
19234 &self,
19235 block_id: CustomBlockId,
19236 cx: &mut Context<Self>,
19237 ) -> Option<DisplayRow> {
19238 self.display_map
19239 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19240 }
19241
19242 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19243 self.focused_block = Some(focused_block);
19244 }
19245
19246 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19247 self.focused_block.take()
19248 }
19249
19250 pub fn insert_creases(
19251 &mut self,
19252 creases: impl IntoIterator<Item = Crease<Anchor>>,
19253 cx: &mut Context<Self>,
19254 ) -> Vec<CreaseId> {
19255 self.display_map
19256 .update(cx, |map, cx| map.insert_creases(creases, cx))
19257 }
19258
19259 pub fn remove_creases(
19260 &mut self,
19261 ids: impl IntoIterator<Item = CreaseId>,
19262 cx: &mut Context<Self>,
19263 ) -> Vec<(CreaseId, Range<Anchor>)> {
19264 self.display_map
19265 .update(cx, |map, cx| map.remove_creases(ids, cx))
19266 }
19267
19268 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19269 self.display_map
19270 .update(cx, |map, cx| map.snapshot(cx))
19271 .longest_row()
19272 }
19273
19274 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19275 self.display_map
19276 .update(cx, |map, cx| map.snapshot(cx))
19277 .max_point()
19278 }
19279
19280 pub fn text(&self, cx: &App) -> String {
19281 self.buffer.read(cx).read(cx).text()
19282 }
19283
19284 pub fn is_empty(&self, cx: &App) -> bool {
19285 self.buffer.read(cx).read(cx).is_empty()
19286 }
19287
19288 pub fn text_option(&self, cx: &App) -> Option<String> {
19289 let text = self.text(cx);
19290 let text = text.trim();
19291
19292 if text.is_empty() {
19293 return None;
19294 }
19295
19296 Some(text.to_string())
19297 }
19298
19299 pub fn set_text(
19300 &mut self,
19301 text: impl Into<Arc<str>>,
19302 window: &mut Window,
19303 cx: &mut Context<Self>,
19304 ) {
19305 self.transact(window, cx, |this, _, cx| {
19306 this.buffer
19307 .read(cx)
19308 .as_singleton()
19309 .expect("you can only call set_text on editors for singleton buffers")
19310 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19311 });
19312 }
19313
19314 pub fn display_text(&self, cx: &mut App) -> String {
19315 self.display_map
19316 .update(cx, |map, cx| map.snapshot(cx))
19317 .text()
19318 }
19319
19320 fn create_minimap(
19321 &self,
19322 minimap_settings: MinimapSettings,
19323 window: &mut Window,
19324 cx: &mut Context<Self>,
19325 ) -> Option<Entity<Self>> {
19326 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19327 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19328 }
19329
19330 fn initialize_new_minimap(
19331 &self,
19332 minimap_settings: MinimapSettings,
19333 window: &mut Window,
19334 cx: &mut Context<Self>,
19335 ) -> Entity<Self> {
19336 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19337
19338 let mut minimap = Editor::new_internal(
19339 EditorMode::Minimap {
19340 parent: cx.weak_entity(),
19341 },
19342 self.buffer.clone(),
19343 None,
19344 Some(self.display_map.clone()),
19345 window,
19346 cx,
19347 );
19348 minimap.scroll_manager.clone_state(&self.scroll_manager);
19349 minimap.set_text_style_refinement(TextStyleRefinement {
19350 font_size: Some(MINIMAP_FONT_SIZE),
19351 font_weight: Some(MINIMAP_FONT_WEIGHT),
19352 ..Default::default()
19353 });
19354 minimap.update_minimap_configuration(minimap_settings, cx);
19355 cx.new(|_| minimap)
19356 }
19357
19358 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19359 let current_line_highlight = minimap_settings
19360 .current_line_highlight
19361 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19362 self.set_current_line_highlight(Some(current_line_highlight));
19363 }
19364
19365 pub fn minimap(&self) -> Option<&Entity<Self>> {
19366 self.minimap
19367 .as_ref()
19368 .filter(|_| self.minimap_visibility.visible())
19369 }
19370
19371 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19372 let mut wrap_guides = smallvec![];
19373
19374 if self.show_wrap_guides == Some(false) {
19375 return wrap_guides;
19376 }
19377
19378 let settings = self.buffer.read(cx).language_settings(cx);
19379 if settings.show_wrap_guides {
19380 match self.soft_wrap_mode(cx) {
19381 SoftWrap::Column(soft_wrap) => {
19382 wrap_guides.push((soft_wrap as usize, true));
19383 }
19384 SoftWrap::Bounded(soft_wrap) => {
19385 wrap_guides.push((soft_wrap as usize, true));
19386 }
19387 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19388 }
19389 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19390 }
19391
19392 wrap_guides
19393 }
19394
19395 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19396 let settings = self.buffer.read(cx).language_settings(cx);
19397 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19398 match mode {
19399 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19400 SoftWrap::None
19401 }
19402 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19403 language_settings::SoftWrap::PreferredLineLength => {
19404 SoftWrap::Column(settings.preferred_line_length)
19405 }
19406 language_settings::SoftWrap::Bounded => {
19407 SoftWrap::Bounded(settings.preferred_line_length)
19408 }
19409 }
19410 }
19411
19412 pub fn set_soft_wrap_mode(
19413 &mut self,
19414 mode: language_settings::SoftWrap,
19415
19416 cx: &mut Context<Self>,
19417 ) {
19418 self.soft_wrap_mode_override = Some(mode);
19419 cx.notify();
19420 }
19421
19422 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19423 self.hard_wrap = hard_wrap;
19424 cx.notify();
19425 }
19426
19427 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19428 self.text_style_refinement = Some(style);
19429 }
19430
19431 /// called by the Element so we know what style we were most recently rendered with.
19432 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19433 // We intentionally do not inform the display map about the minimap style
19434 // so that wrapping is not recalculated and stays consistent for the editor
19435 // and its linked minimap.
19436 if !self.mode.is_minimap() {
19437 let font = style.text.font();
19438 let font_size = style.text.font_size.to_pixels(window.rem_size());
19439 let display_map = self
19440 .placeholder_display_map
19441 .as_ref()
19442 .filter(|_| self.is_empty(cx))
19443 .unwrap_or(&self.display_map);
19444
19445 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19446 }
19447 self.style = Some(style);
19448 }
19449
19450 pub fn style(&self) -> Option<&EditorStyle> {
19451 self.style.as_ref()
19452 }
19453
19454 // Called by the element. This method is not designed to be called outside of the editor
19455 // element's layout code because it does not notify when rewrapping is computed synchronously.
19456 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19457 if self.is_empty(cx) {
19458 self.placeholder_display_map
19459 .as_ref()
19460 .map_or(false, |display_map| {
19461 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19462 })
19463 } else {
19464 self.display_map
19465 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19466 }
19467 }
19468
19469 pub fn set_soft_wrap(&mut self) {
19470 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19471 }
19472
19473 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19474 if self.soft_wrap_mode_override.is_some() {
19475 self.soft_wrap_mode_override.take();
19476 } else {
19477 let soft_wrap = match self.soft_wrap_mode(cx) {
19478 SoftWrap::GitDiff => return,
19479 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19480 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19481 language_settings::SoftWrap::None
19482 }
19483 };
19484 self.soft_wrap_mode_override = Some(soft_wrap);
19485 }
19486 cx.notify();
19487 }
19488
19489 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19490 let Some(workspace) = self.workspace() else {
19491 return;
19492 };
19493 let fs = workspace.read(cx).app_state().fs.clone();
19494 let current_show = TabBarSettings::get_global(cx).show;
19495 update_settings_file(fs, cx, move |setting, _| {
19496 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19497 });
19498 }
19499
19500 pub fn toggle_indent_guides(
19501 &mut self,
19502 _: &ToggleIndentGuides,
19503 _: &mut Window,
19504 cx: &mut Context<Self>,
19505 ) {
19506 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19507 self.buffer
19508 .read(cx)
19509 .language_settings(cx)
19510 .indent_guides
19511 .enabled
19512 });
19513 self.show_indent_guides = Some(!currently_enabled);
19514 cx.notify();
19515 }
19516
19517 fn should_show_indent_guides(&self) -> Option<bool> {
19518 self.show_indent_guides
19519 }
19520
19521 pub fn toggle_line_numbers(
19522 &mut self,
19523 _: &ToggleLineNumbers,
19524 _: &mut Window,
19525 cx: &mut Context<Self>,
19526 ) {
19527 let mut editor_settings = EditorSettings::get_global(cx).clone();
19528 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19529 EditorSettings::override_global(editor_settings, cx);
19530 }
19531
19532 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19533 if let Some(show_line_numbers) = self.show_line_numbers {
19534 return show_line_numbers;
19535 }
19536 EditorSettings::get_global(cx).gutter.line_numbers
19537 }
19538
19539 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19540 self.use_relative_line_numbers
19541 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19542 }
19543
19544 pub fn toggle_relative_line_numbers(
19545 &mut self,
19546 _: &ToggleRelativeLineNumbers,
19547 _: &mut Window,
19548 cx: &mut Context<Self>,
19549 ) {
19550 let is_relative = self.should_use_relative_line_numbers(cx);
19551 self.set_relative_line_number(Some(!is_relative), cx)
19552 }
19553
19554 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19555 self.use_relative_line_numbers = is_relative;
19556 cx.notify();
19557 }
19558
19559 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19560 self.show_gutter = show_gutter;
19561 cx.notify();
19562 }
19563
19564 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19565 self.show_scrollbars = ScrollbarAxes {
19566 horizontal: show,
19567 vertical: show,
19568 };
19569 cx.notify();
19570 }
19571
19572 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19573 self.show_scrollbars.vertical = show;
19574 cx.notify();
19575 }
19576
19577 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19578 self.show_scrollbars.horizontal = show;
19579 cx.notify();
19580 }
19581
19582 pub fn set_minimap_visibility(
19583 &mut self,
19584 minimap_visibility: MinimapVisibility,
19585 window: &mut Window,
19586 cx: &mut Context<Self>,
19587 ) {
19588 if self.minimap_visibility != minimap_visibility {
19589 if minimap_visibility.visible() && self.minimap.is_none() {
19590 let minimap_settings = EditorSettings::get_global(cx).minimap;
19591 self.minimap =
19592 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19593 }
19594 self.minimap_visibility = minimap_visibility;
19595 cx.notify();
19596 }
19597 }
19598
19599 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19600 self.set_show_scrollbars(false, cx);
19601 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19602 }
19603
19604 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19605 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19606 }
19607
19608 /// Normally the text in full mode and auto height editors is padded on the
19609 /// left side by roughly half a character width for improved hit testing.
19610 ///
19611 /// Use this method to disable this for cases where this is not wanted (e.g.
19612 /// if you want to align the editor text with some other text above or below)
19613 /// or if you want to add this padding to single-line editors.
19614 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19615 self.offset_content = offset_content;
19616 cx.notify();
19617 }
19618
19619 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19620 self.show_line_numbers = Some(show_line_numbers);
19621 cx.notify();
19622 }
19623
19624 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19625 self.disable_expand_excerpt_buttons = true;
19626 cx.notify();
19627 }
19628
19629 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19630 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19631 cx.notify();
19632 }
19633
19634 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19635 self.show_code_actions = Some(show_code_actions);
19636 cx.notify();
19637 }
19638
19639 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19640 self.show_runnables = Some(show_runnables);
19641 cx.notify();
19642 }
19643
19644 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19645 self.show_breakpoints = Some(show_breakpoints);
19646 cx.notify();
19647 }
19648
19649 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19650 if self.display_map.read(cx).masked != masked {
19651 self.display_map.update(cx, |map, _| map.masked = masked);
19652 }
19653 cx.notify()
19654 }
19655
19656 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19657 self.show_wrap_guides = Some(show_wrap_guides);
19658 cx.notify();
19659 }
19660
19661 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19662 self.show_indent_guides = Some(show_indent_guides);
19663 cx.notify();
19664 }
19665
19666 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19667 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19668 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19669 && let Some(dir) = file.abs_path(cx).parent()
19670 {
19671 return Some(dir.to_owned());
19672 }
19673 }
19674
19675 None
19676 }
19677
19678 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19679 self.active_excerpt(cx)?
19680 .1
19681 .read(cx)
19682 .file()
19683 .and_then(|f| f.as_local())
19684 }
19685
19686 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19687 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19688 let buffer = buffer.read(cx);
19689 if let Some(project_path) = buffer.project_path(cx) {
19690 let project = self.project()?.read(cx);
19691 project.absolute_path(&project_path, cx)
19692 } else {
19693 buffer
19694 .file()
19695 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19696 }
19697 })
19698 }
19699
19700 pub fn reveal_in_finder(
19701 &mut self,
19702 _: &RevealInFileManager,
19703 _window: &mut Window,
19704 cx: &mut Context<Self>,
19705 ) {
19706 if let Some(target) = self.target_file(cx) {
19707 cx.reveal_path(&target.abs_path(cx));
19708 }
19709 }
19710
19711 pub fn copy_path(
19712 &mut self,
19713 _: &zed_actions::workspace::CopyPath,
19714 _window: &mut Window,
19715 cx: &mut Context<Self>,
19716 ) {
19717 if let Some(path) = self.target_file_abs_path(cx)
19718 && let Some(path) = path.to_str()
19719 {
19720 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19721 } else {
19722 cx.propagate();
19723 }
19724 }
19725
19726 pub fn copy_relative_path(
19727 &mut self,
19728 _: &zed_actions::workspace::CopyRelativePath,
19729 _window: &mut Window,
19730 cx: &mut Context<Self>,
19731 ) {
19732 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19733 let project = self.project()?.read(cx);
19734 let path = buffer.read(cx).file()?.path();
19735 let path = path.display(project.path_style(cx));
19736 Some(path)
19737 }) {
19738 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19739 } else {
19740 cx.propagate();
19741 }
19742 }
19743
19744 /// Returns the project path for the editor's buffer, if any buffer is
19745 /// opened in the editor.
19746 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19747 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19748 buffer.read(cx).project_path(cx)
19749 } else {
19750 None
19751 }
19752 }
19753
19754 // Returns true if the editor handled a go-to-line request
19755 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19756 maybe!({
19757 let breakpoint_store = self.breakpoint_store.as_ref()?;
19758
19759 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19760 else {
19761 self.clear_row_highlights::<ActiveDebugLine>();
19762 return None;
19763 };
19764
19765 let position = active_stack_frame.position;
19766 let buffer_id = position.buffer_id?;
19767 let snapshot = self
19768 .project
19769 .as_ref()?
19770 .read(cx)
19771 .buffer_for_id(buffer_id, cx)?
19772 .read(cx)
19773 .snapshot();
19774
19775 let mut handled = false;
19776 for (id, ExcerptRange { context, .. }) in
19777 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19778 {
19779 if context.start.cmp(&position, &snapshot).is_ge()
19780 || context.end.cmp(&position, &snapshot).is_lt()
19781 {
19782 continue;
19783 }
19784 let snapshot = self.buffer.read(cx).snapshot(cx);
19785 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19786
19787 handled = true;
19788 self.clear_row_highlights::<ActiveDebugLine>();
19789
19790 self.go_to_line::<ActiveDebugLine>(
19791 multibuffer_anchor,
19792 Some(cx.theme().colors().editor_debugger_active_line_background),
19793 window,
19794 cx,
19795 );
19796
19797 cx.notify();
19798 }
19799
19800 handled.then_some(())
19801 })
19802 .is_some()
19803 }
19804
19805 pub fn copy_file_name_without_extension(
19806 &mut self,
19807 _: &CopyFileNameWithoutExtension,
19808 _: &mut Window,
19809 cx: &mut Context<Self>,
19810 ) {
19811 if let Some(file) = self.target_file(cx)
19812 && let Some(file_stem) = file.path().file_stem()
19813 {
19814 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19815 }
19816 }
19817
19818 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19819 if let Some(file) = self.target_file(cx)
19820 && let Some(name) = file.path().file_name()
19821 {
19822 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19823 }
19824 }
19825
19826 pub fn toggle_git_blame(
19827 &mut self,
19828 _: &::git::Blame,
19829 window: &mut Window,
19830 cx: &mut Context<Self>,
19831 ) {
19832 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19833
19834 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19835 self.start_git_blame(true, window, cx);
19836 }
19837
19838 cx.notify();
19839 }
19840
19841 pub fn toggle_git_blame_inline(
19842 &mut self,
19843 _: &ToggleGitBlameInline,
19844 window: &mut Window,
19845 cx: &mut Context<Self>,
19846 ) {
19847 self.toggle_git_blame_inline_internal(true, window, cx);
19848 cx.notify();
19849 }
19850
19851 pub fn open_git_blame_commit(
19852 &mut self,
19853 _: &OpenGitBlameCommit,
19854 window: &mut Window,
19855 cx: &mut Context<Self>,
19856 ) {
19857 self.open_git_blame_commit_internal(window, cx);
19858 }
19859
19860 fn open_git_blame_commit_internal(
19861 &mut self,
19862 window: &mut Window,
19863 cx: &mut Context<Self>,
19864 ) -> Option<()> {
19865 let blame = self.blame.as_ref()?;
19866 let snapshot = self.snapshot(window, cx);
19867 let cursor = self
19868 .selections
19869 .newest::<Point>(&snapshot.display_snapshot)
19870 .head();
19871 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
19872 let (_, blame_entry) = blame
19873 .update(cx, |blame, cx| {
19874 blame
19875 .blame_for_rows(
19876 &[RowInfo {
19877 buffer_id: Some(buffer.remote_id()),
19878 buffer_row: Some(point.row),
19879 ..Default::default()
19880 }],
19881 cx,
19882 )
19883 .next()
19884 })
19885 .flatten()?;
19886 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19887 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19888 let workspace = self.workspace()?.downgrade();
19889 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19890 None
19891 }
19892
19893 pub fn git_blame_inline_enabled(&self) -> bool {
19894 self.git_blame_inline_enabled
19895 }
19896
19897 pub fn toggle_selection_menu(
19898 &mut self,
19899 _: &ToggleSelectionMenu,
19900 _: &mut Window,
19901 cx: &mut Context<Self>,
19902 ) {
19903 self.show_selection_menu = self
19904 .show_selection_menu
19905 .map(|show_selections_menu| !show_selections_menu)
19906 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19907
19908 cx.notify();
19909 }
19910
19911 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19912 self.show_selection_menu
19913 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19914 }
19915
19916 fn start_git_blame(
19917 &mut self,
19918 user_triggered: bool,
19919 window: &mut Window,
19920 cx: &mut Context<Self>,
19921 ) {
19922 if let Some(project) = self.project() {
19923 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19924 && buffer.read(cx).file().is_none()
19925 {
19926 return;
19927 }
19928
19929 let focused = self.focus_handle(cx).contains_focused(window, cx);
19930
19931 let project = project.clone();
19932 let blame = cx
19933 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19934 self.blame_subscription =
19935 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19936 self.blame = Some(blame);
19937 }
19938 }
19939
19940 fn toggle_git_blame_inline_internal(
19941 &mut self,
19942 user_triggered: bool,
19943 window: &mut Window,
19944 cx: &mut Context<Self>,
19945 ) {
19946 if self.git_blame_inline_enabled {
19947 self.git_blame_inline_enabled = false;
19948 self.show_git_blame_inline = false;
19949 self.show_git_blame_inline_delay_task.take();
19950 } else {
19951 self.git_blame_inline_enabled = true;
19952 self.start_git_blame_inline(user_triggered, window, cx);
19953 }
19954
19955 cx.notify();
19956 }
19957
19958 fn start_git_blame_inline(
19959 &mut self,
19960 user_triggered: bool,
19961 window: &mut Window,
19962 cx: &mut Context<Self>,
19963 ) {
19964 self.start_git_blame(user_triggered, window, cx);
19965
19966 if ProjectSettings::get_global(cx)
19967 .git
19968 .inline_blame_delay()
19969 .is_some()
19970 {
19971 self.start_inline_blame_timer(window, cx);
19972 } else {
19973 self.show_git_blame_inline = true
19974 }
19975 }
19976
19977 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19978 self.blame.as_ref()
19979 }
19980
19981 pub fn show_git_blame_gutter(&self) -> bool {
19982 self.show_git_blame_gutter
19983 }
19984
19985 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19986 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19987 }
19988
19989 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19990 self.show_git_blame_inline
19991 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19992 && !self.newest_selection_head_on_empty_line(cx)
19993 && self.has_blame_entries(cx)
19994 }
19995
19996 fn has_blame_entries(&self, cx: &App) -> bool {
19997 self.blame()
19998 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19999 }
20000
20001 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20002 let cursor_anchor = self.selections.newest_anchor().head();
20003
20004 let snapshot = self.buffer.read(cx).snapshot(cx);
20005 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20006
20007 snapshot.line_len(buffer_row) == 0
20008 }
20009
20010 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20011 let buffer_and_selection = maybe!({
20012 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20013 let selection_range = selection.range();
20014
20015 let multi_buffer = self.buffer().read(cx);
20016 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20017 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20018
20019 let (buffer, range, _) = if selection.reversed {
20020 buffer_ranges.first()
20021 } else {
20022 buffer_ranges.last()
20023 }?;
20024
20025 let selection = text::ToPoint::to_point(&range.start, buffer).row
20026 ..text::ToPoint::to_point(&range.end, buffer).row;
20027 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
20028 });
20029
20030 let Some((buffer, selection)) = buffer_and_selection else {
20031 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20032 };
20033
20034 let Some(project) = self.project() else {
20035 return Task::ready(Err(anyhow!("editor does not have project")));
20036 };
20037
20038 project.update(cx, |project, cx| {
20039 project.get_permalink_to_line(&buffer, selection, cx)
20040 })
20041 }
20042
20043 pub fn copy_permalink_to_line(
20044 &mut self,
20045 _: &CopyPermalinkToLine,
20046 window: &mut Window,
20047 cx: &mut Context<Self>,
20048 ) {
20049 let permalink_task = self.get_permalink_to_line(cx);
20050 let workspace = self.workspace();
20051
20052 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20053 Ok(permalink) => {
20054 cx.update(|_, cx| {
20055 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20056 })
20057 .ok();
20058 }
20059 Err(err) => {
20060 let message = format!("Failed to copy permalink: {err}");
20061
20062 anyhow::Result::<()>::Err(err).log_err();
20063
20064 if let Some(workspace) = workspace {
20065 workspace
20066 .update_in(cx, |workspace, _, cx| {
20067 struct CopyPermalinkToLine;
20068
20069 workspace.show_toast(
20070 Toast::new(
20071 NotificationId::unique::<CopyPermalinkToLine>(),
20072 message,
20073 ),
20074 cx,
20075 )
20076 })
20077 .ok();
20078 }
20079 }
20080 })
20081 .detach();
20082 }
20083
20084 pub fn copy_file_location(
20085 &mut self,
20086 _: &CopyFileLocation,
20087 _: &mut Window,
20088 cx: &mut Context<Self>,
20089 ) {
20090 let selection = self
20091 .selections
20092 .newest::<Point>(&self.display_snapshot(cx))
20093 .start
20094 .row
20095 + 1;
20096 if let Some(file) = self.target_file(cx) {
20097 let path = file.path().display(file.path_style(cx));
20098 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
20099 }
20100 }
20101
20102 pub fn open_permalink_to_line(
20103 &mut self,
20104 _: &OpenPermalinkToLine,
20105 window: &mut Window,
20106 cx: &mut Context<Self>,
20107 ) {
20108 let permalink_task = self.get_permalink_to_line(cx);
20109 let workspace = self.workspace();
20110
20111 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20112 Ok(permalink) => {
20113 cx.update(|_, cx| {
20114 cx.open_url(permalink.as_ref());
20115 })
20116 .ok();
20117 }
20118 Err(err) => {
20119 let message = format!("Failed to open permalink: {err}");
20120
20121 anyhow::Result::<()>::Err(err).log_err();
20122
20123 if let Some(workspace) = workspace {
20124 workspace
20125 .update(cx, |workspace, cx| {
20126 struct OpenPermalinkToLine;
20127
20128 workspace.show_toast(
20129 Toast::new(
20130 NotificationId::unique::<OpenPermalinkToLine>(),
20131 message,
20132 ),
20133 cx,
20134 )
20135 })
20136 .ok();
20137 }
20138 }
20139 })
20140 .detach();
20141 }
20142
20143 pub fn insert_uuid_v4(
20144 &mut self,
20145 _: &InsertUuidV4,
20146 window: &mut Window,
20147 cx: &mut Context<Self>,
20148 ) {
20149 self.insert_uuid(UuidVersion::V4, window, cx);
20150 }
20151
20152 pub fn insert_uuid_v7(
20153 &mut self,
20154 _: &InsertUuidV7,
20155 window: &mut Window,
20156 cx: &mut Context<Self>,
20157 ) {
20158 self.insert_uuid(UuidVersion::V7, window, cx);
20159 }
20160
20161 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20162 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20163 self.transact(window, cx, |this, window, cx| {
20164 let edits = this
20165 .selections
20166 .all::<Point>(&this.display_snapshot(cx))
20167 .into_iter()
20168 .map(|selection| {
20169 let uuid = match version {
20170 UuidVersion::V4 => uuid::Uuid::new_v4(),
20171 UuidVersion::V7 => uuid::Uuid::now_v7(),
20172 };
20173
20174 (selection.range(), uuid.to_string())
20175 });
20176 this.edit(edits, cx);
20177 this.refresh_edit_prediction(true, false, window, cx);
20178 });
20179 }
20180
20181 pub fn open_selections_in_multibuffer(
20182 &mut self,
20183 _: &OpenSelectionsInMultibuffer,
20184 window: &mut Window,
20185 cx: &mut Context<Self>,
20186 ) {
20187 let multibuffer = self.buffer.read(cx);
20188
20189 let Some(buffer) = multibuffer.as_singleton() else {
20190 return;
20191 };
20192
20193 let Some(workspace) = self.workspace() else {
20194 return;
20195 };
20196
20197 let title = multibuffer.title(cx).to_string();
20198
20199 let locations = self
20200 .selections
20201 .all_anchors(cx)
20202 .iter()
20203 .map(|selection| {
20204 (
20205 buffer.clone(),
20206 (selection.start.text_anchor..selection.end.text_anchor)
20207 .to_point(buffer.read(cx)),
20208 )
20209 })
20210 .into_group_map();
20211
20212 cx.spawn_in(window, async move |_, cx| {
20213 workspace.update_in(cx, |workspace, window, cx| {
20214 Self::open_locations_in_multibuffer(
20215 workspace,
20216 locations,
20217 format!("Selections for '{title}'"),
20218 false,
20219 MultibufferSelectionMode::All,
20220 window,
20221 cx,
20222 );
20223 })
20224 })
20225 .detach();
20226 }
20227
20228 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20229 /// last highlight added will be used.
20230 ///
20231 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20232 pub fn highlight_rows<T: 'static>(
20233 &mut self,
20234 range: Range<Anchor>,
20235 color: Hsla,
20236 options: RowHighlightOptions,
20237 cx: &mut Context<Self>,
20238 ) {
20239 let snapshot = self.buffer().read(cx).snapshot(cx);
20240 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20241 let ix = row_highlights.binary_search_by(|highlight| {
20242 Ordering::Equal
20243 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20244 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20245 });
20246
20247 if let Err(mut ix) = ix {
20248 let index = post_inc(&mut self.highlight_order);
20249
20250 // If this range intersects with the preceding highlight, then merge it with
20251 // the preceding highlight. Otherwise insert a new highlight.
20252 let mut merged = false;
20253 if ix > 0 {
20254 let prev_highlight = &mut row_highlights[ix - 1];
20255 if prev_highlight
20256 .range
20257 .end
20258 .cmp(&range.start, &snapshot)
20259 .is_ge()
20260 {
20261 ix -= 1;
20262 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20263 prev_highlight.range.end = range.end;
20264 }
20265 merged = true;
20266 prev_highlight.index = index;
20267 prev_highlight.color = color;
20268 prev_highlight.options = options;
20269 }
20270 }
20271
20272 if !merged {
20273 row_highlights.insert(
20274 ix,
20275 RowHighlight {
20276 range,
20277 index,
20278 color,
20279 options,
20280 type_id: TypeId::of::<T>(),
20281 },
20282 );
20283 }
20284
20285 // If any of the following highlights intersect with this one, merge them.
20286 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20287 let highlight = &row_highlights[ix];
20288 if next_highlight
20289 .range
20290 .start
20291 .cmp(&highlight.range.end, &snapshot)
20292 .is_le()
20293 {
20294 if next_highlight
20295 .range
20296 .end
20297 .cmp(&highlight.range.end, &snapshot)
20298 .is_gt()
20299 {
20300 row_highlights[ix].range.end = next_highlight.range.end;
20301 }
20302 row_highlights.remove(ix + 1);
20303 } else {
20304 break;
20305 }
20306 }
20307 }
20308 }
20309
20310 /// Remove any highlighted row ranges of the given type that intersect the
20311 /// given ranges.
20312 pub fn remove_highlighted_rows<T: 'static>(
20313 &mut self,
20314 ranges_to_remove: Vec<Range<Anchor>>,
20315 cx: &mut Context<Self>,
20316 ) {
20317 let snapshot = self.buffer().read(cx).snapshot(cx);
20318 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20319 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20320 row_highlights.retain(|highlight| {
20321 while let Some(range_to_remove) = ranges_to_remove.peek() {
20322 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20323 Ordering::Less | Ordering::Equal => {
20324 ranges_to_remove.next();
20325 }
20326 Ordering::Greater => {
20327 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20328 Ordering::Less | Ordering::Equal => {
20329 return false;
20330 }
20331 Ordering::Greater => break,
20332 }
20333 }
20334 }
20335 }
20336
20337 true
20338 })
20339 }
20340
20341 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20342 pub fn clear_row_highlights<T: 'static>(&mut self) {
20343 self.highlighted_rows.remove(&TypeId::of::<T>());
20344 }
20345
20346 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20347 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20348 self.highlighted_rows
20349 .get(&TypeId::of::<T>())
20350 .map_or(&[] as &[_], |vec| vec.as_slice())
20351 .iter()
20352 .map(|highlight| (highlight.range.clone(), highlight.color))
20353 }
20354
20355 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20356 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20357 /// Allows to ignore certain kinds of highlights.
20358 pub fn highlighted_display_rows(
20359 &self,
20360 window: &mut Window,
20361 cx: &mut App,
20362 ) -> BTreeMap<DisplayRow, LineHighlight> {
20363 let snapshot = self.snapshot(window, cx);
20364 let mut used_highlight_orders = HashMap::default();
20365 self.highlighted_rows
20366 .iter()
20367 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20368 .fold(
20369 BTreeMap::<DisplayRow, LineHighlight>::new(),
20370 |mut unique_rows, highlight| {
20371 let start = highlight.range.start.to_display_point(&snapshot);
20372 let end = highlight.range.end.to_display_point(&snapshot);
20373 let start_row = start.row().0;
20374 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20375 && end.column() == 0
20376 {
20377 end.row().0.saturating_sub(1)
20378 } else {
20379 end.row().0
20380 };
20381 for row in start_row..=end_row {
20382 let used_index =
20383 used_highlight_orders.entry(row).or_insert(highlight.index);
20384 if highlight.index >= *used_index {
20385 *used_index = highlight.index;
20386 unique_rows.insert(
20387 DisplayRow(row),
20388 LineHighlight {
20389 include_gutter: highlight.options.include_gutter,
20390 border: None,
20391 background: highlight.color.into(),
20392 type_id: Some(highlight.type_id),
20393 },
20394 );
20395 }
20396 }
20397 unique_rows
20398 },
20399 )
20400 }
20401
20402 pub fn highlighted_display_row_for_autoscroll(
20403 &self,
20404 snapshot: &DisplaySnapshot,
20405 ) -> Option<DisplayRow> {
20406 self.highlighted_rows
20407 .values()
20408 .flat_map(|highlighted_rows| highlighted_rows.iter())
20409 .filter_map(|highlight| {
20410 if highlight.options.autoscroll {
20411 Some(highlight.range.start.to_display_point(snapshot).row())
20412 } else {
20413 None
20414 }
20415 })
20416 .min()
20417 }
20418
20419 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20420 self.highlight_background::<SearchWithinRange>(
20421 ranges,
20422 |colors| colors.colors().editor_document_highlight_read_background,
20423 cx,
20424 )
20425 }
20426
20427 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20428 self.breadcrumb_header = Some(new_header);
20429 }
20430
20431 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20432 self.clear_background_highlights::<SearchWithinRange>(cx);
20433 }
20434
20435 pub fn highlight_background<T: 'static>(
20436 &mut self,
20437 ranges: &[Range<Anchor>],
20438 color_fetcher: fn(&Theme) -> Hsla,
20439 cx: &mut Context<Self>,
20440 ) {
20441 self.background_highlights.insert(
20442 HighlightKey::Type(TypeId::of::<T>()),
20443 (color_fetcher, Arc::from(ranges)),
20444 );
20445 self.scrollbar_marker_state.dirty = true;
20446 cx.notify();
20447 }
20448
20449 pub fn highlight_background_key<T: 'static>(
20450 &mut self,
20451 key: usize,
20452 ranges: &[Range<Anchor>],
20453 color_fetcher: fn(&Theme) -> Hsla,
20454 cx: &mut Context<Self>,
20455 ) {
20456 self.background_highlights.insert(
20457 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20458 (color_fetcher, Arc::from(ranges)),
20459 );
20460 self.scrollbar_marker_state.dirty = true;
20461 cx.notify();
20462 }
20463
20464 pub fn clear_background_highlights<T: 'static>(
20465 &mut self,
20466 cx: &mut Context<Self>,
20467 ) -> Option<BackgroundHighlight> {
20468 let text_highlights = self
20469 .background_highlights
20470 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20471 if !text_highlights.1.is_empty() {
20472 self.scrollbar_marker_state.dirty = true;
20473 cx.notify();
20474 }
20475 Some(text_highlights)
20476 }
20477
20478 pub fn highlight_gutter<T: 'static>(
20479 &mut self,
20480 ranges: impl Into<Vec<Range<Anchor>>>,
20481 color_fetcher: fn(&App) -> Hsla,
20482 cx: &mut Context<Self>,
20483 ) {
20484 self.gutter_highlights
20485 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20486 cx.notify();
20487 }
20488
20489 pub fn clear_gutter_highlights<T: 'static>(
20490 &mut self,
20491 cx: &mut Context<Self>,
20492 ) -> Option<GutterHighlight> {
20493 cx.notify();
20494 self.gutter_highlights.remove(&TypeId::of::<T>())
20495 }
20496
20497 pub fn insert_gutter_highlight<T: 'static>(
20498 &mut self,
20499 range: Range<Anchor>,
20500 color_fetcher: fn(&App) -> Hsla,
20501 cx: &mut Context<Self>,
20502 ) {
20503 let snapshot = self.buffer().read(cx).snapshot(cx);
20504 let mut highlights = self
20505 .gutter_highlights
20506 .remove(&TypeId::of::<T>())
20507 .map(|(_, highlights)| highlights)
20508 .unwrap_or_default();
20509 let ix = highlights.binary_search_by(|highlight| {
20510 Ordering::Equal
20511 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20512 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20513 });
20514 if let Err(ix) = ix {
20515 highlights.insert(ix, range);
20516 }
20517 self.gutter_highlights
20518 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20519 }
20520
20521 pub fn remove_gutter_highlights<T: 'static>(
20522 &mut self,
20523 ranges_to_remove: Vec<Range<Anchor>>,
20524 cx: &mut Context<Self>,
20525 ) {
20526 let snapshot = self.buffer().read(cx).snapshot(cx);
20527 let Some((color_fetcher, mut gutter_highlights)) =
20528 self.gutter_highlights.remove(&TypeId::of::<T>())
20529 else {
20530 return;
20531 };
20532 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20533 gutter_highlights.retain(|highlight| {
20534 while let Some(range_to_remove) = ranges_to_remove.peek() {
20535 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20536 Ordering::Less | Ordering::Equal => {
20537 ranges_to_remove.next();
20538 }
20539 Ordering::Greater => {
20540 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20541 Ordering::Less | Ordering::Equal => {
20542 return false;
20543 }
20544 Ordering::Greater => break,
20545 }
20546 }
20547 }
20548 }
20549
20550 true
20551 });
20552 self.gutter_highlights
20553 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20554 }
20555
20556 #[cfg(feature = "test-support")]
20557 pub fn all_text_highlights(
20558 &self,
20559 window: &mut Window,
20560 cx: &mut Context<Self>,
20561 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20562 let snapshot = self.snapshot(window, cx);
20563 self.display_map.update(cx, |display_map, _| {
20564 display_map
20565 .all_text_highlights()
20566 .map(|highlight| {
20567 let (style, ranges) = highlight.as_ref();
20568 (
20569 *style,
20570 ranges
20571 .iter()
20572 .map(|range| range.clone().to_display_points(&snapshot))
20573 .collect(),
20574 )
20575 })
20576 .collect()
20577 })
20578 }
20579
20580 #[cfg(feature = "test-support")]
20581 pub fn all_text_background_highlights(
20582 &self,
20583 window: &mut Window,
20584 cx: &mut Context<Self>,
20585 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20586 let snapshot = self.snapshot(window, cx);
20587 let buffer = &snapshot.buffer_snapshot();
20588 let start = buffer.anchor_before(0);
20589 let end = buffer.anchor_after(buffer.len());
20590 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20591 }
20592
20593 #[cfg(any(test, feature = "test-support"))]
20594 pub fn sorted_background_highlights_in_range(
20595 &self,
20596 search_range: Range<Anchor>,
20597 display_snapshot: &DisplaySnapshot,
20598 theme: &Theme,
20599 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20600 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20601 res.sort_by(|a, b| {
20602 a.0.start
20603 .cmp(&b.0.start)
20604 .then_with(|| a.0.end.cmp(&b.0.end))
20605 .then_with(|| a.1.cmp(&b.1))
20606 });
20607 res
20608 }
20609
20610 #[cfg(feature = "test-support")]
20611 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20612 let snapshot = self.buffer().read(cx).snapshot(cx);
20613
20614 let highlights = self
20615 .background_highlights
20616 .get(&HighlightKey::Type(TypeId::of::<
20617 items::BufferSearchHighlights,
20618 >()));
20619
20620 if let Some((_color, ranges)) = highlights {
20621 ranges
20622 .iter()
20623 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20624 .collect_vec()
20625 } else {
20626 vec![]
20627 }
20628 }
20629
20630 fn document_highlights_for_position<'a>(
20631 &'a self,
20632 position: Anchor,
20633 buffer: &'a MultiBufferSnapshot,
20634 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20635 let read_highlights = self
20636 .background_highlights
20637 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20638 .map(|h| &h.1);
20639 let write_highlights = self
20640 .background_highlights
20641 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20642 .map(|h| &h.1);
20643 let left_position = position.bias_left(buffer);
20644 let right_position = position.bias_right(buffer);
20645 read_highlights
20646 .into_iter()
20647 .chain(write_highlights)
20648 .flat_map(move |ranges| {
20649 let start_ix = match ranges.binary_search_by(|probe| {
20650 let cmp = probe.end.cmp(&left_position, buffer);
20651 if cmp.is_ge() {
20652 Ordering::Greater
20653 } else {
20654 Ordering::Less
20655 }
20656 }) {
20657 Ok(i) | Err(i) => i,
20658 };
20659
20660 ranges[start_ix..]
20661 .iter()
20662 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20663 })
20664 }
20665
20666 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20667 self.background_highlights
20668 .get(&HighlightKey::Type(TypeId::of::<T>()))
20669 .is_some_and(|(_, highlights)| !highlights.is_empty())
20670 }
20671
20672 /// Returns all background highlights for a given range.
20673 ///
20674 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20675 pub fn background_highlights_in_range(
20676 &self,
20677 search_range: Range<Anchor>,
20678 display_snapshot: &DisplaySnapshot,
20679 theme: &Theme,
20680 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20681 let mut results = Vec::new();
20682 for (color_fetcher, ranges) in self.background_highlights.values() {
20683 let color = color_fetcher(theme);
20684 let start_ix = match ranges.binary_search_by(|probe| {
20685 let cmp = probe
20686 .end
20687 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20688 if cmp.is_gt() {
20689 Ordering::Greater
20690 } else {
20691 Ordering::Less
20692 }
20693 }) {
20694 Ok(i) | Err(i) => i,
20695 };
20696 for range in &ranges[start_ix..] {
20697 if range
20698 .start
20699 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20700 .is_ge()
20701 {
20702 break;
20703 }
20704
20705 let start = range.start.to_display_point(display_snapshot);
20706 let end = range.end.to_display_point(display_snapshot);
20707 results.push((start..end, color))
20708 }
20709 }
20710 results
20711 }
20712
20713 pub fn gutter_highlights_in_range(
20714 &self,
20715 search_range: Range<Anchor>,
20716 display_snapshot: &DisplaySnapshot,
20717 cx: &App,
20718 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20719 let mut results = Vec::new();
20720 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20721 let color = color_fetcher(cx);
20722 let start_ix = match ranges.binary_search_by(|probe| {
20723 let cmp = probe
20724 .end
20725 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20726 if cmp.is_gt() {
20727 Ordering::Greater
20728 } else {
20729 Ordering::Less
20730 }
20731 }) {
20732 Ok(i) | Err(i) => i,
20733 };
20734 for range in &ranges[start_ix..] {
20735 if range
20736 .start
20737 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20738 .is_ge()
20739 {
20740 break;
20741 }
20742
20743 let start = range.start.to_display_point(display_snapshot);
20744 let end = range.end.to_display_point(display_snapshot);
20745 results.push((start..end, color))
20746 }
20747 }
20748 results
20749 }
20750
20751 /// Get the text ranges corresponding to the redaction query
20752 pub fn redacted_ranges(
20753 &self,
20754 search_range: Range<Anchor>,
20755 display_snapshot: &DisplaySnapshot,
20756 cx: &App,
20757 ) -> Vec<Range<DisplayPoint>> {
20758 display_snapshot
20759 .buffer_snapshot()
20760 .redacted_ranges(search_range, |file| {
20761 if let Some(file) = file {
20762 file.is_private()
20763 && EditorSettings::get(
20764 Some(SettingsLocation {
20765 worktree_id: file.worktree_id(cx),
20766 path: file.path().as_ref(),
20767 }),
20768 cx,
20769 )
20770 .redact_private_values
20771 } else {
20772 false
20773 }
20774 })
20775 .map(|range| {
20776 range.start.to_display_point(display_snapshot)
20777 ..range.end.to_display_point(display_snapshot)
20778 })
20779 .collect()
20780 }
20781
20782 pub fn highlight_text_key<T: 'static>(
20783 &mut self,
20784 key: usize,
20785 ranges: Vec<Range<Anchor>>,
20786 style: HighlightStyle,
20787 cx: &mut Context<Self>,
20788 ) {
20789 self.display_map.update(cx, |map, _| {
20790 map.highlight_text(
20791 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20792 ranges,
20793 style,
20794 );
20795 });
20796 cx.notify();
20797 }
20798
20799 pub fn highlight_text<T: 'static>(
20800 &mut self,
20801 ranges: Vec<Range<Anchor>>,
20802 style: HighlightStyle,
20803 cx: &mut Context<Self>,
20804 ) {
20805 self.display_map.update(cx, |map, _| {
20806 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20807 });
20808 cx.notify();
20809 }
20810
20811 pub(crate) fn highlight_inlays<T: 'static>(
20812 &mut self,
20813 highlights: Vec<InlayHighlight>,
20814 style: HighlightStyle,
20815 cx: &mut Context<Self>,
20816 ) {
20817 self.display_map.update(cx, |map, _| {
20818 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20819 });
20820 cx.notify();
20821 }
20822
20823 pub fn text_highlights<'a, T: 'static>(
20824 &'a self,
20825 cx: &'a App,
20826 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20827 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20828 }
20829
20830 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20831 let cleared = self
20832 .display_map
20833 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20834 if cleared {
20835 cx.notify();
20836 }
20837 }
20838
20839 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20840 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20841 && self.focus_handle.is_focused(window)
20842 }
20843
20844 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20845 self.show_cursor_when_unfocused = is_enabled;
20846 cx.notify();
20847 }
20848
20849 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20850 cx.notify();
20851 }
20852
20853 fn on_debug_session_event(
20854 &mut self,
20855 _session: Entity<Session>,
20856 event: &SessionEvent,
20857 cx: &mut Context<Self>,
20858 ) {
20859 if let SessionEvent::InvalidateInlineValue = event {
20860 self.refresh_inline_values(cx);
20861 }
20862 }
20863
20864 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20865 let Some(project) = self.project.clone() else {
20866 return;
20867 };
20868
20869 if !self.inline_value_cache.enabled {
20870 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20871 self.splice_inlays(&inlays, Vec::new(), cx);
20872 return;
20873 }
20874
20875 let current_execution_position = self
20876 .highlighted_rows
20877 .get(&TypeId::of::<ActiveDebugLine>())
20878 .and_then(|lines| lines.last().map(|line| line.range.end));
20879
20880 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20881 let inline_values = editor
20882 .update(cx, |editor, cx| {
20883 let Some(current_execution_position) = current_execution_position else {
20884 return Some(Task::ready(Ok(Vec::new())));
20885 };
20886
20887 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20888 let snapshot = buffer.snapshot(cx);
20889
20890 let excerpt = snapshot.excerpt_containing(
20891 current_execution_position..current_execution_position,
20892 )?;
20893
20894 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20895 })?;
20896
20897 let range =
20898 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20899
20900 project.inline_values(buffer, range, cx)
20901 })
20902 .ok()
20903 .flatten()?
20904 .await
20905 .context("refreshing debugger inlays")
20906 .log_err()?;
20907
20908 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20909
20910 for (buffer_id, inline_value) in inline_values
20911 .into_iter()
20912 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20913 {
20914 buffer_inline_values
20915 .entry(buffer_id)
20916 .or_default()
20917 .push(inline_value);
20918 }
20919
20920 editor
20921 .update(cx, |editor, cx| {
20922 let snapshot = editor.buffer.read(cx).snapshot(cx);
20923 let mut new_inlays = Vec::default();
20924
20925 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20926 let buffer_id = buffer_snapshot.remote_id();
20927 buffer_inline_values
20928 .get(&buffer_id)
20929 .into_iter()
20930 .flatten()
20931 .for_each(|hint| {
20932 let inlay = Inlay::debugger(
20933 post_inc(&mut editor.next_inlay_id),
20934 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20935 hint.text(),
20936 );
20937 if !inlay.text().chars().contains(&'\n') {
20938 new_inlays.push(inlay);
20939 }
20940 });
20941 }
20942
20943 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20944 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20945
20946 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20947 })
20948 .ok()?;
20949 Some(())
20950 });
20951 }
20952
20953 fn on_buffer_event(
20954 &mut self,
20955 multibuffer: &Entity<MultiBuffer>,
20956 event: &multi_buffer::Event,
20957 window: &mut Window,
20958 cx: &mut Context<Self>,
20959 ) {
20960 match event {
20961 multi_buffer::Event::Edited { edited_buffer } => {
20962 self.scrollbar_marker_state.dirty = true;
20963 self.active_indent_guides_state.dirty = true;
20964 self.refresh_active_diagnostics(cx);
20965 self.refresh_code_actions(window, cx);
20966 self.refresh_selected_text_highlights(true, window, cx);
20967 self.refresh_single_line_folds(window, cx);
20968 refresh_matching_bracket_highlights(self, cx);
20969 if self.has_active_edit_prediction() {
20970 self.update_visible_edit_prediction(window, cx);
20971 }
20972
20973 if let Some(edited_buffer) = edited_buffer {
20974 if edited_buffer.read(cx).file().is_none() {
20975 cx.emit(EditorEvent::TitleChanged);
20976 }
20977
20978 let buffer_id = edited_buffer.read(cx).remote_id();
20979 if let Some(project) = self.project.clone() {
20980 self.register_buffer(buffer_id, cx);
20981 self.update_lsp_data(Some(buffer_id), window, cx);
20982 #[allow(clippy::mutable_key_type)]
20983 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20984 multibuffer
20985 .all_buffers()
20986 .into_iter()
20987 .filter_map(|buffer| {
20988 buffer.update(cx, |buffer, cx| {
20989 let language = buffer.language()?;
20990 let should_discard = project.update(cx, |project, cx| {
20991 project.is_local()
20992 && !project.has_language_servers_for(buffer, cx)
20993 });
20994 should_discard.not().then_some(language.clone())
20995 })
20996 })
20997 .collect::<HashSet<_>>()
20998 });
20999 if !languages_affected.is_empty() {
21000 self.refresh_inlay_hints(
21001 InlayHintRefreshReason::BufferEdited(languages_affected),
21002 cx,
21003 );
21004 }
21005 }
21006 }
21007
21008 cx.emit(EditorEvent::BufferEdited);
21009 cx.emit(SearchEvent::MatchesInvalidated);
21010
21011 let Some(project) = &self.project else { return };
21012 let (telemetry, is_via_ssh) = {
21013 let project = project.read(cx);
21014 let telemetry = project.client().telemetry().clone();
21015 let is_via_ssh = project.is_via_remote_server();
21016 (telemetry, is_via_ssh)
21017 };
21018 telemetry.log_edit_event("editor", is_via_ssh);
21019 }
21020 multi_buffer::Event::ExcerptsAdded {
21021 buffer,
21022 predecessor,
21023 excerpts,
21024 } => {
21025 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21026 let buffer_id = buffer.read(cx).remote_id();
21027 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21028 && let Some(project) = &self.project
21029 {
21030 update_uncommitted_diff_for_buffer(
21031 cx.entity(),
21032 project,
21033 [buffer.clone()],
21034 self.buffer.clone(),
21035 cx,
21036 )
21037 .detach();
21038 }
21039 self.update_lsp_data(Some(buffer_id), window, cx);
21040 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21041 cx.emit(EditorEvent::ExcerptsAdded {
21042 buffer: buffer.clone(),
21043 predecessor: *predecessor,
21044 excerpts: excerpts.clone(),
21045 });
21046 }
21047 multi_buffer::Event::ExcerptsRemoved {
21048 ids,
21049 removed_buffer_ids,
21050 } => {
21051 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21052 for buffer_id in removed_buffer_ids {
21053 self.registered_buffers.remove(buffer_id);
21054 }
21055 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21056 cx.emit(EditorEvent::ExcerptsRemoved {
21057 ids: ids.clone(),
21058 removed_buffer_ids: removed_buffer_ids.clone(),
21059 });
21060 }
21061 multi_buffer::Event::ExcerptsEdited {
21062 excerpt_ids,
21063 buffer_ids,
21064 } => {
21065 self.display_map.update(cx, |map, cx| {
21066 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21067 });
21068 cx.emit(EditorEvent::ExcerptsEdited {
21069 ids: excerpt_ids.clone(),
21070 });
21071 }
21072 multi_buffer::Event::ExcerptsExpanded { ids } => {
21073 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21074 self.refresh_document_highlights(cx);
21075 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21076 }
21077 multi_buffer::Event::Reparsed(buffer_id) => {
21078 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21079 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21080
21081 cx.emit(EditorEvent::Reparsed(*buffer_id));
21082 }
21083 multi_buffer::Event::DiffHunksToggled => {
21084 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21085 }
21086 multi_buffer::Event::LanguageChanged(buffer_id) => {
21087 self.registered_buffers.remove(&buffer_id);
21088 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21089 cx.emit(EditorEvent::Reparsed(*buffer_id));
21090 cx.notify();
21091 }
21092 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21093 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21094 multi_buffer::Event::FileHandleChanged
21095 | multi_buffer::Event::Reloaded
21096 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21097 multi_buffer::Event::DiagnosticsUpdated => {
21098 self.update_diagnostics_state(window, cx);
21099 }
21100 _ => {}
21101 };
21102 }
21103
21104 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21105 if !self.diagnostics_enabled() {
21106 return;
21107 }
21108 self.refresh_active_diagnostics(cx);
21109 self.refresh_inline_diagnostics(true, window, cx);
21110 self.scrollbar_marker_state.dirty = true;
21111 cx.notify();
21112 }
21113
21114 pub fn start_temporary_diff_override(&mut self) {
21115 self.load_diff_task.take();
21116 self.temporary_diff_override = true;
21117 }
21118
21119 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21120 self.temporary_diff_override = false;
21121 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21122 self.buffer.update(cx, |buffer, cx| {
21123 buffer.set_all_diff_hunks_collapsed(cx);
21124 });
21125
21126 if let Some(project) = self.project.clone() {
21127 self.load_diff_task = Some(
21128 update_uncommitted_diff_for_buffer(
21129 cx.entity(),
21130 &project,
21131 self.buffer.read(cx).all_buffers(),
21132 self.buffer.clone(),
21133 cx,
21134 )
21135 .shared(),
21136 );
21137 }
21138 }
21139
21140 fn on_display_map_changed(
21141 &mut self,
21142 _: Entity<DisplayMap>,
21143 _: &mut Window,
21144 cx: &mut Context<Self>,
21145 ) {
21146 cx.notify();
21147 }
21148
21149 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21150 if self.diagnostics_enabled() {
21151 let new_severity = EditorSettings::get_global(cx)
21152 .diagnostics_max_severity
21153 .unwrap_or(DiagnosticSeverity::Hint);
21154 self.set_max_diagnostics_severity(new_severity, cx);
21155 }
21156 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21157 self.update_edit_prediction_settings(cx);
21158 self.refresh_edit_prediction(true, false, window, cx);
21159 self.refresh_inline_values(cx);
21160 self.refresh_inlay_hints(
21161 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21162 self.selections.newest_anchor().head(),
21163 &self.buffer.read(cx).snapshot(cx),
21164 cx,
21165 )),
21166 cx,
21167 );
21168
21169 let old_cursor_shape = self.cursor_shape;
21170 let old_show_breadcrumbs = self.show_breadcrumbs;
21171
21172 {
21173 let editor_settings = EditorSettings::get_global(cx);
21174 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21175 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21176 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21177 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21178 }
21179
21180 if old_cursor_shape != self.cursor_shape {
21181 cx.emit(EditorEvent::CursorShapeChanged);
21182 }
21183
21184 if old_show_breadcrumbs != self.show_breadcrumbs {
21185 cx.emit(EditorEvent::BreadcrumbsChanged);
21186 }
21187
21188 let project_settings = ProjectSettings::get_global(cx);
21189 self.serialize_dirty_buffers =
21190 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
21191
21192 if self.mode.is_full() {
21193 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21194 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21195 if self.show_inline_diagnostics != show_inline_diagnostics {
21196 self.show_inline_diagnostics = show_inline_diagnostics;
21197 self.refresh_inline_diagnostics(false, window, cx);
21198 }
21199
21200 if self.git_blame_inline_enabled != inline_blame_enabled {
21201 self.toggle_git_blame_inline_internal(false, window, cx);
21202 }
21203
21204 let minimap_settings = EditorSettings::get_global(cx).minimap;
21205 if self.minimap_visibility != MinimapVisibility::Disabled {
21206 if self.minimap_visibility.settings_visibility()
21207 != minimap_settings.minimap_enabled()
21208 {
21209 self.set_minimap_visibility(
21210 MinimapVisibility::for_mode(self.mode(), cx),
21211 window,
21212 cx,
21213 );
21214 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21215 minimap_entity.update(cx, |minimap_editor, cx| {
21216 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21217 })
21218 }
21219 }
21220 }
21221
21222 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21223 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21224 }) {
21225 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
21226 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21227 }
21228 self.refresh_colors_for_visible_range(None, window, cx);
21229 }
21230
21231 cx.notify();
21232 }
21233
21234 pub fn set_searchable(&mut self, searchable: bool) {
21235 self.searchable = searchable;
21236 }
21237
21238 pub fn searchable(&self) -> bool {
21239 self.searchable
21240 }
21241
21242 fn open_proposed_changes_editor(
21243 &mut self,
21244 _: &OpenProposedChangesEditor,
21245 window: &mut Window,
21246 cx: &mut Context<Self>,
21247 ) {
21248 let Some(workspace) = self.workspace() else {
21249 cx.propagate();
21250 return;
21251 };
21252
21253 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
21254 let multi_buffer = self.buffer.read(cx);
21255 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
21256 let mut new_selections_by_buffer = HashMap::default();
21257 for selection in selections {
21258 for (buffer, range, _) in
21259 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
21260 {
21261 let mut range = range.to_point(buffer);
21262 range.start.column = 0;
21263 range.end.column = buffer.line_len(range.end.row);
21264 new_selections_by_buffer
21265 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
21266 .or_insert(Vec::new())
21267 .push(range)
21268 }
21269 }
21270
21271 let proposed_changes_buffers = new_selections_by_buffer
21272 .into_iter()
21273 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
21274 .collect::<Vec<_>>();
21275 let proposed_changes_editor = cx.new(|cx| {
21276 ProposedChangesEditor::new(
21277 "Proposed changes",
21278 proposed_changes_buffers,
21279 self.project.clone(),
21280 window,
21281 cx,
21282 )
21283 });
21284
21285 window.defer(cx, move |window, cx| {
21286 workspace.update(cx, |workspace, cx| {
21287 workspace.active_pane().update(cx, |pane, cx| {
21288 pane.add_item(
21289 Box::new(proposed_changes_editor),
21290 true,
21291 true,
21292 None,
21293 window,
21294 cx,
21295 );
21296 });
21297 });
21298 });
21299 }
21300
21301 pub fn open_excerpts_in_split(
21302 &mut self,
21303 _: &OpenExcerptsSplit,
21304 window: &mut Window,
21305 cx: &mut Context<Self>,
21306 ) {
21307 self.open_excerpts_common(None, true, window, cx)
21308 }
21309
21310 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21311 self.open_excerpts_common(None, false, window, cx)
21312 }
21313
21314 fn open_excerpts_common(
21315 &mut self,
21316 jump_data: Option<JumpData>,
21317 split: bool,
21318 window: &mut Window,
21319 cx: &mut Context<Self>,
21320 ) {
21321 let Some(workspace) = self.workspace() else {
21322 cx.propagate();
21323 return;
21324 };
21325
21326 if self.buffer.read(cx).is_singleton() {
21327 cx.propagate();
21328 return;
21329 }
21330
21331 let mut new_selections_by_buffer = HashMap::default();
21332 match &jump_data {
21333 Some(JumpData::MultiBufferPoint {
21334 excerpt_id,
21335 position,
21336 anchor,
21337 line_offset_from_top,
21338 }) => {
21339 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21340 if let Some(buffer) = multi_buffer_snapshot
21341 .buffer_id_for_excerpt(*excerpt_id)
21342 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21343 {
21344 let buffer_snapshot = buffer.read(cx).snapshot();
21345 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21346 language::ToPoint::to_point(anchor, &buffer_snapshot)
21347 } else {
21348 buffer_snapshot.clip_point(*position, Bias::Left)
21349 };
21350 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21351 new_selections_by_buffer.insert(
21352 buffer,
21353 (
21354 vec![jump_to_offset..jump_to_offset],
21355 Some(*line_offset_from_top),
21356 ),
21357 );
21358 }
21359 }
21360 Some(JumpData::MultiBufferRow {
21361 row,
21362 line_offset_from_top,
21363 }) => {
21364 let point = MultiBufferPoint::new(row.0, 0);
21365 if let Some((buffer, buffer_point, _)) =
21366 self.buffer.read(cx).point_to_buffer_point(point, cx)
21367 {
21368 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21369 new_selections_by_buffer
21370 .entry(buffer)
21371 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21372 .0
21373 .push(buffer_offset..buffer_offset)
21374 }
21375 }
21376 None => {
21377 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
21378 let multi_buffer = self.buffer.read(cx);
21379 for selection in selections {
21380 for (snapshot, range, _, anchor) in multi_buffer
21381 .snapshot(cx)
21382 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21383 {
21384 if let Some(anchor) = anchor {
21385 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21386 else {
21387 continue;
21388 };
21389 let offset = text::ToOffset::to_offset(
21390 &anchor.text_anchor,
21391 &buffer_handle.read(cx).snapshot(),
21392 );
21393 let range = offset..offset;
21394 new_selections_by_buffer
21395 .entry(buffer_handle)
21396 .or_insert((Vec::new(), None))
21397 .0
21398 .push(range)
21399 } else {
21400 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21401 else {
21402 continue;
21403 };
21404 new_selections_by_buffer
21405 .entry(buffer_handle)
21406 .or_insert((Vec::new(), None))
21407 .0
21408 .push(range)
21409 }
21410 }
21411 }
21412 }
21413 }
21414
21415 new_selections_by_buffer
21416 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21417
21418 if new_selections_by_buffer.is_empty() {
21419 return;
21420 }
21421
21422 // We defer the pane interaction because we ourselves are a workspace item
21423 // and activating a new item causes the pane to call a method on us reentrantly,
21424 // which panics if we're on the stack.
21425 window.defer(cx, move |window, cx| {
21426 workspace.update(cx, |workspace, cx| {
21427 let pane = if split {
21428 workspace.adjacent_pane(window, cx)
21429 } else {
21430 workspace.active_pane().clone()
21431 };
21432
21433 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21434 let editor = buffer
21435 .read(cx)
21436 .file()
21437 .is_none()
21438 .then(|| {
21439 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21440 // so `workspace.open_project_item` will never find them, always opening a new editor.
21441 // Instead, we try to activate the existing editor in the pane first.
21442 let (editor, pane_item_index) =
21443 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21444 let editor = item.downcast::<Editor>()?;
21445 let singleton_buffer =
21446 editor.read(cx).buffer().read(cx).as_singleton()?;
21447 if singleton_buffer == buffer {
21448 Some((editor, i))
21449 } else {
21450 None
21451 }
21452 })?;
21453 pane.update(cx, |pane, cx| {
21454 pane.activate_item(pane_item_index, true, true, window, cx)
21455 });
21456 Some(editor)
21457 })
21458 .flatten()
21459 .unwrap_or_else(|| {
21460 workspace.open_project_item::<Self>(
21461 pane.clone(),
21462 buffer,
21463 true,
21464 true,
21465 window,
21466 cx,
21467 )
21468 });
21469
21470 editor.update(cx, |editor, cx| {
21471 let autoscroll = match scroll_offset {
21472 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21473 None => Autoscroll::newest(),
21474 };
21475 let nav_history = editor.nav_history.take();
21476 editor.change_selections(
21477 SelectionEffects::scroll(autoscroll),
21478 window,
21479 cx,
21480 |s| {
21481 s.select_ranges(ranges);
21482 },
21483 );
21484 editor.nav_history = nav_history;
21485 });
21486 }
21487 })
21488 });
21489 }
21490
21491 // For now, don't allow opening excerpts in buffers that aren't backed by
21492 // regular project files.
21493 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21494 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21495 }
21496
21497 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21498 let snapshot = self.buffer.read(cx).read(cx);
21499 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21500 Some(
21501 ranges
21502 .iter()
21503 .map(move |range| {
21504 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21505 })
21506 .collect(),
21507 )
21508 }
21509
21510 fn selection_replacement_ranges(
21511 &self,
21512 range: Range<OffsetUtf16>,
21513 cx: &mut App,
21514 ) -> Vec<Range<OffsetUtf16>> {
21515 let selections = self
21516 .selections
21517 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21518 let newest_selection = selections
21519 .iter()
21520 .max_by_key(|selection| selection.id)
21521 .unwrap();
21522 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21523 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21524 let snapshot = self.buffer.read(cx).read(cx);
21525 selections
21526 .into_iter()
21527 .map(|mut selection| {
21528 selection.start.0 =
21529 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21530 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21531 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21532 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21533 })
21534 .collect()
21535 }
21536
21537 fn report_editor_event(
21538 &self,
21539 reported_event: ReportEditorEvent,
21540 file_extension: Option<String>,
21541 cx: &App,
21542 ) {
21543 if cfg!(any(test, feature = "test-support")) {
21544 return;
21545 }
21546
21547 let Some(project) = &self.project else { return };
21548
21549 // If None, we are in a file without an extension
21550 let file = self
21551 .buffer
21552 .read(cx)
21553 .as_singleton()
21554 .and_then(|b| b.read(cx).file());
21555 let file_extension = file_extension.or(file
21556 .as_ref()
21557 .and_then(|file| Path::new(file.file_name(cx)).extension())
21558 .and_then(|e| e.to_str())
21559 .map(|a| a.to_string()));
21560
21561 let vim_mode = vim_enabled(cx);
21562
21563 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21564 let copilot_enabled = edit_predictions_provider
21565 == language::language_settings::EditPredictionProvider::Copilot;
21566 let copilot_enabled_for_language = self
21567 .buffer
21568 .read(cx)
21569 .language_settings(cx)
21570 .show_edit_predictions;
21571
21572 let project = project.read(cx);
21573 let event_type = reported_event.event_type();
21574
21575 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21576 telemetry::event!(
21577 event_type,
21578 type = if auto_saved {"autosave"} else {"manual"},
21579 file_extension,
21580 vim_mode,
21581 copilot_enabled,
21582 copilot_enabled_for_language,
21583 edit_predictions_provider,
21584 is_via_ssh = project.is_via_remote_server(),
21585 );
21586 } else {
21587 telemetry::event!(
21588 event_type,
21589 file_extension,
21590 vim_mode,
21591 copilot_enabled,
21592 copilot_enabled_for_language,
21593 edit_predictions_provider,
21594 is_via_ssh = project.is_via_remote_server(),
21595 );
21596 };
21597 }
21598
21599 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21600 /// with each line being an array of {text, highlight} objects.
21601 fn copy_highlight_json(
21602 &mut self,
21603 _: &CopyHighlightJson,
21604 window: &mut Window,
21605 cx: &mut Context<Self>,
21606 ) {
21607 #[derive(Serialize)]
21608 struct Chunk<'a> {
21609 text: String,
21610 highlight: Option<&'a str>,
21611 }
21612
21613 let snapshot = self.buffer.read(cx).snapshot(cx);
21614 let range = self
21615 .selected_text_range(false, window, cx)
21616 .and_then(|selection| {
21617 if selection.range.is_empty() {
21618 None
21619 } else {
21620 Some(selection.range)
21621 }
21622 })
21623 .unwrap_or_else(|| 0..snapshot.len());
21624
21625 let chunks = snapshot.chunks(range, true);
21626 let mut lines = Vec::new();
21627 let mut line: VecDeque<Chunk> = VecDeque::new();
21628
21629 let Some(style) = self.style.as_ref() else {
21630 return;
21631 };
21632
21633 for chunk in chunks {
21634 let highlight = chunk
21635 .syntax_highlight_id
21636 .and_then(|id| id.name(&style.syntax));
21637 let mut chunk_lines = chunk.text.split('\n').peekable();
21638 while let Some(text) = chunk_lines.next() {
21639 let mut merged_with_last_token = false;
21640 if let Some(last_token) = line.back_mut()
21641 && last_token.highlight == highlight
21642 {
21643 last_token.text.push_str(text);
21644 merged_with_last_token = true;
21645 }
21646
21647 if !merged_with_last_token {
21648 line.push_back(Chunk {
21649 text: text.into(),
21650 highlight,
21651 });
21652 }
21653
21654 if chunk_lines.peek().is_some() {
21655 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21656 line.pop_front();
21657 }
21658 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21659 line.pop_back();
21660 }
21661
21662 lines.push(mem::take(&mut line));
21663 }
21664 }
21665 }
21666
21667 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21668 return;
21669 };
21670 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21671 }
21672
21673 pub fn open_context_menu(
21674 &mut self,
21675 _: &OpenContextMenu,
21676 window: &mut Window,
21677 cx: &mut Context<Self>,
21678 ) {
21679 self.request_autoscroll(Autoscroll::newest(), cx);
21680 let position = self
21681 .selections
21682 .newest_display(&self.display_snapshot(cx))
21683 .start;
21684 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21685 }
21686
21687 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
21688 &self.inlay_hint_cache
21689 }
21690
21691 pub fn replay_insert_event(
21692 &mut self,
21693 text: &str,
21694 relative_utf16_range: Option<Range<isize>>,
21695 window: &mut Window,
21696 cx: &mut Context<Self>,
21697 ) {
21698 if !self.input_enabled {
21699 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21700 return;
21701 }
21702 if let Some(relative_utf16_range) = relative_utf16_range {
21703 let selections = self
21704 .selections
21705 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21706 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21707 let new_ranges = selections.into_iter().map(|range| {
21708 let start = OffsetUtf16(
21709 range
21710 .head()
21711 .0
21712 .saturating_add_signed(relative_utf16_range.start),
21713 );
21714 let end = OffsetUtf16(
21715 range
21716 .head()
21717 .0
21718 .saturating_add_signed(relative_utf16_range.end),
21719 );
21720 start..end
21721 });
21722 s.select_ranges(new_ranges);
21723 });
21724 }
21725
21726 self.handle_input(text, window, cx);
21727 }
21728
21729 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
21730 let Some(provider) = self.semantics_provider.as_ref() else {
21731 return false;
21732 };
21733
21734 let mut supports = false;
21735 self.buffer().update(cx, |this, cx| {
21736 this.for_each_buffer(|buffer| {
21737 supports |= provider.supports_inlay_hints(buffer, cx);
21738 });
21739 });
21740
21741 supports
21742 }
21743
21744 pub fn is_focused(&self, window: &Window) -> bool {
21745 self.focus_handle.is_focused(window)
21746 }
21747
21748 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21749 cx.emit(EditorEvent::Focused);
21750
21751 if let Some(descendant) = self
21752 .last_focused_descendant
21753 .take()
21754 .and_then(|descendant| descendant.upgrade())
21755 {
21756 window.focus(&descendant);
21757 } else {
21758 if let Some(blame) = self.blame.as_ref() {
21759 blame.update(cx, GitBlame::focus)
21760 }
21761
21762 self.blink_manager.update(cx, BlinkManager::enable);
21763 self.show_cursor_names(window, cx);
21764 self.buffer.update(cx, |buffer, cx| {
21765 buffer.finalize_last_transaction(cx);
21766 if self.leader_id.is_none() {
21767 buffer.set_active_selections(
21768 &self.selections.disjoint_anchors_arc(),
21769 self.selections.line_mode(),
21770 self.cursor_shape,
21771 cx,
21772 );
21773 }
21774 });
21775 }
21776 }
21777
21778 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21779 cx.emit(EditorEvent::FocusedIn)
21780 }
21781
21782 fn handle_focus_out(
21783 &mut self,
21784 event: FocusOutEvent,
21785 _window: &mut Window,
21786 cx: &mut Context<Self>,
21787 ) {
21788 if event.blurred != self.focus_handle {
21789 self.last_focused_descendant = Some(event.blurred);
21790 }
21791 self.selection_drag_state = SelectionDragState::None;
21792 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21793 }
21794
21795 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21796 self.blink_manager.update(cx, BlinkManager::disable);
21797 self.buffer
21798 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21799
21800 if let Some(blame) = self.blame.as_ref() {
21801 blame.update(cx, GitBlame::blur)
21802 }
21803 if !self.hover_state.focused(window, cx) {
21804 hide_hover(self, cx);
21805 }
21806 if !self
21807 .context_menu
21808 .borrow()
21809 .as_ref()
21810 .is_some_and(|context_menu| context_menu.focused(window, cx))
21811 {
21812 self.hide_context_menu(window, cx);
21813 }
21814 self.take_active_edit_prediction(cx);
21815 cx.emit(EditorEvent::Blurred);
21816 cx.notify();
21817 }
21818
21819 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21820 let mut pending: String = window
21821 .pending_input_keystrokes()
21822 .into_iter()
21823 .flatten()
21824 .filter_map(|keystroke| {
21825 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21826 keystroke.key_char.clone()
21827 } else {
21828 None
21829 }
21830 })
21831 .collect();
21832
21833 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21834 pending = "".to_string();
21835 }
21836
21837 let existing_pending = self
21838 .text_highlights::<PendingInput>(cx)
21839 .map(|(_, ranges)| ranges.to_vec());
21840 if existing_pending.is_none() && pending.is_empty() {
21841 return;
21842 }
21843 let transaction =
21844 self.transact(window, cx, |this, window, cx| {
21845 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
21846 let edits = selections
21847 .iter()
21848 .map(|selection| (selection.end..selection.end, pending.clone()));
21849 this.edit(edits, cx);
21850 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21851 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21852 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21853 }));
21854 });
21855 if let Some(existing_ranges) = existing_pending {
21856 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21857 this.edit(edits, cx);
21858 }
21859 });
21860
21861 let snapshot = self.snapshot(window, cx);
21862 let ranges = self
21863 .selections
21864 .all::<usize>(&snapshot.display_snapshot)
21865 .into_iter()
21866 .map(|selection| {
21867 snapshot.buffer_snapshot().anchor_after(selection.end)
21868 ..snapshot
21869 .buffer_snapshot()
21870 .anchor_before(selection.end + pending.len())
21871 })
21872 .collect();
21873
21874 if pending.is_empty() {
21875 self.clear_highlights::<PendingInput>(cx);
21876 } else {
21877 self.highlight_text::<PendingInput>(
21878 ranges,
21879 HighlightStyle {
21880 underline: Some(UnderlineStyle {
21881 thickness: px(1.),
21882 color: None,
21883 wavy: false,
21884 }),
21885 ..Default::default()
21886 },
21887 cx,
21888 );
21889 }
21890
21891 self.ime_transaction = self.ime_transaction.or(transaction);
21892 if let Some(transaction) = self.ime_transaction {
21893 self.buffer.update(cx, |buffer, cx| {
21894 buffer.group_until_transaction(transaction, cx);
21895 });
21896 }
21897
21898 if self.text_highlights::<PendingInput>(cx).is_none() {
21899 self.ime_transaction.take();
21900 }
21901 }
21902
21903 pub fn register_action_renderer(
21904 &mut self,
21905 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21906 ) -> Subscription {
21907 let id = self.next_editor_action_id.post_inc();
21908 self.editor_actions
21909 .borrow_mut()
21910 .insert(id, Box::new(listener));
21911
21912 let editor_actions = self.editor_actions.clone();
21913 Subscription::new(move || {
21914 editor_actions.borrow_mut().remove(&id);
21915 })
21916 }
21917
21918 pub fn register_action<A: Action>(
21919 &mut self,
21920 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21921 ) -> Subscription {
21922 let id = self.next_editor_action_id.post_inc();
21923 let listener = Arc::new(listener);
21924 self.editor_actions.borrow_mut().insert(
21925 id,
21926 Box::new(move |_, window, _| {
21927 let listener = listener.clone();
21928 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21929 let action = action.downcast_ref().unwrap();
21930 if phase == DispatchPhase::Bubble {
21931 listener(action, window, cx)
21932 }
21933 })
21934 }),
21935 );
21936
21937 let editor_actions = self.editor_actions.clone();
21938 Subscription::new(move || {
21939 editor_actions.borrow_mut().remove(&id);
21940 })
21941 }
21942
21943 pub fn file_header_size(&self) -> u32 {
21944 FILE_HEADER_HEIGHT
21945 }
21946
21947 pub fn restore(
21948 &mut self,
21949 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21950 window: &mut Window,
21951 cx: &mut Context<Self>,
21952 ) {
21953 let workspace = self.workspace();
21954 let project = self.project();
21955 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21956 let mut tasks = Vec::new();
21957 for (buffer_id, changes) in revert_changes {
21958 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21959 buffer.update(cx, |buffer, cx| {
21960 buffer.edit(
21961 changes
21962 .into_iter()
21963 .map(|(range, text)| (range, text.to_string())),
21964 None,
21965 cx,
21966 );
21967 });
21968
21969 if let Some(project) =
21970 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21971 {
21972 project.update(cx, |project, cx| {
21973 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21974 })
21975 }
21976 }
21977 }
21978 tasks
21979 });
21980 cx.spawn_in(window, async move |_, cx| {
21981 for (buffer, task) in save_tasks {
21982 let result = task.await;
21983 if result.is_err() {
21984 let Some(path) = buffer
21985 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21986 .ok()
21987 else {
21988 continue;
21989 };
21990 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21991 let Some(task) = cx
21992 .update_window_entity(workspace, |workspace, window, cx| {
21993 workspace
21994 .open_path_preview(path, None, false, false, false, window, cx)
21995 })
21996 .ok()
21997 else {
21998 continue;
21999 };
22000 task.await.log_err();
22001 }
22002 }
22003 }
22004 })
22005 .detach();
22006 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22007 selections.refresh()
22008 });
22009 }
22010
22011 pub fn to_pixel_point(
22012 &self,
22013 source: multi_buffer::Anchor,
22014 editor_snapshot: &EditorSnapshot,
22015 window: &mut Window,
22016 ) -> Option<gpui::Point<Pixels>> {
22017 let source_point = source.to_display_point(editor_snapshot);
22018 self.display_to_pixel_point(source_point, editor_snapshot, window)
22019 }
22020
22021 pub fn display_to_pixel_point(
22022 &self,
22023 source: DisplayPoint,
22024 editor_snapshot: &EditorSnapshot,
22025 window: &mut Window,
22026 ) -> Option<gpui::Point<Pixels>> {
22027 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
22028 let text_layout_details = self.text_layout_details(window);
22029 let scroll_top = text_layout_details
22030 .scroll_anchor
22031 .scroll_position(editor_snapshot)
22032 .y;
22033
22034 if source.row().as_f64() < scroll_top.floor() {
22035 return None;
22036 }
22037 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
22038 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
22039 Some(gpui::Point::new(source_x, source_y))
22040 }
22041
22042 pub fn has_visible_completions_menu(&self) -> bool {
22043 !self.edit_prediction_preview_is_active()
22044 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
22045 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
22046 })
22047 }
22048
22049 pub fn register_addon<T: Addon>(&mut self, instance: T) {
22050 if self.mode.is_minimap() {
22051 return;
22052 }
22053 self.addons
22054 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
22055 }
22056
22057 pub fn unregister_addon<T: Addon>(&mut self) {
22058 self.addons.remove(&std::any::TypeId::of::<T>());
22059 }
22060
22061 pub fn addon<T: Addon>(&self) -> Option<&T> {
22062 let type_id = std::any::TypeId::of::<T>();
22063 self.addons
22064 .get(&type_id)
22065 .and_then(|item| item.to_any().downcast_ref::<T>())
22066 }
22067
22068 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
22069 let type_id = std::any::TypeId::of::<T>();
22070 self.addons
22071 .get_mut(&type_id)
22072 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
22073 }
22074
22075 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
22076 let text_layout_details = self.text_layout_details(window);
22077 let style = &text_layout_details.editor_style;
22078 let font_id = window.text_system().resolve_font(&style.text.font());
22079 let font_size = style.text.font_size.to_pixels(window.rem_size());
22080 let line_height = style.text.line_height_in_pixels(window.rem_size());
22081 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
22082 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
22083
22084 CharacterDimensions {
22085 em_width,
22086 em_advance,
22087 line_height,
22088 }
22089 }
22090
22091 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
22092 self.load_diff_task.clone()
22093 }
22094
22095 fn read_metadata_from_db(
22096 &mut self,
22097 item_id: u64,
22098 workspace_id: WorkspaceId,
22099 window: &mut Window,
22100 cx: &mut Context<Editor>,
22101 ) {
22102 if self.buffer_kind(cx) == ItemBufferKind::Singleton
22103 && !self.mode.is_minimap()
22104 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
22105 {
22106 let buffer_snapshot = OnceCell::new();
22107
22108 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
22109 && !folds.is_empty()
22110 {
22111 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22112 self.fold_ranges(
22113 folds
22114 .into_iter()
22115 .map(|(start, end)| {
22116 snapshot.clip_offset(start, Bias::Left)
22117 ..snapshot.clip_offset(end, Bias::Right)
22118 })
22119 .collect(),
22120 false,
22121 window,
22122 cx,
22123 );
22124 }
22125
22126 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22127 && !selections.is_empty()
22128 {
22129 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22130 // skip adding the initial selection to selection history
22131 self.selection_history.mode = SelectionHistoryMode::Skipping;
22132 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22133 s.select_ranges(selections.into_iter().map(|(start, end)| {
22134 snapshot.clip_offset(start, Bias::Left)
22135 ..snapshot.clip_offset(end, Bias::Right)
22136 }));
22137 });
22138 self.selection_history.mode = SelectionHistoryMode::Normal;
22139 };
22140 }
22141
22142 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22143 }
22144
22145 fn update_lsp_data(
22146 &mut self,
22147 for_buffer: Option<BufferId>,
22148 window: &mut Window,
22149 cx: &mut Context<'_, Self>,
22150 ) {
22151 self.pull_diagnostics(for_buffer, window, cx);
22152 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22153 }
22154
22155 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22156 if self.ignore_lsp_data() {
22157 return;
22158 }
22159 for (_, (visible_buffer, _, _)) in self.visible_excerpts(None, cx) {
22160 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22161 }
22162 }
22163
22164 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) -> bool {
22165 if !self.registered_buffers.contains_key(&buffer_id)
22166 && let Some(project) = self.project.as_ref()
22167 {
22168 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22169 project.update(cx, |project, cx| {
22170 self.registered_buffers.insert(
22171 buffer_id,
22172 project.register_buffer_with_language_servers(&buffer, cx),
22173 );
22174 });
22175 return true;
22176 } else {
22177 self.registered_buffers.remove(&buffer_id);
22178 }
22179 }
22180
22181 false
22182 }
22183
22184 fn ignore_lsp_data(&self) -> bool {
22185 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22186 // skip any LSP updates for it.
22187 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22188 }
22189}
22190
22191fn edit_for_markdown_paste<'a>(
22192 buffer: &MultiBufferSnapshot,
22193 range: Range<usize>,
22194 to_insert: &'a str,
22195 url: Option<url::Url>,
22196) -> (Range<usize>, Cow<'a, str>) {
22197 if url.is_none() {
22198 return (range, Cow::Borrowed(to_insert));
22199 };
22200
22201 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22202
22203 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22204 Cow::Borrowed(to_insert)
22205 } else {
22206 Cow::Owned(format!("[{old_text}]({to_insert})"))
22207 };
22208 (range, new_text)
22209}
22210
22211fn vim_enabled(cx: &App) -> bool {
22212 vim_mode_setting::VimModeSetting::try_get(cx)
22213 .map(|vim_mode| vim_mode.0)
22214 .unwrap_or(false)
22215}
22216
22217fn process_completion_for_edit(
22218 completion: &Completion,
22219 intent: CompletionIntent,
22220 buffer: &Entity<Buffer>,
22221 cursor_position: &text::Anchor,
22222 cx: &mut Context<Editor>,
22223) -> CompletionEdit {
22224 let buffer = buffer.read(cx);
22225 let buffer_snapshot = buffer.snapshot();
22226 let (snippet, new_text) = if completion.is_snippet() {
22227 let mut snippet_source = completion.new_text.clone();
22228 // Workaround for typescript language server issues so that methods don't expand within
22229 // strings and functions with type expressions. The previous point is used because the query
22230 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22231 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22232 let previous_point = if previous_point.column > 0 {
22233 cursor_position.to_previous_offset(&buffer_snapshot)
22234 } else {
22235 cursor_position.to_offset(&buffer_snapshot)
22236 };
22237 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22238 && scope.prefers_label_for_snippet_in_completion()
22239 && let Some(label) = completion.label()
22240 && matches!(
22241 completion.kind(),
22242 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22243 )
22244 {
22245 snippet_source = label;
22246 }
22247 match Snippet::parse(&snippet_source).log_err() {
22248 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22249 None => (None, completion.new_text.clone()),
22250 }
22251 } else {
22252 (None, completion.new_text.clone())
22253 };
22254
22255 let mut range_to_replace = {
22256 let replace_range = &completion.replace_range;
22257 if let CompletionSource::Lsp {
22258 insert_range: Some(insert_range),
22259 ..
22260 } = &completion.source
22261 {
22262 debug_assert_eq!(
22263 insert_range.start, replace_range.start,
22264 "insert_range and replace_range should start at the same position"
22265 );
22266 debug_assert!(
22267 insert_range
22268 .start
22269 .cmp(cursor_position, &buffer_snapshot)
22270 .is_le(),
22271 "insert_range should start before or at cursor position"
22272 );
22273 debug_assert!(
22274 replace_range
22275 .start
22276 .cmp(cursor_position, &buffer_snapshot)
22277 .is_le(),
22278 "replace_range should start before or at cursor position"
22279 );
22280
22281 let should_replace = match intent {
22282 CompletionIntent::CompleteWithInsert => false,
22283 CompletionIntent::CompleteWithReplace => true,
22284 CompletionIntent::Complete | CompletionIntent::Compose => {
22285 let insert_mode =
22286 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22287 .completions
22288 .lsp_insert_mode;
22289 match insert_mode {
22290 LspInsertMode::Insert => false,
22291 LspInsertMode::Replace => true,
22292 LspInsertMode::ReplaceSubsequence => {
22293 let mut text_to_replace = buffer.chars_for_range(
22294 buffer.anchor_before(replace_range.start)
22295 ..buffer.anchor_after(replace_range.end),
22296 );
22297 let mut current_needle = text_to_replace.next();
22298 for haystack_ch in completion.label.text.chars() {
22299 if let Some(needle_ch) = current_needle
22300 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22301 {
22302 current_needle = text_to_replace.next();
22303 }
22304 }
22305 current_needle.is_none()
22306 }
22307 LspInsertMode::ReplaceSuffix => {
22308 if replace_range
22309 .end
22310 .cmp(cursor_position, &buffer_snapshot)
22311 .is_gt()
22312 {
22313 let range_after_cursor = *cursor_position..replace_range.end;
22314 let text_after_cursor = buffer
22315 .text_for_range(
22316 buffer.anchor_before(range_after_cursor.start)
22317 ..buffer.anchor_after(range_after_cursor.end),
22318 )
22319 .collect::<String>()
22320 .to_ascii_lowercase();
22321 completion
22322 .label
22323 .text
22324 .to_ascii_lowercase()
22325 .ends_with(&text_after_cursor)
22326 } else {
22327 true
22328 }
22329 }
22330 }
22331 }
22332 };
22333
22334 if should_replace {
22335 replace_range.clone()
22336 } else {
22337 insert_range.clone()
22338 }
22339 } else {
22340 replace_range.clone()
22341 }
22342 };
22343
22344 if range_to_replace
22345 .end
22346 .cmp(cursor_position, &buffer_snapshot)
22347 .is_lt()
22348 {
22349 range_to_replace.end = *cursor_position;
22350 }
22351
22352 CompletionEdit {
22353 new_text,
22354 replace_range: range_to_replace.to_offset(buffer),
22355 snippet,
22356 }
22357}
22358
22359struct CompletionEdit {
22360 new_text: String,
22361 replace_range: Range<usize>,
22362 snippet: Option<Snippet>,
22363}
22364
22365fn insert_extra_newline_brackets(
22366 buffer: &MultiBufferSnapshot,
22367 range: Range<usize>,
22368 language: &language::LanguageScope,
22369) -> bool {
22370 let leading_whitespace_len = buffer
22371 .reversed_chars_at(range.start)
22372 .take_while(|c| c.is_whitespace() && *c != '\n')
22373 .map(|c| c.len_utf8())
22374 .sum::<usize>();
22375 let trailing_whitespace_len = buffer
22376 .chars_at(range.end)
22377 .take_while(|c| c.is_whitespace() && *c != '\n')
22378 .map(|c| c.len_utf8())
22379 .sum::<usize>();
22380 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22381
22382 language.brackets().any(|(pair, enabled)| {
22383 let pair_start = pair.start.trim_end();
22384 let pair_end = pair.end.trim_start();
22385
22386 enabled
22387 && pair.newline
22388 && buffer.contains_str_at(range.end, pair_end)
22389 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22390 })
22391}
22392
22393fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22394 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22395 [(buffer, range, _)] => (*buffer, range.clone()),
22396 _ => return false,
22397 };
22398 let pair = {
22399 let mut result: Option<BracketMatch> = None;
22400
22401 for pair in buffer
22402 .all_bracket_ranges(range.clone())
22403 .filter(move |pair| {
22404 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22405 })
22406 {
22407 let len = pair.close_range.end - pair.open_range.start;
22408
22409 if let Some(existing) = &result {
22410 let existing_len = existing.close_range.end - existing.open_range.start;
22411 if len > existing_len {
22412 continue;
22413 }
22414 }
22415
22416 result = Some(pair);
22417 }
22418
22419 result
22420 };
22421 let Some(pair) = pair else {
22422 return false;
22423 };
22424 pair.newline_only
22425 && buffer
22426 .chars_for_range(pair.open_range.end..range.start)
22427 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22428 .all(|c| c.is_whitespace() && c != '\n')
22429}
22430
22431fn update_uncommitted_diff_for_buffer(
22432 editor: Entity<Editor>,
22433 project: &Entity<Project>,
22434 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22435 buffer: Entity<MultiBuffer>,
22436 cx: &mut App,
22437) -> Task<()> {
22438 let mut tasks = Vec::new();
22439 project.update(cx, |project, cx| {
22440 for buffer in buffers {
22441 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22442 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22443 }
22444 }
22445 });
22446 cx.spawn(async move |cx| {
22447 let diffs = future::join_all(tasks).await;
22448 if editor
22449 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22450 .unwrap_or(false)
22451 {
22452 return;
22453 }
22454
22455 buffer
22456 .update(cx, |buffer, cx| {
22457 for diff in diffs.into_iter().flatten() {
22458 buffer.add_diff(diff, cx);
22459 }
22460 })
22461 .ok();
22462 })
22463}
22464
22465fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22466 let tab_size = tab_size.get() as usize;
22467 let mut width = offset;
22468
22469 for ch in text.chars() {
22470 width += if ch == '\t' {
22471 tab_size - (width % tab_size)
22472 } else {
22473 1
22474 };
22475 }
22476
22477 width - offset
22478}
22479
22480#[cfg(test)]
22481mod tests {
22482 use super::*;
22483
22484 #[test]
22485 fn test_string_size_with_expanded_tabs() {
22486 let nz = |val| NonZeroU32::new(val).unwrap();
22487 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22488 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22489 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22490 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22491 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22492 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22493 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22494 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22495 }
22496}
22497
22498/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22499struct WordBreakingTokenizer<'a> {
22500 input: &'a str,
22501}
22502
22503impl<'a> WordBreakingTokenizer<'a> {
22504 fn new(input: &'a str) -> Self {
22505 Self { input }
22506 }
22507}
22508
22509fn is_char_ideographic(ch: char) -> bool {
22510 use unicode_script::Script::*;
22511 use unicode_script::UnicodeScript;
22512 matches!(ch.script(), Han | Tangut | Yi)
22513}
22514
22515fn is_grapheme_ideographic(text: &str) -> bool {
22516 text.chars().any(is_char_ideographic)
22517}
22518
22519fn is_grapheme_whitespace(text: &str) -> bool {
22520 text.chars().any(|x| x.is_whitespace())
22521}
22522
22523fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22524 text.chars()
22525 .next()
22526 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22527}
22528
22529#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22530enum WordBreakToken<'a> {
22531 Word { token: &'a str, grapheme_len: usize },
22532 InlineWhitespace { token: &'a str, grapheme_len: usize },
22533 Newline,
22534}
22535
22536impl<'a> Iterator for WordBreakingTokenizer<'a> {
22537 /// Yields a span, the count of graphemes in the token, and whether it was
22538 /// whitespace. Note that it also breaks at word boundaries.
22539 type Item = WordBreakToken<'a>;
22540
22541 fn next(&mut self) -> Option<Self::Item> {
22542 use unicode_segmentation::UnicodeSegmentation;
22543 if self.input.is_empty() {
22544 return None;
22545 }
22546
22547 let mut iter = self.input.graphemes(true).peekable();
22548 let mut offset = 0;
22549 let mut grapheme_len = 0;
22550 if let Some(first_grapheme) = iter.next() {
22551 let is_newline = first_grapheme == "\n";
22552 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22553 offset += first_grapheme.len();
22554 grapheme_len += 1;
22555 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22556 if let Some(grapheme) = iter.peek().copied()
22557 && should_stay_with_preceding_ideograph(grapheme)
22558 {
22559 offset += grapheme.len();
22560 grapheme_len += 1;
22561 }
22562 } else {
22563 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22564 let mut next_word_bound = words.peek().copied();
22565 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22566 next_word_bound = words.next();
22567 }
22568 while let Some(grapheme) = iter.peek().copied() {
22569 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22570 break;
22571 };
22572 if is_grapheme_whitespace(grapheme) != is_whitespace
22573 || (grapheme == "\n") != is_newline
22574 {
22575 break;
22576 };
22577 offset += grapheme.len();
22578 grapheme_len += 1;
22579 iter.next();
22580 }
22581 }
22582 let token = &self.input[..offset];
22583 self.input = &self.input[offset..];
22584 if token == "\n" {
22585 Some(WordBreakToken::Newline)
22586 } else if is_whitespace {
22587 Some(WordBreakToken::InlineWhitespace {
22588 token,
22589 grapheme_len,
22590 })
22591 } else {
22592 Some(WordBreakToken::Word {
22593 token,
22594 grapheme_len,
22595 })
22596 }
22597 } else {
22598 None
22599 }
22600 }
22601}
22602
22603#[test]
22604fn test_word_breaking_tokenizer() {
22605 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22606 ("", &[]),
22607 (" ", &[whitespace(" ", 2)]),
22608 ("Ʒ", &[word("Ʒ", 1)]),
22609 ("Ǽ", &[word("Ǽ", 1)]),
22610 ("⋑", &[word("⋑", 1)]),
22611 ("⋑⋑", &[word("⋑⋑", 2)]),
22612 (
22613 "原理,进而",
22614 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22615 ),
22616 (
22617 "hello world",
22618 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22619 ),
22620 (
22621 "hello, world",
22622 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22623 ),
22624 (
22625 " hello world",
22626 &[
22627 whitespace(" ", 2),
22628 word("hello", 5),
22629 whitespace(" ", 1),
22630 word("world", 5),
22631 ],
22632 ),
22633 (
22634 "这是什么 \n 钢笔",
22635 &[
22636 word("这", 1),
22637 word("是", 1),
22638 word("什", 1),
22639 word("么", 1),
22640 whitespace(" ", 1),
22641 newline(),
22642 whitespace(" ", 1),
22643 word("钢", 1),
22644 word("笔", 1),
22645 ],
22646 ),
22647 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22648 ];
22649
22650 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22651 WordBreakToken::Word {
22652 token,
22653 grapheme_len,
22654 }
22655 }
22656
22657 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22658 WordBreakToken::InlineWhitespace {
22659 token,
22660 grapheme_len,
22661 }
22662 }
22663
22664 fn newline() -> WordBreakToken<'static> {
22665 WordBreakToken::Newline
22666 }
22667
22668 for (input, result) in tests {
22669 assert_eq!(
22670 WordBreakingTokenizer::new(input)
22671 .collect::<Vec<_>>()
22672 .as_slice(),
22673 *result,
22674 );
22675 }
22676}
22677
22678fn wrap_with_prefix(
22679 first_line_prefix: String,
22680 subsequent_lines_prefix: String,
22681 unwrapped_text: String,
22682 wrap_column: usize,
22683 tab_size: NonZeroU32,
22684 preserve_existing_whitespace: bool,
22685) -> String {
22686 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22687 let subsequent_lines_prefix_len =
22688 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22689 let mut wrapped_text = String::new();
22690 let mut current_line = first_line_prefix;
22691 let mut is_first_line = true;
22692
22693 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22694 let mut current_line_len = first_line_prefix_len;
22695 let mut in_whitespace = false;
22696 for token in tokenizer {
22697 let have_preceding_whitespace = in_whitespace;
22698 match token {
22699 WordBreakToken::Word {
22700 token,
22701 grapheme_len,
22702 } => {
22703 in_whitespace = false;
22704 let current_prefix_len = if is_first_line {
22705 first_line_prefix_len
22706 } else {
22707 subsequent_lines_prefix_len
22708 };
22709 if current_line_len + grapheme_len > wrap_column
22710 && current_line_len != current_prefix_len
22711 {
22712 wrapped_text.push_str(current_line.trim_end());
22713 wrapped_text.push('\n');
22714 is_first_line = false;
22715 current_line = subsequent_lines_prefix.clone();
22716 current_line_len = subsequent_lines_prefix_len;
22717 }
22718 current_line.push_str(token);
22719 current_line_len += grapheme_len;
22720 }
22721 WordBreakToken::InlineWhitespace {
22722 mut token,
22723 mut grapheme_len,
22724 } => {
22725 in_whitespace = true;
22726 if have_preceding_whitespace && !preserve_existing_whitespace {
22727 continue;
22728 }
22729 if !preserve_existing_whitespace {
22730 // Keep a single whitespace grapheme as-is
22731 if let Some(first) =
22732 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22733 {
22734 token = first;
22735 } else {
22736 token = " ";
22737 }
22738 grapheme_len = 1;
22739 }
22740 let current_prefix_len = if is_first_line {
22741 first_line_prefix_len
22742 } else {
22743 subsequent_lines_prefix_len
22744 };
22745 if current_line_len + grapheme_len > wrap_column {
22746 wrapped_text.push_str(current_line.trim_end());
22747 wrapped_text.push('\n');
22748 is_first_line = false;
22749 current_line = subsequent_lines_prefix.clone();
22750 current_line_len = subsequent_lines_prefix_len;
22751 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22752 current_line.push_str(token);
22753 current_line_len += grapheme_len;
22754 }
22755 }
22756 WordBreakToken::Newline => {
22757 in_whitespace = true;
22758 let current_prefix_len = if is_first_line {
22759 first_line_prefix_len
22760 } else {
22761 subsequent_lines_prefix_len
22762 };
22763 if preserve_existing_whitespace {
22764 wrapped_text.push_str(current_line.trim_end());
22765 wrapped_text.push('\n');
22766 is_first_line = false;
22767 current_line = subsequent_lines_prefix.clone();
22768 current_line_len = subsequent_lines_prefix_len;
22769 } else if have_preceding_whitespace {
22770 continue;
22771 } else if current_line_len + 1 > wrap_column
22772 && current_line_len != current_prefix_len
22773 {
22774 wrapped_text.push_str(current_line.trim_end());
22775 wrapped_text.push('\n');
22776 is_first_line = false;
22777 current_line = subsequent_lines_prefix.clone();
22778 current_line_len = subsequent_lines_prefix_len;
22779 } else if current_line_len != current_prefix_len {
22780 current_line.push(' ');
22781 current_line_len += 1;
22782 }
22783 }
22784 }
22785 }
22786
22787 if !current_line.is_empty() {
22788 wrapped_text.push_str(¤t_line);
22789 }
22790 wrapped_text
22791}
22792
22793#[test]
22794fn test_wrap_with_prefix() {
22795 assert_eq!(
22796 wrap_with_prefix(
22797 "# ".to_string(),
22798 "# ".to_string(),
22799 "abcdefg".to_string(),
22800 4,
22801 NonZeroU32::new(4).unwrap(),
22802 false,
22803 ),
22804 "# abcdefg"
22805 );
22806 assert_eq!(
22807 wrap_with_prefix(
22808 "".to_string(),
22809 "".to_string(),
22810 "\thello world".to_string(),
22811 8,
22812 NonZeroU32::new(4).unwrap(),
22813 false,
22814 ),
22815 "hello\nworld"
22816 );
22817 assert_eq!(
22818 wrap_with_prefix(
22819 "// ".to_string(),
22820 "// ".to_string(),
22821 "xx \nyy zz aa bb cc".to_string(),
22822 12,
22823 NonZeroU32::new(4).unwrap(),
22824 false,
22825 ),
22826 "// xx yy zz\n// aa bb cc"
22827 );
22828 assert_eq!(
22829 wrap_with_prefix(
22830 String::new(),
22831 String::new(),
22832 "这是什么 \n 钢笔".to_string(),
22833 3,
22834 NonZeroU32::new(4).unwrap(),
22835 false,
22836 ),
22837 "这是什\n么 钢\n笔"
22838 );
22839 assert_eq!(
22840 wrap_with_prefix(
22841 String::new(),
22842 String::new(),
22843 format!("foo{}bar", '\u{2009}'), // thin space
22844 80,
22845 NonZeroU32::new(4).unwrap(),
22846 false,
22847 ),
22848 format!("foo{}bar", '\u{2009}')
22849 );
22850}
22851
22852pub trait CollaborationHub {
22853 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22854 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22855 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22856}
22857
22858impl CollaborationHub for Entity<Project> {
22859 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22860 self.read(cx).collaborators()
22861 }
22862
22863 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22864 self.read(cx).user_store().read(cx).participant_indices()
22865 }
22866
22867 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22868 let this = self.read(cx);
22869 let user_ids = this.collaborators().values().map(|c| c.user_id);
22870 this.user_store().read(cx).participant_names(user_ids, cx)
22871 }
22872}
22873
22874pub trait SemanticsProvider {
22875 fn hover(
22876 &self,
22877 buffer: &Entity<Buffer>,
22878 position: text::Anchor,
22879 cx: &mut App,
22880 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22881
22882 fn inline_values(
22883 &self,
22884 buffer_handle: Entity<Buffer>,
22885 range: Range<text::Anchor>,
22886 cx: &mut App,
22887 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22888
22889 fn inlay_hints(
22890 &self,
22891 buffer_handle: Entity<Buffer>,
22892 range: Range<text::Anchor>,
22893 cx: &mut App,
22894 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22895
22896 fn resolve_inlay_hint(
22897 &self,
22898 hint: InlayHint,
22899 buffer_handle: Entity<Buffer>,
22900 server_id: LanguageServerId,
22901 cx: &mut App,
22902 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22903
22904 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22905
22906 fn document_highlights(
22907 &self,
22908 buffer: &Entity<Buffer>,
22909 position: text::Anchor,
22910 cx: &mut App,
22911 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22912
22913 fn definitions(
22914 &self,
22915 buffer: &Entity<Buffer>,
22916 position: text::Anchor,
22917 kind: GotoDefinitionKind,
22918 cx: &mut App,
22919 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22920
22921 fn range_for_rename(
22922 &self,
22923 buffer: &Entity<Buffer>,
22924 position: text::Anchor,
22925 cx: &mut App,
22926 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22927
22928 fn perform_rename(
22929 &self,
22930 buffer: &Entity<Buffer>,
22931 position: text::Anchor,
22932 new_name: String,
22933 cx: &mut App,
22934 ) -> Option<Task<Result<ProjectTransaction>>>;
22935}
22936
22937pub trait CompletionProvider {
22938 fn completions(
22939 &self,
22940 excerpt_id: ExcerptId,
22941 buffer: &Entity<Buffer>,
22942 buffer_position: text::Anchor,
22943 trigger: CompletionContext,
22944 window: &mut Window,
22945 cx: &mut Context<Editor>,
22946 ) -> Task<Result<Vec<CompletionResponse>>>;
22947
22948 fn resolve_completions(
22949 &self,
22950 _buffer: Entity<Buffer>,
22951 _completion_indices: Vec<usize>,
22952 _completions: Rc<RefCell<Box<[Completion]>>>,
22953 _cx: &mut Context<Editor>,
22954 ) -> Task<Result<bool>> {
22955 Task::ready(Ok(false))
22956 }
22957
22958 fn apply_additional_edits_for_completion(
22959 &self,
22960 _buffer: Entity<Buffer>,
22961 _completions: Rc<RefCell<Box<[Completion]>>>,
22962 _completion_index: usize,
22963 _push_to_history: bool,
22964 _cx: &mut Context<Editor>,
22965 ) -> Task<Result<Option<language::Transaction>>> {
22966 Task::ready(Ok(None))
22967 }
22968
22969 fn is_completion_trigger(
22970 &self,
22971 buffer: &Entity<Buffer>,
22972 position: language::Anchor,
22973 text: &str,
22974 trigger_in_words: bool,
22975 menu_is_open: bool,
22976 cx: &mut Context<Editor>,
22977 ) -> bool;
22978
22979 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22980
22981 fn sort_completions(&self) -> bool {
22982 true
22983 }
22984
22985 fn filter_completions(&self) -> bool {
22986 true
22987 }
22988}
22989
22990pub trait CodeActionProvider {
22991 fn id(&self) -> Arc<str>;
22992
22993 fn code_actions(
22994 &self,
22995 buffer: &Entity<Buffer>,
22996 range: Range<text::Anchor>,
22997 window: &mut Window,
22998 cx: &mut App,
22999 ) -> Task<Result<Vec<CodeAction>>>;
23000
23001 fn apply_code_action(
23002 &self,
23003 buffer_handle: Entity<Buffer>,
23004 action: CodeAction,
23005 excerpt_id: ExcerptId,
23006 push_to_history: bool,
23007 window: &mut Window,
23008 cx: &mut App,
23009 ) -> Task<Result<ProjectTransaction>>;
23010}
23011
23012impl CodeActionProvider for Entity<Project> {
23013 fn id(&self) -> Arc<str> {
23014 "project".into()
23015 }
23016
23017 fn code_actions(
23018 &self,
23019 buffer: &Entity<Buffer>,
23020 range: Range<text::Anchor>,
23021 _window: &mut Window,
23022 cx: &mut App,
23023 ) -> Task<Result<Vec<CodeAction>>> {
23024 self.update(cx, |project, cx| {
23025 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
23026 let code_actions = project.code_actions(buffer, range, None, cx);
23027 cx.background_spawn(async move {
23028 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
23029 Ok(code_lens_actions
23030 .context("code lens fetch")?
23031 .into_iter()
23032 .flatten()
23033 .chain(
23034 code_actions
23035 .context("code action fetch")?
23036 .into_iter()
23037 .flatten(),
23038 )
23039 .collect())
23040 })
23041 })
23042 }
23043
23044 fn apply_code_action(
23045 &self,
23046 buffer_handle: Entity<Buffer>,
23047 action: CodeAction,
23048 _excerpt_id: ExcerptId,
23049 push_to_history: bool,
23050 _window: &mut Window,
23051 cx: &mut App,
23052 ) -> Task<Result<ProjectTransaction>> {
23053 self.update(cx, |project, cx| {
23054 project.apply_code_action(buffer_handle, action, push_to_history, cx)
23055 })
23056 }
23057}
23058
23059fn snippet_completions(
23060 project: &Project,
23061 buffer: &Entity<Buffer>,
23062 buffer_position: text::Anchor,
23063 cx: &mut App,
23064) -> Task<Result<CompletionResponse>> {
23065 let languages = buffer.read(cx).languages_at(buffer_position);
23066 let snippet_store = project.snippets().read(cx);
23067
23068 let scopes: Vec<_> = languages
23069 .iter()
23070 .filter_map(|language| {
23071 let language_name = language.lsp_id();
23072 let snippets = snippet_store.snippets_for(Some(language_name), cx);
23073
23074 if snippets.is_empty() {
23075 None
23076 } else {
23077 Some((language.default_scope(), snippets))
23078 }
23079 })
23080 .collect();
23081
23082 if scopes.is_empty() {
23083 return Task::ready(Ok(CompletionResponse {
23084 completions: vec![],
23085 display_options: CompletionDisplayOptions::default(),
23086 is_incomplete: false,
23087 }));
23088 }
23089
23090 let snapshot = buffer.read(cx).text_snapshot();
23091 let executor = cx.background_executor().clone();
23092
23093 cx.background_spawn(async move {
23094 let mut is_incomplete = false;
23095 let mut completions: Vec<Completion> = Vec::new();
23096 for (scope, snippets) in scopes.into_iter() {
23097 let classifier =
23098 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
23099
23100 const MAX_WORD_PREFIX_LEN: usize = 128;
23101 let last_word: String = snapshot
23102 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
23103 .take(MAX_WORD_PREFIX_LEN)
23104 .take_while(|c| classifier.is_word(*c))
23105 .collect::<String>()
23106 .chars()
23107 .rev()
23108 .collect();
23109
23110 if last_word.is_empty() {
23111 return Ok(CompletionResponse {
23112 completions: vec![],
23113 display_options: CompletionDisplayOptions::default(),
23114 is_incomplete: true,
23115 });
23116 }
23117
23118 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
23119 let to_lsp = |point: &text::Anchor| {
23120 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
23121 point_to_lsp(end)
23122 };
23123 let lsp_end = to_lsp(&buffer_position);
23124
23125 let candidates = snippets
23126 .iter()
23127 .enumerate()
23128 .flat_map(|(ix, snippet)| {
23129 snippet
23130 .prefix
23131 .iter()
23132 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
23133 })
23134 .collect::<Vec<StringMatchCandidate>>();
23135
23136 const MAX_RESULTS: usize = 100;
23137 let mut matches = fuzzy::match_strings(
23138 &candidates,
23139 &last_word,
23140 last_word.chars().any(|c| c.is_uppercase()),
23141 true,
23142 MAX_RESULTS,
23143 &Default::default(),
23144 executor.clone(),
23145 )
23146 .await;
23147
23148 if matches.len() >= MAX_RESULTS {
23149 is_incomplete = true;
23150 }
23151
23152 // Remove all candidates where the query's start does not match the start of any word in the candidate
23153 if let Some(query_start) = last_word.chars().next() {
23154 matches.retain(|string_match| {
23155 split_words(&string_match.string).any(|word| {
23156 // Check that the first codepoint of the word as lowercase matches the first
23157 // codepoint of the query as lowercase
23158 word.chars()
23159 .flat_map(|codepoint| codepoint.to_lowercase())
23160 .zip(query_start.to_lowercase())
23161 .all(|(word_cp, query_cp)| word_cp == query_cp)
23162 })
23163 });
23164 }
23165
23166 let matched_strings = matches
23167 .into_iter()
23168 .map(|m| m.string)
23169 .collect::<HashSet<_>>();
23170
23171 completions.extend(snippets.iter().filter_map(|snippet| {
23172 let matching_prefix = snippet
23173 .prefix
23174 .iter()
23175 .find(|prefix| matched_strings.contains(*prefix))?;
23176 let start = as_offset - last_word.len();
23177 let start = snapshot.anchor_before(start);
23178 let range = start..buffer_position;
23179 let lsp_start = to_lsp(&start);
23180 let lsp_range = lsp::Range {
23181 start: lsp_start,
23182 end: lsp_end,
23183 };
23184 Some(Completion {
23185 replace_range: range,
23186 new_text: snippet.body.clone(),
23187 source: CompletionSource::Lsp {
23188 insert_range: None,
23189 server_id: LanguageServerId(usize::MAX),
23190 resolved: true,
23191 lsp_completion: Box::new(lsp::CompletionItem {
23192 label: snippet.prefix.first().unwrap().clone(),
23193 kind: Some(CompletionItemKind::SNIPPET),
23194 label_details: snippet.description.as_ref().map(|description| {
23195 lsp::CompletionItemLabelDetails {
23196 detail: Some(description.clone()),
23197 description: None,
23198 }
23199 }),
23200 insert_text_format: Some(InsertTextFormat::SNIPPET),
23201 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23202 lsp::InsertReplaceEdit {
23203 new_text: snippet.body.clone(),
23204 insert: lsp_range,
23205 replace: lsp_range,
23206 },
23207 )),
23208 filter_text: Some(snippet.body.clone()),
23209 sort_text: Some(char::MAX.to_string()),
23210 ..lsp::CompletionItem::default()
23211 }),
23212 lsp_defaults: None,
23213 },
23214 label: CodeLabel::plain(matching_prefix.clone(), None),
23215 icon_path: None,
23216 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23217 single_line: snippet.name.clone().into(),
23218 plain_text: snippet
23219 .description
23220 .clone()
23221 .map(|description| description.into()),
23222 }),
23223 insert_text_mode: None,
23224 confirm: None,
23225 })
23226 }))
23227 }
23228
23229 Ok(CompletionResponse {
23230 completions,
23231 display_options: CompletionDisplayOptions::default(),
23232 is_incomplete,
23233 })
23234 })
23235}
23236
23237impl CompletionProvider for Entity<Project> {
23238 fn completions(
23239 &self,
23240 _excerpt_id: ExcerptId,
23241 buffer: &Entity<Buffer>,
23242 buffer_position: text::Anchor,
23243 options: CompletionContext,
23244 _window: &mut Window,
23245 cx: &mut Context<Editor>,
23246 ) -> Task<Result<Vec<CompletionResponse>>> {
23247 self.update(cx, |project, cx| {
23248 let snippets = snippet_completions(project, buffer, buffer_position, cx);
23249 let project_completions = project.completions(buffer, buffer_position, options, cx);
23250 cx.background_spawn(async move {
23251 let mut responses = project_completions.await?;
23252 let snippets = snippets.await?;
23253 if !snippets.completions.is_empty() {
23254 responses.push(snippets);
23255 }
23256 Ok(responses)
23257 })
23258 })
23259 }
23260
23261 fn resolve_completions(
23262 &self,
23263 buffer: Entity<Buffer>,
23264 completion_indices: Vec<usize>,
23265 completions: Rc<RefCell<Box<[Completion]>>>,
23266 cx: &mut Context<Editor>,
23267 ) -> Task<Result<bool>> {
23268 self.update(cx, |project, cx| {
23269 project.lsp_store().update(cx, |lsp_store, cx| {
23270 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23271 })
23272 })
23273 }
23274
23275 fn apply_additional_edits_for_completion(
23276 &self,
23277 buffer: Entity<Buffer>,
23278 completions: Rc<RefCell<Box<[Completion]>>>,
23279 completion_index: usize,
23280 push_to_history: bool,
23281 cx: &mut Context<Editor>,
23282 ) -> Task<Result<Option<language::Transaction>>> {
23283 self.update(cx, |project, cx| {
23284 project.lsp_store().update(cx, |lsp_store, cx| {
23285 lsp_store.apply_additional_edits_for_completion(
23286 buffer,
23287 completions,
23288 completion_index,
23289 push_to_history,
23290 cx,
23291 )
23292 })
23293 })
23294 }
23295
23296 fn is_completion_trigger(
23297 &self,
23298 buffer: &Entity<Buffer>,
23299 position: language::Anchor,
23300 text: &str,
23301 trigger_in_words: bool,
23302 menu_is_open: bool,
23303 cx: &mut Context<Editor>,
23304 ) -> bool {
23305 let mut chars = text.chars();
23306 let char = if let Some(char) = chars.next() {
23307 char
23308 } else {
23309 return false;
23310 };
23311 if chars.next().is_some() {
23312 return false;
23313 }
23314
23315 let buffer = buffer.read(cx);
23316 let snapshot = buffer.snapshot();
23317 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23318 return false;
23319 }
23320 let classifier = snapshot
23321 .char_classifier_at(position)
23322 .scope_context(Some(CharScopeContext::Completion));
23323 if trigger_in_words && classifier.is_word(char) {
23324 return true;
23325 }
23326
23327 buffer.completion_triggers().contains(text)
23328 }
23329}
23330
23331impl SemanticsProvider for Entity<Project> {
23332 fn hover(
23333 &self,
23334 buffer: &Entity<Buffer>,
23335 position: text::Anchor,
23336 cx: &mut App,
23337 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23338 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23339 }
23340
23341 fn document_highlights(
23342 &self,
23343 buffer: &Entity<Buffer>,
23344 position: text::Anchor,
23345 cx: &mut App,
23346 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23347 Some(self.update(cx, |project, cx| {
23348 project.document_highlights(buffer, position, cx)
23349 }))
23350 }
23351
23352 fn definitions(
23353 &self,
23354 buffer: &Entity<Buffer>,
23355 position: text::Anchor,
23356 kind: GotoDefinitionKind,
23357 cx: &mut App,
23358 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23359 Some(self.update(cx, |project, cx| match kind {
23360 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23361 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23362 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23363 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23364 }))
23365 }
23366
23367 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23368 self.update(cx, |project, cx| {
23369 if project
23370 .active_debug_session(cx)
23371 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23372 {
23373 return true;
23374 }
23375
23376 buffer.update(cx, |buffer, cx| {
23377 project.any_language_server_supports_inlay_hints(buffer, cx)
23378 })
23379 })
23380 }
23381
23382 fn inline_values(
23383 &self,
23384 buffer_handle: Entity<Buffer>,
23385 range: Range<text::Anchor>,
23386 cx: &mut App,
23387 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23388 self.update(cx, |project, cx| {
23389 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23390
23391 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23392 })
23393 }
23394
23395 fn inlay_hints(
23396 &self,
23397 buffer_handle: Entity<Buffer>,
23398 range: Range<text::Anchor>,
23399 cx: &mut App,
23400 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23401 Some(self.update(cx, |project, cx| {
23402 project.inlay_hints(buffer_handle, range, cx)
23403 }))
23404 }
23405
23406 fn resolve_inlay_hint(
23407 &self,
23408 hint: InlayHint,
23409 buffer_handle: Entity<Buffer>,
23410 server_id: LanguageServerId,
23411 cx: &mut App,
23412 ) -> Option<Task<anyhow::Result<InlayHint>>> {
23413 Some(self.update(cx, |project, cx| {
23414 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
23415 }))
23416 }
23417
23418 fn range_for_rename(
23419 &self,
23420 buffer: &Entity<Buffer>,
23421 position: text::Anchor,
23422 cx: &mut App,
23423 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23424 Some(self.update(cx, |project, cx| {
23425 let buffer = buffer.clone();
23426 let task = project.prepare_rename(buffer.clone(), position, cx);
23427 cx.spawn(async move |_, cx| {
23428 Ok(match task.await? {
23429 PrepareRenameResponse::Success(range) => Some(range),
23430 PrepareRenameResponse::InvalidPosition => None,
23431 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23432 // Fallback on using TreeSitter info to determine identifier range
23433 buffer.read_with(cx, |buffer, _| {
23434 let snapshot = buffer.snapshot();
23435 let (range, kind) = snapshot.surrounding_word(position, None);
23436 if kind != Some(CharKind::Word) {
23437 return None;
23438 }
23439 Some(
23440 snapshot.anchor_before(range.start)
23441 ..snapshot.anchor_after(range.end),
23442 )
23443 })?
23444 }
23445 })
23446 })
23447 }))
23448 }
23449
23450 fn perform_rename(
23451 &self,
23452 buffer: &Entity<Buffer>,
23453 position: text::Anchor,
23454 new_name: String,
23455 cx: &mut App,
23456 ) -> Option<Task<Result<ProjectTransaction>>> {
23457 Some(self.update(cx, |project, cx| {
23458 project.perform_rename(buffer.clone(), position, new_name, cx)
23459 }))
23460 }
23461}
23462
23463fn inlay_hint_settings(
23464 location: Anchor,
23465 snapshot: &MultiBufferSnapshot,
23466 cx: &mut Context<Editor>,
23467) -> InlayHintSettings {
23468 let file = snapshot.file_at(location);
23469 let language = snapshot.language_at(location).map(|l| l.name());
23470 language_settings(language, file, cx).inlay_hints
23471}
23472
23473fn consume_contiguous_rows(
23474 contiguous_row_selections: &mut Vec<Selection<Point>>,
23475 selection: &Selection<Point>,
23476 display_map: &DisplaySnapshot,
23477 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23478) -> (MultiBufferRow, MultiBufferRow) {
23479 contiguous_row_selections.push(selection.clone());
23480 let start_row = starting_row(selection, display_map);
23481 let mut end_row = ending_row(selection, display_map);
23482
23483 while let Some(next_selection) = selections.peek() {
23484 if next_selection.start.row <= end_row.0 {
23485 end_row = ending_row(next_selection, display_map);
23486 contiguous_row_selections.push(selections.next().unwrap().clone());
23487 } else {
23488 break;
23489 }
23490 }
23491 (start_row, end_row)
23492}
23493
23494fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23495 if selection.start.column > 0 {
23496 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23497 } else {
23498 MultiBufferRow(selection.start.row)
23499 }
23500}
23501
23502fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23503 if next_selection.end.column > 0 || next_selection.is_empty() {
23504 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23505 } else {
23506 MultiBufferRow(next_selection.end.row)
23507 }
23508}
23509
23510impl EditorSnapshot {
23511 pub fn remote_selections_in_range<'a>(
23512 &'a self,
23513 range: &'a Range<Anchor>,
23514 collaboration_hub: &dyn CollaborationHub,
23515 cx: &'a App,
23516 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23517 let participant_names = collaboration_hub.user_names(cx);
23518 let participant_indices = collaboration_hub.user_participant_indices(cx);
23519 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23520 let collaborators_by_replica_id = collaborators_by_peer_id
23521 .values()
23522 .map(|collaborator| (collaborator.replica_id, collaborator))
23523 .collect::<HashMap<_, _>>();
23524 self.buffer_snapshot()
23525 .selections_in_range(range, false)
23526 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23527 if replica_id == ReplicaId::AGENT {
23528 Some(RemoteSelection {
23529 replica_id,
23530 selection,
23531 cursor_shape,
23532 line_mode,
23533 collaborator_id: CollaboratorId::Agent,
23534 user_name: Some("Agent".into()),
23535 color: cx.theme().players().agent(),
23536 })
23537 } else {
23538 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23539 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23540 let user_name = participant_names.get(&collaborator.user_id).cloned();
23541 Some(RemoteSelection {
23542 replica_id,
23543 selection,
23544 cursor_shape,
23545 line_mode,
23546 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23547 user_name,
23548 color: if let Some(index) = participant_index {
23549 cx.theme().players().color_for_participant(index.0)
23550 } else {
23551 cx.theme().players().absent()
23552 },
23553 })
23554 }
23555 })
23556 }
23557
23558 pub fn hunks_for_ranges(
23559 &self,
23560 ranges: impl IntoIterator<Item = Range<Point>>,
23561 ) -> Vec<MultiBufferDiffHunk> {
23562 let mut hunks = Vec::new();
23563 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23564 HashMap::default();
23565 for query_range in ranges {
23566 let query_rows =
23567 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23568 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23569 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23570 ) {
23571 // Include deleted hunks that are adjacent to the query range, because
23572 // otherwise they would be missed.
23573 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23574 if hunk.status().is_deleted() {
23575 intersects_range |= hunk.row_range.start == query_rows.end;
23576 intersects_range |= hunk.row_range.end == query_rows.start;
23577 }
23578 if intersects_range {
23579 if !processed_buffer_rows
23580 .entry(hunk.buffer_id)
23581 .or_default()
23582 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23583 {
23584 continue;
23585 }
23586 hunks.push(hunk);
23587 }
23588 }
23589 }
23590
23591 hunks
23592 }
23593
23594 fn display_diff_hunks_for_rows<'a>(
23595 &'a self,
23596 display_rows: Range<DisplayRow>,
23597 folded_buffers: &'a HashSet<BufferId>,
23598 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23599 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23600 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23601
23602 self.buffer_snapshot()
23603 .diff_hunks_in_range(buffer_start..buffer_end)
23604 .filter_map(|hunk| {
23605 if folded_buffers.contains(&hunk.buffer_id) {
23606 return None;
23607 }
23608
23609 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23610 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23611
23612 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23613 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23614
23615 let display_hunk = if hunk_display_start.column() != 0 {
23616 DisplayDiffHunk::Folded {
23617 display_row: hunk_display_start.row(),
23618 }
23619 } else {
23620 let mut end_row = hunk_display_end.row();
23621 if hunk_display_end.column() > 0 {
23622 end_row.0 += 1;
23623 }
23624 let is_created_file = hunk.is_created_file();
23625 DisplayDiffHunk::Unfolded {
23626 status: hunk.status(),
23627 diff_base_byte_range: hunk.diff_base_byte_range,
23628 display_row_range: hunk_display_start.row()..end_row,
23629 multi_buffer_range: Anchor::range_in_buffer(
23630 hunk.excerpt_id,
23631 hunk.buffer_id,
23632 hunk.buffer_range,
23633 ),
23634 is_created_file,
23635 }
23636 };
23637
23638 Some(display_hunk)
23639 })
23640 }
23641
23642 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23643 self.display_snapshot
23644 .buffer_snapshot()
23645 .language_at(position)
23646 }
23647
23648 pub fn is_focused(&self) -> bool {
23649 self.is_focused
23650 }
23651
23652 pub fn placeholder_text(&self) -> Option<String> {
23653 self.placeholder_display_snapshot
23654 .as_ref()
23655 .map(|display_map| display_map.text())
23656 }
23657
23658 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23659 self.scroll_anchor.scroll_position(&self.display_snapshot)
23660 }
23661
23662 fn gutter_dimensions(
23663 &self,
23664 font_id: FontId,
23665 font_size: Pixels,
23666 max_line_number_width: Pixels,
23667 cx: &App,
23668 ) -> Option<GutterDimensions> {
23669 if !self.show_gutter {
23670 return None;
23671 }
23672
23673 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23674 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23675
23676 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23677 matches!(
23678 ProjectSettings::get_global(cx).git.git_gutter,
23679 GitGutterSetting::TrackedFiles
23680 )
23681 });
23682 let gutter_settings = EditorSettings::get_global(cx).gutter;
23683 let show_line_numbers = self
23684 .show_line_numbers
23685 .unwrap_or(gutter_settings.line_numbers);
23686 let line_gutter_width = if show_line_numbers {
23687 // Avoid flicker-like gutter resizes when the line number gains another digit by
23688 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23689 let min_width_for_number_on_gutter =
23690 ch_advance * gutter_settings.min_line_number_digits as f32;
23691 max_line_number_width.max(min_width_for_number_on_gutter)
23692 } else {
23693 0.0.into()
23694 };
23695
23696 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23697 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23698
23699 let git_blame_entries_width =
23700 self.git_blame_gutter_max_author_length
23701 .map(|max_author_length| {
23702 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23703 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23704
23705 /// The number of characters to dedicate to gaps and margins.
23706 const SPACING_WIDTH: usize = 4;
23707
23708 let max_char_count = max_author_length.min(renderer.max_author_length())
23709 + ::git::SHORT_SHA_LENGTH
23710 + MAX_RELATIVE_TIMESTAMP.len()
23711 + SPACING_WIDTH;
23712
23713 ch_advance * max_char_count
23714 });
23715
23716 let is_singleton = self.buffer_snapshot().is_singleton();
23717
23718 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23719 left_padding += if !is_singleton {
23720 ch_width * 4.0
23721 } else if show_runnables || show_breakpoints {
23722 ch_width * 3.0
23723 } else if show_git_gutter && show_line_numbers {
23724 ch_width * 2.0
23725 } else if show_git_gutter || show_line_numbers {
23726 ch_width
23727 } else {
23728 px(0.)
23729 };
23730
23731 let shows_folds = is_singleton && gutter_settings.folds;
23732
23733 let right_padding = if shows_folds && show_line_numbers {
23734 ch_width * 4.0
23735 } else if shows_folds || (!is_singleton && show_line_numbers) {
23736 ch_width * 3.0
23737 } else if show_line_numbers {
23738 ch_width
23739 } else {
23740 px(0.)
23741 };
23742
23743 Some(GutterDimensions {
23744 left_padding,
23745 right_padding,
23746 width: line_gutter_width + left_padding + right_padding,
23747 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23748 git_blame_entries_width,
23749 })
23750 }
23751
23752 pub fn render_crease_toggle(
23753 &self,
23754 buffer_row: MultiBufferRow,
23755 row_contains_cursor: bool,
23756 editor: Entity<Editor>,
23757 window: &mut Window,
23758 cx: &mut App,
23759 ) -> Option<AnyElement> {
23760 let folded = self.is_line_folded(buffer_row);
23761 let mut is_foldable = false;
23762
23763 if let Some(crease) = self
23764 .crease_snapshot
23765 .query_row(buffer_row, self.buffer_snapshot())
23766 {
23767 is_foldable = true;
23768 match crease {
23769 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23770 if let Some(render_toggle) = render_toggle {
23771 let toggle_callback =
23772 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23773 if folded {
23774 editor.update(cx, |editor, cx| {
23775 editor.fold_at(buffer_row, window, cx)
23776 });
23777 } else {
23778 editor.update(cx, |editor, cx| {
23779 editor.unfold_at(buffer_row, window, cx)
23780 });
23781 }
23782 });
23783 return Some((render_toggle)(
23784 buffer_row,
23785 folded,
23786 toggle_callback,
23787 window,
23788 cx,
23789 ));
23790 }
23791 }
23792 }
23793 }
23794
23795 is_foldable |= self.starts_indent(buffer_row);
23796
23797 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23798 Some(
23799 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23800 .toggle_state(folded)
23801 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23802 if folded {
23803 this.unfold_at(buffer_row, window, cx);
23804 } else {
23805 this.fold_at(buffer_row, window, cx);
23806 }
23807 }))
23808 .into_any_element(),
23809 )
23810 } else {
23811 None
23812 }
23813 }
23814
23815 pub fn render_crease_trailer(
23816 &self,
23817 buffer_row: MultiBufferRow,
23818 window: &mut Window,
23819 cx: &mut App,
23820 ) -> Option<AnyElement> {
23821 let folded = self.is_line_folded(buffer_row);
23822 if let Crease::Inline { render_trailer, .. } = self
23823 .crease_snapshot
23824 .query_row(buffer_row, self.buffer_snapshot())?
23825 {
23826 let render_trailer = render_trailer.as_ref()?;
23827 Some(render_trailer(buffer_row, folded, window, cx))
23828 } else {
23829 None
23830 }
23831 }
23832}
23833
23834impl Deref for EditorSnapshot {
23835 type Target = DisplaySnapshot;
23836
23837 fn deref(&self) -> &Self::Target {
23838 &self.display_snapshot
23839 }
23840}
23841
23842#[derive(Clone, Debug, PartialEq, Eq)]
23843pub enum EditorEvent {
23844 InputIgnored {
23845 text: Arc<str>,
23846 },
23847 InputHandled {
23848 utf16_range_to_replace: Option<Range<isize>>,
23849 text: Arc<str>,
23850 },
23851 ExcerptsAdded {
23852 buffer: Entity<Buffer>,
23853 predecessor: ExcerptId,
23854 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23855 },
23856 ExcerptsRemoved {
23857 ids: Vec<ExcerptId>,
23858 removed_buffer_ids: Vec<BufferId>,
23859 },
23860 BufferFoldToggled {
23861 ids: Vec<ExcerptId>,
23862 folded: bool,
23863 },
23864 ExcerptsEdited {
23865 ids: Vec<ExcerptId>,
23866 },
23867 ExcerptsExpanded {
23868 ids: Vec<ExcerptId>,
23869 },
23870 BufferEdited,
23871 Edited {
23872 transaction_id: clock::Lamport,
23873 },
23874 Reparsed(BufferId),
23875 Focused,
23876 FocusedIn,
23877 Blurred,
23878 DirtyChanged,
23879 Saved,
23880 TitleChanged,
23881 SelectionsChanged {
23882 local: bool,
23883 },
23884 ScrollPositionChanged {
23885 local: bool,
23886 autoscroll: bool,
23887 },
23888 TransactionUndone {
23889 transaction_id: clock::Lamport,
23890 },
23891 TransactionBegun {
23892 transaction_id: clock::Lamport,
23893 },
23894 CursorShapeChanged,
23895 BreadcrumbsChanged,
23896 PushedToNavHistory {
23897 anchor: Anchor,
23898 is_deactivate: bool,
23899 },
23900}
23901
23902impl EventEmitter<EditorEvent> for Editor {}
23903
23904impl Focusable for Editor {
23905 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23906 self.focus_handle.clone()
23907 }
23908}
23909
23910impl Render for Editor {
23911 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23912 let settings = ThemeSettings::get_global(cx);
23913
23914 let mut text_style = match self.mode {
23915 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23916 color: cx.theme().colors().editor_foreground,
23917 font_family: settings.ui_font.family.clone(),
23918 font_features: settings.ui_font.features.clone(),
23919 font_fallbacks: settings.ui_font.fallbacks.clone(),
23920 font_size: rems(0.875).into(),
23921 font_weight: settings.ui_font.weight,
23922 line_height: relative(settings.buffer_line_height.value()),
23923 ..Default::default()
23924 },
23925 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23926 color: cx.theme().colors().editor_foreground,
23927 font_family: settings.buffer_font.family.clone(),
23928 font_features: settings.buffer_font.features.clone(),
23929 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23930 font_size: settings.buffer_font_size(cx).into(),
23931 font_weight: settings.buffer_font.weight,
23932 line_height: relative(settings.buffer_line_height.value()),
23933 ..Default::default()
23934 },
23935 };
23936 if let Some(text_style_refinement) = &self.text_style_refinement {
23937 text_style.refine(text_style_refinement)
23938 }
23939
23940 let background = match self.mode {
23941 EditorMode::SingleLine => cx.theme().system().transparent,
23942 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23943 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23944 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23945 };
23946
23947 EditorElement::new(
23948 &cx.entity(),
23949 EditorStyle {
23950 background,
23951 border: cx.theme().colors().border,
23952 local_player: cx.theme().players().local(),
23953 text: text_style,
23954 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23955 syntax: cx.theme().syntax().clone(),
23956 status: cx.theme().status().clone(),
23957 inlay_hints_style: make_inlay_hints_style(cx),
23958 edit_prediction_styles: make_suggestion_styles(cx),
23959 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23960 show_underlines: self.diagnostics_enabled(),
23961 },
23962 )
23963 }
23964}
23965
23966impl EntityInputHandler for Editor {
23967 fn text_for_range(
23968 &mut self,
23969 range_utf16: Range<usize>,
23970 adjusted_range: &mut Option<Range<usize>>,
23971 _: &mut Window,
23972 cx: &mut Context<Self>,
23973 ) -> Option<String> {
23974 let snapshot = self.buffer.read(cx).read(cx);
23975 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23976 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23977 if (start.0..end.0) != range_utf16 {
23978 adjusted_range.replace(start.0..end.0);
23979 }
23980 Some(snapshot.text_for_range(start..end).collect())
23981 }
23982
23983 fn selected_text_range(
23984 &mut self,
23985 ignore_disabled_input: bool,
23986 _: &mut Window,
23987 cx: &mut Context<Self>,
23988 ) -> Option<UTF16Selection> {
23989 // Prevent the IME menu from appearing when holding down an alphabetic key
23990 // while input is disabled.
23991 if !ignore_disabled_input && !self.input_enabled {
23992 return None;
23993 }
23994
23995 let selection = self
23996 .selections
23997 .newest::<OffsetUtf16>(&self.display_snapshot(cx));
23998 let range = selection.range();
23999
24000 Some(UTF16Selection {
24001 range: range.start.0..range.end.0,
24002 reversed: selection.reversed,
24003 })
24004 }
24005
24006 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
24007 let snapshot = self.buffer.read(cx).read(cx);
24008 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
24009 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
24010 }
24011
24012 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24013 self.clear_highlights::<InputComposition>(cx);
24014 self.ime_transaction.take();
24015 }
24016
24017 fn replace_text_in_range(
24018 &mut self,
24019 range_utf16: Option<Range<usize>>,
24020 text: &str,
24021 window: &mut Window,
24022 cx: &mut Context<Self>,
24023 ) {
24024 if !self.input_enabled {
24025 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24026 return;
24027 }
24028
24029 self.transact(window, cx, |this, window, cx| {
24030 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
24031 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24032 Some(this.selection_replacement_ranges(range_utf16, cx))
24033 } else {
24034 this.marked_text_ranges(cx)
24035 };
24036
24037 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
24038 let newest_selection_id = this.selections.newest_anchor().id;
24039 this.selections
24040 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24041 .iter()
24042 .zip(ranges_to_replace.iter())
24043 .find_map(|(selection, range)| {
24044 if selection.id == newest_selection_id {
24045 Some(
24046 (range.start.0 as isize - selection.head().0 as isize)
24047 ..(range.end.0 as isize - selection.head().0 as isize),
24048 )
24049 } else {
24050 None
24051 }
24052 })
24053 });
24054
24055 cx.emit(EditorEvent::InputHandled {
24056 utf16_range_to_replace: range_to_replace,
24057 text: text.into(),
24058 });
24059
24060 if let Some(new_selected_ranges) = new_selected_ranges {
24061 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24062 selections.select_ranges(new_selected_ranges)
24063 });
24064 this.backspace(&Default::default(), window, cx);
24065 }
24066
24067 this.handle_input(text, window, cx);
24068 });
24069
24070 if let Some(transaction) = self.ime_transaction {
24071 self.buffer.update(cx, |buffer, cx| {
24072 buffer.group_until_transaction(transaction, cx);
24073 });
24074 }
24075
24076 self.unmark_text(window, cx);
24077 }
24078
24079 fn replace_and_mark_text_in_range(
24080 &mut self,
24081 range_utf16: Option<Range<usize>>,
24082 text: &str,
24083 new_selected_range_utf16: Option<Range<usize>>,
24084 window: &mut Window,
24085 cx: &mut Context<Self>,
24086 ) {
24087 if !self.input_enabled {
24088 return;
24089 }
24090
24091 let transaction = self.transact(window, cx, |this, window, cx| {
24092 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
24093 let snapshot = this.buffer.read(cx).read(cx);
24094 if let Some(relative_range_utf16) = range_utf16.as_ref() {
24095 for marked_range in &mut marked_ranges {
24096 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
24097 marked_range.start.0 += relative_range_utf16.start;
24098 marked_range.start =
24099 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
24100 marked_range.end =
24101 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
24102 }
24103 }
24104 Some(marked_ranges)
24105 } else if let Some(range_utf16) = range_utf16 {
24106 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24107 Some(this.selection_replacement_ranges(range_utf16, cx))
24108 } else {
24109 None
24110 };
24111
24112 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
24113 let newest_selection_id = this.selections.newest_anchor().id;
24114 this.selections
24115 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24116 .iter()
24117 .zip(ranges_to_replace.iter())
24118 .find_map(|(selection, range)| {
24119 if selection.id == newest_selection_id {
24120 Some(
24121 (range.start.0 as isize - selection.head().0 as isize)
24122 ..(range.end.0 as isize - selection.head().0 as isize),
24123 )
24124 } else {
24125 None
24126 }
24127 })
24128 });
24129
24130 cx.emit(EditorEvent::InputHandled {
24131 utf16_range_to_replace: range_to_replace,
24132 text: text.into(),
24133 });
24134
24135 if let Some(ranges) = ranges_to_replace {
24136 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24137 s.select_ranges(ranges)
24138 });
24139 }
24140
24141 let marked_ranges = {
24142 let snapshot = this.buffer.read(cx).read(cx);
24143 this.selections
24144 .disjoint_anchors_arc()
24145 .iter()
24146 .map(|selection| {
24147 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24148 })
24149 .collect::<Vec<_>>()
24150 };
24151
24152 if text.is_empty() {
24153 this.unmark_text(window, cx);
24154 } else {
24155 this.highlight_text::<InputComposition>(
24156 marked_ranges.clone(),
24157 HighlightStyle {
24158 underline: Some(UnderlineStyle {
24159 thickness: px(1.),
24160 color: None,
24161 wavy: false,
24162 }),
24163 ..Default::default()
24164 },
24165 cx,
24166 );
24167 }
24168
24169 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24170 let use_autoclose = this.use_autoclose;
24171 let use_auto_surround = this.use_auto_surround;
24172 this.set_use_autoclose(false);
24173 this.set_use_auto_surround(false);
24174 this.handle_input(text, window, cx);
24175 this.set_use_autoclose(use_autoclose);
24176 this.set_use_auto_surround(use_auto_surround);
24177
24178 if let Some(new_selected_range) = new_selected_range_utf16 {
24179 let snapshot = this.buffer.read(cx).read(cx);
24180 let new_selected_ranges = marked_ranges
24181 .into_iter()
24182 .map(|marked_range| {
24183 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24184 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
24185 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
24186 snapshot.clip_offset_utf16(new_start, Bias::Left)
24187 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24188 })
24189 .collect::<Vec<_>>();
24190
24191 drop(snapshot);
24192 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24193 selections.select_ranges(new_selected_ranges)
24194 });
24195 }
24196 });
24197
24198 self.ime_transaction = self.ime_transaction.or(transaction);
24199 if let Some(transaction) = self.ime_transaction {
24200 self.buffer.update(cx, |buffer, cx| {
24201 buffer.group_until_transaction(transaction, cx);
24202 });
24203 }
24204
24205 if self.text_highlights::<InputComposition>(cx).is_none() {
24206 self.ime_transaction.take();
24207 }
24208 }
24209
24210 fn bounds_for_range(
24211 &mut self,
24212 range_utf16: Range<usize>,
24213 element_bounds: gpui::Bounds<Pixels>,
24214 window: &mut Window,
24215 cx: &mut Context<Self>,
24216 ) -> Option<gpui::Bounds<Pixels>> {
24217 let text_layout_details = self.text_layout_details(window);
24218 let CharacterDimensions {
24219 em_width,
24220 em_advance,
24221 line_height,
24222 } = self.character_dimensions(window);
24223
24224 let snapshot = self.snapshot(window, cx);
24225 let scroll_position = snapshot.scroll_position();
24226 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24227
24228 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
24229 let x = Pixels::from(
24230 ScrollOffset::from(
24231 snapshot.x_for_display_point(start, &text_layout_details)
24232 + self.gutter_dimensions.full_width(),
24233 ) - scroll_left,
24234 );
24235 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24236
24237 Some(Bounds {
24238 origin: element_bounds.origin + point(x, y),
24239 size: size(em_width, line_height),
24240 })
24241 }
24242
24243 fn character_index_for_point(
24244 &mut self,
24245 point: gpui::Point<Pixels>,
24246 _window: &mut Window,
24247 _cx: &mut Context<Self>,
24248 ) -> Option<usize> {
24249 let position_map = self.last_position_map.as_ref()?;
24250 if !position_map.text_hitbox.contains(&point) {
24251 return None;
24252 }
24253 let display_point = position_map.point_for_position(point).previous_valid;
24254 let anchor = position_map
24255 .snapshot
24256 .display_point_to_anchor(display_point, Bias::Left);
24257 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24258 Some(utf16_offset.0)
24259 }
24260}
24261
24262trait SelectionExt {
24263 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24264 fn spanned_rows(
24265 &self,
24266 include_end_if_at_line_start: bool,
24267 map: &DisplaySnapshot,
24268 ) -> Range<MultiBufferRow>;
24269}
24270
24271impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24272 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24273 let start = self
24274 .start
24275 .to_point(map.buffer_snapshot())
24276 .to_display_point(map);
24277 let end = self
24278 .end
24279 .to_point(map.buffer_snapshot())
24280 .to_display_point(map);
24281 if self.reversed {
24282 end..start
24283 } else {
24284 start..end
24285 }
24286 }
24287
24288 fn spanned_rows(
24289 &self,
24290 include_end_if_at_line_start: bool,
24291 map: &DisplaySnapshot,
24292 ) -> Range<MultiBufferRow> {
24293 let start = self.start.to_point(map.buffer_snapshot());
24294 let mut end = self.end.to_point(map.buffer_snapshot());
24295 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24296 end.row -= 1;
24297 }
24298
24299 let buffer_start = map.prev_line_boundary(start).0;
24300 let buffer_end = map.next_line_boundary(end).0;
24301 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24302 }
24303}
24304
24305impl<T: InvalidationRegion> InvalidationStack<T> {
24306 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24307 where
24308 S: Clone + ToOffset,
24309 {
24310 while let Some(region) = self.last() {
24311 let all_selections_inside_invalidation_ranges =
24312 if selections.len() == region.ranges().len() {
24313 selections
24314 .iter()
24315 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24316 .all(|(selection, invalidation_range)| {
24317 let head = selection.head().to_offset(buffer);
24318 invalidation_range.start <= head && invalidation_range.end >= head
24319 })
24320 } else {
24321 false
24322 };
24323
24324 if all_selections_inside_invalidation_ranges {
24325 break;
24326 } else {
24327 self.pop();
24328 }
24329 }
24330 }
24331}
24332
24333impl<T> Default for InvalidationStack<T> {
24334 fn default() -> Self {
24335 Self(Default::default())
24336 }
24337}
24338
24339impl<T> Deref for InvalidationStack<T> {
24340 type Target = Vec<T>;
24341
24342 fn deref(&self) -> &Self::Target {
24343 &self.0
24344 }
24345}
24346
24347impl<T> DerefMut for InvalidationStack<T> {
24348 fn deref_mut(&mut self) -> &mut Self::Target {
24349 &mut self.0
24350 }
24351}
24352
24353impl InvalidationRegion for SnippetState {
24354 fn ranges(&self) -> &[Range<Anchor>] {
24355 &self.ranges[self.active_index]
24356 }
24357}
24358
24359fn edit_prediction_edit_text(
24360 current_snapshot: &BufferSnapshot,
24361 edits: &[(Range<Anchor>, String)],
24362 edit_preview: &EditPreview,
24363 include_deletions: bool,
24364 cx: &App,
24365) -> HighlightedText {
24366 let edits = edits
24367 .iter()
24368 .map(|(anchor, text)| {
24369 (
24370 anchor.start.text_anchor..anchor.end.text_anchor,
24371 text.clone(),
24372 )
24373 })
24374 .collect::<Vec<_>>();
24375
24376 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24377}
24378
24379fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
24380 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24381 // Just show the raw edit text with basic styling
24382 let mut text = String::new();
24383 let mut highlights = Vec::new();
24384
24385 let insertion_highlight_style = HighlightStyle {
24386 color: Some(cx.theme().colors().text),
24387 ..Default::default()
24388 };
24389
24390 for (_, edit_text) in edits {
24391 let start_offset = text.len();
24392 text.push_str(edit_text);
24393 let end_offset = text.len();
24394
24395 if start_offset < end_offset {
24396 highlights.push((start_offset..end_offset, insertion_highlight_style));
24397 }
24398 }
24399
24400 HighlightedText {
24401 text: text.into(),
24402 highlights,
24403 }
24404}
24405
24406pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24407 match severity {
24408 lsp::DiagnosticSeverity::ERROR => colors.error,
24409 lsp::DiagnosticSeverity::WARNING => colors.warning,
24410 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24411 lsp::DiagnosticSeverity::HINT => colors.info,
24412 _ => colors.ignored,
24413 }
24414}
24415
24416pub fn styled_runs_for_code_label<'a>(
24417 label: &'a CodeLabel,
24418 syntax_theme: &'a theme::SyntaxTheme,
24419) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24420 let fade_out = HighlightStyle {
24421 fade_out: Some(0.35),
24422 ..Default::default()
24423 };
24424
24425 let mut prev_end = label.filter_range.end;
24426 label
24427 .runs
24428 .iter()
24429 .enumerate()
24430 .flat_map(move |(ix, (range, highlight_id))| {
24431 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24432 style
24433 } else {
24434 return Default::default();
24435 };
24436 let muted_style = style.highlight(fade_out);
24437
24438 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24439 if range.start >= label.filter_range.end {
24440 if range.start > prev_end {
24441 runs.push((prev_end..range.start, fade_out));
24442 }
24443 runs.push((range.clone(), muted_style));
24444 } else if range.end <= label.filter_range.end {
24445 runs.push((range.clone(), style));
24446 } else {
24447 runs.push((range.start..label.filter_range.end, style));
24448 runs.push((label.filter_range.end..range.end, muted_style));
24449 }
24450 prev_end = cmp::max(prev_end, range.end);
24451
24452 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24453 runs.push((prev_end..label.text.len(), fade_out));
24454 }
24455
24456 runs
24457 })
24458}
24459
24460pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24461 let mut prev_index = 0;
24462 let mut prev_codepoint: Option<char> = None;
24463 text.char_indices()
24464 .chain([(text.len(), '\0')])
24465 .filter_map(move |(index, codepoint)| {
24466 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24467 let is_boundary = index == text.len()
24468 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24469 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24470 if is_boundary {
24471 let chunk = &text[prev_index..index];
24472 prev_index = index;
24473 Some(chunk)
24474 } else {
24475 None
24476 }
24477 })
24478}
24479
24480pub trait RangeToAnchorExt: Sized {
24481 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24482
24483 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24484 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24485 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24486 }
24487}
24488
24489impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24490 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24491 let start_offset = self.start.to_offset(snapshot);
24492 let end_offset = self.end.to_offset(snapshot);
24493 if start_offset == end_offset {
24494 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24495 } else {
24496 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24497 }
24498 }
24499}
24500
24501pub trait RowExt {
24502 fn as_f64(&self) -> f64;
24503
24504 fn next_row(&self) -> Self;
24505
24506 fn previous_row(&self) -> Self;
24507
24508 fn minus(&self, other: Self) -> u32;
24509}
24510
24511impl RowExt for DisplayRow {
24512 fn as_f64(&self) -> f64 {
24513 self.0 as _
24514 }
24515
24516 fn next_row(&self) -> Self {
24517 Self(self.0 + 1)
24518 }
24519
24520 fn previous_row(&self) -> Self {
24521 Self(self.0.saturating_sub(1))
24522 }
24523
24524 fn minus(&self, other: Self) -> u32 {
24525 self.0 - other.0
24526 }
24527}
24528
24529impl RowExt for MultiBufferRow {
24530 fn as_f64(&self) -> f64 {
24531 self.0 as _
24532 }
24533
24534 fn next_row(&self) -> Self {
24535 Self(self.0 + 1)
24536 }
24537
24538 fn previous_row(&self) -> Self {
24539 Self(self.0.saturating_sub(1))
24540 }
24541
24542 fn minus(&self, other: Self) -> u32 {
24543 self.0 - other.0
24544 }
24545}
24546
24547trait RowRangeExt {
24548 type Row;
24549
24550 fn len(&self) -> usize;
24551
24552 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24553}
24554
24555impl RowRangeExt for Range<MultiBufferRow> {
24556 type Row = MultiBufferRow;
24557
24558 fn len(&self) -> usize {
24559 (self.end.0 - self.start.0) as usize
24560 }
24561
24562 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24563 (self.start.0..self.end.0).map(MultiBufferRow)
24564 }
24565}
24566
24567impl RowRangeExt for Range<DisplayRow> {
24568 type Row = DisplayRow;
24569
24570 fn len(&self) -> usize {
24571 (self.end.0 - self.start.0) as usize
24572 }
24573
24574 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24575 (self.start.0..self.end.0).map(DisplayRow)
24576 }
24577}
24578
24579/// If select range has more than one line, we
24580/// just point the cursor to range.start.
24581fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24582 if range.start.row == range.end.row {
24583 range
24584 } else {
24585 range.start..range.start
24586 }
24587}
24588pub struct KillRing(ClipboardItem);
24589impl Global for KillRing {}
24590
24591const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24592
24593enum BreakpointPromptEditAction {
24594 Log,
24595 Condition,
24596 HitCondition,
24597}
24598
24599struct BreakpointPromptEditor {
24600 pub(crate) prompt: Entity<Editor>,
24601 editor: WeakEntity<Editor>,
24602 breakpoint_anchor: Anchor,
24603 breakpoint: Breakpoint,
24604 edit_action: BreakpointPromptEditAction,
24605 block_ids: HashSet<CustomBlockId>,
24606 editor_margins: Arc<Mutex<EditorMargins>>,
24607 _subscriptions: Vec<Subscription>,
24608}
24609
24610impl BreakpointPromptEditor {
24611 const MAX_LINES: u8 = 4;
24612
24613 fn new(
24614 editor: WeakEntity<Editor>,
24615 breakpoint_anchor: Anchor,
24616 breakpoint: Breakpoint,
24617 edit_action: BreakpointPromptEditAction,
24618 window: &mut Window,
24619 cx: &mut Context<Self>,
24620 ) -> Self {
24621 let base_text = match edit_action {
24622 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24623 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24624 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24625 }
24626 .map(|msg| msg.to_string())
24627 .unwrap_or_default();
24628
24629 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24630 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24631
24632 let prompt = cx.new(|cx| {
24633 let mut prompt = Editor::new(
24634 EditorMode::AutoHeight {
24635 min_lines: 1,
24636 max_lines: Some(Self::MAX_LINES as usize),
24637 },
24638 buffer,
24639 None,
24640 window,
24641 cx,
24642 );
24643 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24644 prompt.set_show_cursor_when_unfocused(false, cx);
24645 prompt.set_placeholder_text(
24646 match edit_action {
24647 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24648 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24649 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24650 },
24651 window,
24652 cx,
24653 );
24654
24655 prompt
24656 });
24657
24658 Self {
24659 prompt,
24660 editor,
24661 breakpoint_anchor,
24662 breakpoint,
24663 edit_action,
24664 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24665 block_ids: Default::default(),
24666 _subscriptions: vec![],
24667 }
24668 }
24669
24670 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24671 self.block_ids.extend(block_ids)
24672 }
24673
24674 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24675 if let Some(editor) = self.editor.upgrade() {
24676 let message = self
24677 .prompt
24678 .read(cx)
24679 .buffer
24680 .read(cx)
24681 .as_singleton()
24682 .expect("A multi buffer in breakpoint prompt isn't possible")
24683 .read(cx)
24684 .as_rope()
24685 .to_string();
24686
24687 editor.update(cx, |editor, cx| {
24688 editor.edit_breakpoint_at_anchor(
24689 self.breakpoint_anchor,
24690 self.breakpoint.clone(),
24691 match self.edit_action {
24692 BreakpointPromptEditAction::Log => {
24693 BreakpointEditAction::EditLogMessage(message.into())
24694 }
24695 BreakpointPromptEditAction::Condition => {
24696 BreakpointEditAction::EditCondition(message.into())
24697 }
24698 BreakpointPromptEditAction::HitCondition => {
24699 BreakpointEditAction::EditHitCondition(message.into())
24700 }
24701 },
24702 cx,
24703 );
24704
24705 editor.remove_blocks(self.block_ids.clone(), None, cx);
24706 cx.focus_self(window);
24707 });
24708 }
24709 }
24710
24711 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24712 self.editor
24713 .update(cx, |editor, cx| {
24714 editor.remove_blocks(self.block_ids.clone(), None, cx);
24715 window.focus(&editor.focus_handle);
24716 })
24717 .log_err();
24718 }
24719
24720 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24721 let settings = ThemeSettings::get_global(cx);
24722 let text_style = TextStyle {
24723 color: if self.prompt.read(cx).read_only(cx) {
24724 cx.theme().colors().text_disabled
24725 } else {
24726 cx.theme().colors().text
24727 },
24728 font_family: settings.buffer_font.family.clone(),
24729 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24730 font_size: settings.buffer_font_size(cx).into(),
24731 font_weight: settings.buffer_font.weight,
24732 line_height: relative(settings.buffer_line_height.value()),
24733 ..Default::default()
24734 };
24735 EditorElement::new(
24736 &self.prompt,
24737 EditorStyle {
24738 background: cx.theme().colors().editor_background,
24739 local_player: cx.theme().players().local(),
24740 text: text_style,
24741 ..Default::default()
24742 },
24743 )
24744 }
24745}
24746
24747impl Render for BreakpointPromptEditor {
24748 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24749 let editor_margins = *self.editor_margins.lock();
24750 let gutter_dimensions = editor_margins.gutter;
24751 h_flex()
24752 .key_context("Editor")
24753 .bg(cx.theme().colors().editor_background)
24754 .border_y_1()
24755 .border_color(cx.theme().status().info_border)
24756 .size_full()
24757 .py(window.line_height() / 2.5)
24758 .on_action(cx.listener(Self::confirm))
24759 .on_action(cx.listener(Self::cancel))
24760 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24761 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24762 }
24763}
24764
24765impl Focusable for BreakpointPromptEditor {
24766 fn focus_handle(&self, cx: &App) -> FocusHandle {
24767 self.prompt.focus_handle(cx)
24768 }
24769}
24770
24771fn all_edits_insertions_or_deletions(
24772 edits: &Vec<(Range<Anchor>, String)>,
24773 snapshot: &MultiBufferSnapshot,
24774) -> bool {
24775 let mut all_insertions = true;
24776 let mut all_deletions = true;
24777
24778 for (range, new_text) in edits.iter() {
24779 let range_is_empty = range.to_offset(snapshot).is_empty();
24780 let text_is_empty = new_text.is_empty();
24781
24782 if range_is_empty != text_is_empty {
24783 if range_is_empty {
24784 all_deletions = false;
24785 } else {
24786 all_insertions = false;
24787 }
24788 } else {
24789 return false;
24790 }
24791
24792 if !all_insertions && !all_deletions {
24793 return false;
24794 }
24795 }
24796 all_insertions || all_deletions
24797}
24798
24799struct MissingEditPredictionKeybindingTooltip;
24800
24801impl Render for MissingEditPredictionKeybindingTooltip {
24802 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24803 ui::tooltip_container(cx, |container, cx| {
24804 container
24805 .flex_shrink_0()
24806 .max_w_80()
24807 .min_h(rems_from_px(124.))
24808 .justify_between()
24809 .child(
24810 v_flex()
24811 .flex_1()
24812 .text_ui_sm(cx)
24813 .child(Label::new("Conflict with Accept Keybinding"))
24814 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24815 )
24816 .child(
24817 h_flex()
24818 .pb_1()
24819 .gap_1()
24820 .items_end()
24821 .w_full()
24822 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24823 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
24824 }))
24825 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24826 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24827 })),
24828 )
24829 })
24830 }
24831}
24832
24833#[derive(Debug, Clone, Copy, PartialEq)]
24834pub struct LineHighlight {
24835 pub background: Background,
24836 pub border: Option<gpui::Hsla>,
24837 pub include_gutter: bool,
24838 pub type_id: Option<TypeId>,
24839}
24840
24841struct LineManipulationResult {
24842 pub new_text: String,
24843 pub line_count_before: usize,
24844 pub line_count_after: usize,
24845}
24846
24847fn render_diff_hunk_controls(
24848 row: u32,
24849 status: &DiffHunkStatus,
24850 hunk_range: Range<Anchor>,
24851 is_created_file: bool,
24852 line_height: Pixels,
24853 editor: &Entity<Editor>,
24854 _window: &mut Window,
24855 cx: &mut App,
24856) -> AnyElement {
24857 h_flex()
24858 .h(line_height)
24859 .mr_1()
24860 .gap_1()
24861 .px_0p5()
24862 .pb_1()
24863 .border_x_1()
24864 .border_b_1()
24865 .border_color(cx.theme().colors().border_variant)
24866 .rounded_b_lg()
24867 .bg(cx.theme().colors().editor_background)
24868 .gap_1()
24869 .block_mouse_except_scroll()
24870 .shadow_md()
24871 .child(if status.has_secondary_hunk() {
24872 Button::new(("stage", row as u64), "Stage")
24873 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24874 .tooltip({
24875 let focus_handle = editor.focus_handle(cx);
24876 move |window, cx| {
24877 Tooltip::for_action_in(
24878 "Stage Hunk",
24879 &::git::ToggleStaged,
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 editor.stage_or_unstage_diff_hunks(
24891 true,
24892 vec![hunk_range.start..hunk_range.start],
24893 cx,
24894 );
24895 });
24896 }
24897 })
24898 } else {
24899 Button::new(("unstage", row as u64), "Unstage")
24900 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24901 .tooltip({
24902 let focus_handle = editor.focus_handle(cx);
24903 move |window, cx| {
24904 Tooltip::for_action_in(
24905 "Unstage Hunk",
24906 &::git::ToggleStaged,
24907 &focus_handle,
24908 window,
24909 cx,
24910 )
24911 }
24912 })
24913 .on_click({
24914 let editor = editor.clone();
24915 move |_event, _window, cx| {
24916 editor.update(cx, |editor, cx| {
24917 editor.stage_or_unstage_diff_hunks(
24918 false,
24919 vec![hunk_range.start..hunk_range.start],
24920 cx,
24921 );
24922 });
24923 }
24924 })
24925 })
24926 .child(
24927 Button::new(("restore", row as u64), "Restore")
24928 .tooltip({
24929 let focus_handle = editor.focus_handle(cx);
24930 move |window, cx| {
24931 Tooltip::for_action_in(
24932 "Restore Hunk",
24933 &::git::Restore,
24934 &focus_handle,
24935 window,
24936 cx,
24937 )
24938 }
24939 })
24940 .on_click({
24941 let editor = editor.clone();
24942 move |_event, window, cx| {
24943 editor.update(cx, |editor, cx| {
24944 let snapshot = editor.snapshot(window, cx);
24945 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
24946 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24947 });
24948 }
24949 })
24950 .disabled(is_created_file),
24951 )
24952 .when(
24953 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24954 |el| {
24955 el.child(
24956 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24957 .shape(IconButtonShape::Square)
24958 .icon_size(IconSize::Small)
24959 // .disabled(!has_multiple_hunks)
24960 .tooltip({
24961 let focus_handle = editor.focus_handle(cx);
24962 move |window, cx| {
24963 Tooltip::for_action_in(
24964 "Next Hunk",
24965 &GoToHunk,
24966 &focus_handle,
24967 window,
24968 cx,
24969 )
24970 }
24971 })
24972 .on_click({
24973 let editor = editor.clone();
24974 move |_event, window, cx| {
24975 editor.update(cx, |editor, cx| {
24976 let snapshot = editor.snapshot(window, cx);
24977 let position =
24978 hunk_range.end.to_point(&snapshot.buffer_snapshot());
24979 editor.go_to_hunk_before_or_after_position(
24980 &snapshot,
24981 position,
24982 Direction::Next,
24983 window,
24984 cx,
24985 );
24986 editor.expand_selected_diff_hunks(cx);
24987 });
24988 }
24989 }),
24990 )
24991 .child(
24992 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24993 .shape(IconButtonShape::Square)
24994 .icon_size(IconSize::Small)
24995 // .disabled(!has_multiple_hunks)
24996 .tooltip({
24997 let focus_handle = editor.focus_handle(cx);
24998 move |window, cx| {
24999 Tooltip::for_action_in(
25000 "Previous Hunk",
25001 &GoToPreviousHunk,
25002 &focus_handle,
25003 window,
25004 cx,
25005 )
25006 }
25007 })
25008 .on_click({
25009 let editor = editor.clone();
25010 move |_event, window, cx| {
25011 editor.update(cx, |editor, cx| {
25012 let snapshot = editor.snapshot(window, cx);
25013 let point =
25014 hunk_range.start.to_point(&snapshot.buffer_snapshot());
25015 editor.go_to_hunk_before_or_after_position(
25016 &snapshot,
25017 point,
25018 Direction::Prev,
25019 window,
25020 cx,
25021 );
25022 editor.expand_selected_diff_hunks(cx);
25023 });
25024 }
25025 }),
25026 )
25027 },
25028 )
25029 .into_any_element()
25030}
25031
25032pub fn multibuffer_context_lines(cx: &App) -> u32 {
25033 EditorSettings::try_get(cx)
25034 .map(|settings| settings.excerpt_context_lines)
25035 .unwrap_or(2)
25036 .min(32)
25037}